Support for Secure Transport SSL library on macOS as OpenSSL alternative
In /messages/by-id/69DB7657-3F9D-4D30-8A4B-E06034251F61@yesql.se I
presented a WIP patch for adding support for the Apple Secure Transport SSL
library on macOS as, an alternative to OpenSSL. That patch got put on the
backburner for a bit, but I’ve now found the time to make enough progress to
warrant a new submission for discussions on this (and hopefully help hacking).
It is a drop-in replacement for the OpenSSL code, and supports all the same
features and options, except for two things: compression is not supported and
the CRL cannot be loaded from a plain PEM file. A Keychain must be used for
that instead.
Current state
=============
The frontend and backend are implemented more or less fully, the two main
things missing being the CRL support (further details below) and loading DH
files (to support the GUC added in c0a15e07cd). All non-CRL tests but one are
passing. The failing test is in the frontend and it also fails when running
against an OpenSSL backend, but I can’t find where the logic is flawed and
could do with some help there.
Threads
=======
Just like the CFLocaleCopyCurrent() call referenced in postmaster.c, the Secure
Transport APIs makes the process multithreaded. To keep this out of the
postmaster, and contained in the backend, I’ve moved all functionality to
open_server and left init empty. I could definitely need some clues on how to
properly handle this, or if it’s a complete showstopper.
Keychains
=========
The frontend has support for using PEM files for certificates and keys. It can
also look up the key for the certificate in a Keychain, or both certificate and
key in a Keychain. The root certificate is still just read from a PEM file.
The existence of an sslcert config trumps a keychain, but when a keychain is
used I’m currently using the sslcert parameter (awaiting a discussion on how to
properly do this) for the certificate label required to search the keychain.
There is a new frontend parameter called “keychain” with which a path to a
custom Keychain file can be passed. If set, this Keychain will be searched as
well as the default. If not, only the default user Keychain is used. There is
nothing that modifies the Keychain in this patch, it can read identities
(certificates and its key) but not alter them in any way.
The backend is only supporting PEM files at this point.
Once we have support for Keychains, we can however use them for additionally
supporting other Secure Transport features like OCSP etc.
“keychain” is obviously a very Secure Transport specific name, and I personally
think we should avoid that. Any new configuration added here should take
future potential implementation into consideration such that avoid the need for
lots of backend specific knobs. “sslcertstore” comes to mind as an
alternative, but we’d also need parameters to point into the certstore for
finding what we need. Another option would be to do a URL based scheme
perhaps.
Certificate Revocation
======================
Secure Transport supports loading CRL lists into Keychain files, the command
line certtool can for example do that. When doing the trust evaluation on the
connection, a policy can be added which enables revocation checking via CRL. I
have however been unable to figure out how to load a CRL list programmatically,
and as far as I can tell there is no API for this. The certtool utility is
using the low-level CSSM APIs for this it seems, but adding that level of
complexity doesn’t seem worth it for us to maintain (I did a test and it turned
big, ugly and messy).
Unless someone knows how to implement this, we’d be limited to requiring the
use of a Keychain file for CRL support. This would break drop-in replacement
support, but supporting certificate revocation seems more important.
Platform Support
================
I’ve tested this on 10.11 El Capitan and 10.12 Sierra, which are the systems I
have access to. Supporting prairiedog and dromedary in the buildfarm will
probably be too hard (if at all possible), but down to 10.9 Mavericks should be
quite possible (if someone has the required systems to test on). It adds a
dependency on Core Foundation on top of Secure Transport, no other macOS APIs
are used.
Testing
=======
In order to test this we need to provide an alternative to the openssl calls in
src/test/ssl/Makefile for Secure Transport. On top of that, code to generate
Keychains is required. The certtool application can do all the Keychain
generations (I think) but this is still left open. The main thing to figure
out here is how to avoid having to type in the Keychain password in a modal GUI
that Secure Transport pops up. Since a Keychain can be passwordless it should
be doable, but the right certtool incantations for that is still evading me.
I’ve included a show-and-tell patch for this which I’ve used locally for
testing during hacking.
Documentation
=============
I have started fiddling with this a little, but to avoid spending time on the
wrong thing I have done very little awaiting the outcome of discussions here.
I have tried to add lots of comments to the code however, to explain the quirks
of Secure Transport.
I went into this thinking I would write a README for how to implement a new SSL
library. But in the end, turns out the refactoring that went into our SSL code
earlier made that part almost too easy to warrant that. It’s really quite
straightforward.
Patches
=======
0001 - Adds support for running the SSL tests against another set of server
binaries. This is only useful for testing during the implementation of a new
SSL library, but then it’s crucial. Nothing Secure Transport specific in this
patch.
0002 - Secure Transport fronten and backend as well as required scaffolding for
building and a new GUC to show the current backend.
securetransport_test.diff - Some ugly hacks I’ve used for testing, included as
a show-and-tell, not for any form of submission.
This leaves quite a few open questions that I hope to get some feedback on, and
some code issues which I’d love eyes/hacking on. Do we still want this, and if
so how to handle that we can’t be fully drop-in compatible with OpenSSL with
regards to using a PEM file only configuration? Should we support PEM files at
all in the backend or only Keychains? Another option could be to support
PKCS12 files instead of (or additionally to) PEM since there is likely to be
API support for loading PKCS12 and they can afaik contain CRLs. How can we
ensure that new parameters hopefully covers more libraries than just Secure
Transport?
cheers ./daniel
Attachments:
0002-WIP-Add-support-for-Apple-Secure-Transport-SSL-libra.patchapplication/octet-stream; name=0002-WIP-Add-support-for-Apple-Secure-Transport-SSL-libra.patchDownload
From 5192f9cfe8efe322f6b2d5c1e70290a250f9d0c3 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 2 Aug 2017 23:09:07 +0200
Subject: [PATCH 2/2] WIP: Add support for Apple Secure Transport SSL library
This adds frontend and backend support for the Secure Transport SSL
library on macOS. The patch is not in a final state, but works to a
fairly large degree. See email to -hackers for full details.
---
configure | 57 +
configure.in | 14 +
src/Makefile.global.in | 1 +
src/backend/Makefile | 4 +
src/backend/libpq/Makefile | 5 +
src/backend/libpq/be-secure-securetransport.c | 1028 +++++++++++++++++
src/backend/utils/misc/guc.c | 13 +
src/include/common/securetransport.h | 510 ++++++++
src/include/common/sha2.h | 4 +-
src/include/libpq/libpq-be.h | 17 +-
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 10 +-
src/interfaces/libpq/Makefile | 6 +-
src/interfaces/libpq/fe-connect.c | 6 +
src/interfaces/libpq/fe-secure-securetransport.c | 1342 ++++++++++++++++++++++
src/interfaces/libpq/libpq-int.h | 22 +-
16 files changed, 3031 insertions(+), 11 deletions(-)
create mode 100644 src/backend/libpq/be-secure-securetransport.c
create mode 100644 src/include/common/securetransport.h
create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c
diff --git a/configure b/configure
index 8b24bf1128..a345d4b2bd 100755
--- a/configure
+++ b/configure
@@ -709,6 +709,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
krb_srvtab
with_python
@@ -838,6 +839,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1534,6 +1536,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -6052,6 +6055,41 @@ $as_echo "$with_openssl" >&6; }
#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
+#
# SELinux
#
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with SELinux support" >&5
@@ -11017,6 +11055,25 @@ fi
fi
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+fi
+
if test "$with_pam" = yes ; then
for ac_header in security/pam_appl.h
do :
diff --git a/configure.in b/configure.in
index a0f0f85add..ed4ac073aa 100644
--- a/configure.in
+++ b/configure.in
@@ -735,6 +735,15 @@ AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
+#
# SELinux
#
AC_MSG_CHECKING([whether to build with SELinux support])
@@ -1255,6 +1264,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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 0d3f8ca950..76a4af7116 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_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_libxml = @with_libxml@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index bce9d2c3eb..9c4b248185 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -49,6 +49,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LDFLAGS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-schemapg postgres $(POSTGRES_IMP)
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 7fa2b02743..3a835235b9 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000000..88368aae98
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1028 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - Load DH keys from file
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future).
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interested to investigate.
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#define bool pg_bool
+#include <Security/cssmerr.h>
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef bool
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_bool bool
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static OSStatus load_certificate(char *name, CFArrayRef *cert_array);
+static void load_key(char *name, CFArrayRef *out);
+
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+
+/* ------------------------------------------------------------ */
+/* Hardcoded DH parameters */
+/* ------------------------------------------------------------ */
+
+/*
+ * Secure Transport doesn't support setting different keysizes for DH like
+ * the OpenSSL callback, instead a single keysize is set and there doesn't
+ * seem to be any negotiation from that?
+ */
+static const uint8_t file_dh2048[] =
+ "\x30\x82\x01\x08\x02\x82\x01\x01\x00\xa3\x09\x9b\x20\x73\xdd\x59"
+ "\x1b\x91\x05\x5b\x6c\x5f\xe7\xd7\xea\xaa\x01\x73\xe3\xf5\x89\xbe"
+ "\xc7\xb5\x38\x12\xf5\x28\x40\x03\x32\x89\x48\x39\x77\xb7\xa3\xa0"
+ "\x83\x5f\xff\xbe\x03\xe2\xa5\xf6\x64\xb7\xba\xbd\xb7\xeb\x57\x42"
+ "\xe3\x16\x12\xc3\x9d\xba\x06\xf0\xbb\xab\x13\x98\x0a\xcc\x6c\x63"
+ "\xb9\xca\xb0\x39\x86\x9e\xb7\xa0\x23\x96\xd5\xce\x24\x44\x2a\x05"
+ "\x1c\xe5\x69\x5d\xd1\x83\x8c\xc1\x92\xb1\xd6\x18\xe5\x4e\xc8\xeb"
+ "\x21\xe8\x16\x87\x69\x4f\x86\x95\x25\x10\xdb\x1e\x49\x1c\x80\x3e"
+ "\x9a\x3d\x43\x66\xf7\x45\x0d\x37\x61\x37\xad\xce\x31\xec\x3b\xbd"
+ "\x55\x08\xe9\xb2\x97\xf0\xfc\x59\x8e\xd3\x73\xe2\x4a\x9b\x58\xbb"
+ "\x0a\x34\xb7\xea\x42\x94\xf9\xf5\xba\xf2\x06\xd9\xe6\xf1\xa7\x4a"
+ "\x6f\xd4\x72\x1f\x8d\x20\x63\x85\x29\xe5\x90\x59\xc0\x36\x3e\x16"
+ "\x5c\xa4\x46\xac\x44\x8c\x89\x00\x5d\xa2\xe9\x5b\xf0\xe4\x8c\xea"
+ "\xa6\x37\xac\xf8\x2a\x33\xbb\x39\xc6\xdf\x14\x96\x64\x13\x9e\x99"
+ "\xd8\xdd\x94\x61\xfe\xa1\x87\x48\xb6\x65\x69\xc7\xe7\x49\x53\xcf"
+ "\xa8\xa1\xc0\x9d\xf6\x7c\x29\xfc\xb2\x74\xb1\x2d\xe5\x0c\xd0\x32"
+ "\xc4\x35\x85\x82\x30\x07\x60\x01\x93\x02\x01\x02";
+
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+
+ /*
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ */
+
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * bt_tls_open_server
+ * Attempt to negotiate a secure connection
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFMutableArrayRef chain;
+
+ Assert(!port->ssl);
+
+ status = load_certificate(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": \"%s\"",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ load_key(ssl_key_file, &keys);
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": \"%s\"",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API
+ */
+ if (ssl_crl_file[0])
+ ereport(FATAL,
+ (errmsg("CRL files not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ /*
+ * We use kTryAuthenticate here since we don't know which sslmode the
+ * client is using. If we were to use kAlwaysAuthenticate then sslmode
+ * require won't work as intended.
+ */
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * TODO: This loads a pregenerated DH parameter, code for loading DH from a
+ * specified file needs to be added. In Secure Transport, DH is always on
+ * an in case no parameter has been loaded one with be precomputed
+ * automatically, so that step is not required to be added. This is of
+ * course the one step we want to avoid since it consumes a lot of time.
+ */
+ SSLSetDiffieHellmanParams((SSLContextRef) port->ssl,
+ file_dh2048, sizeof(file_dh2048));
+
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ if (status == errSSLWouldBlock)
+ continue;
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+
+ CFStringRef usercert_cn;
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+ port->peer_cn = pstrdup(CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8));
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ *
+ * Loads a private key from the specified filename. Unless the key loads this
+ * will not return but will error out.
+ */
+static void
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+
+ ret = stat(name, &stat_buf);
+ if (ret != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to stat",
+ name)));
+
+ if (!S_ISREG(stat_buf.st_mode))
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": not a regular file",
+ name)));
+
+ /*
+ * Require no public access to the key file. If the file is owned by us,
+ * require mode 0600 or less. If owned by root, require 0640 or less to
+ * allow read access through our gid, or a supplementary gid that allows to
+ * read system-wide certificates.
+ */
+ if ((stat_buf.st_uid == geteuid() && stat_buf.st_mode & (S_IRWXG | S_IRWXO)) ||
+ (stat_buf.st_uid == 0 && stat_buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" has group or world access",
+ name),
+ errdetail("File must have permissions u=rw (0600) or less "
+ "if owned by the database user, or permissions "
+ "u=rw,g=r (0640) or less if owned by root.")));
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ SecItemImportExportKeyParameters params;
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": \"%s\"",
+ name, pg_SSLerrmessage(status))));
+}
+
+/*
+ * load_certificate
+ * Extracts a certificate from a PEM file on the filesystem
+ *
+ * TODO: figure out better returncodes
+ */
+static OSStatus
+load_certificate(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ /*
+ * If the configured ssl_cert_file filename is set to a non-existing
+ * file, assume it's referencing a Keychain label and attempt to load
+ * the certificate from the Keychain instead.
+ */
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ /*
+ * TODO: Do we want to search keychains for certificates serverside
+ * like we do clientside, or do we want to stick to a single way to
+ * configure the server? Since CRL files aren't supported outside
+ * keychains I guess we need to, but worth a discussion.
+ */
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ return errSecInternalError;
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ return errSecInternalError;
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL, cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ return errSecInternalError;
+}
+
+
+/*
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+void
+be_tls_get_version(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (ptr == NULL || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ strlcpy(ptr, "TLSv1.1", len);
+ break;
+ case kTLSProtocol12:
+ strlcpy(ptr, "TLSv1.2", len);
+ break;
+ default:
+ strlcpy(ptr, "unknown", len);
+ break;
+ }
+ }
+}
+
+/*
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ *
+ * Returns NULL in case we weren't able to either get the negotiated cipher, or
+ * translate it into a human readable string.
+ */
+void
+be_tls_get_cipher(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+ const char *cipher_name;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return;
+
+ cipher_name = pg_SSLciphername(cipher);
+ if (cipher_name != NULL)
+ strlcpy(ptr, cipher_name, len);
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ /*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there still is no translation for it in 10.11 El Capitan, so we
+ * maintain our own
+ */
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 246fea8693..c026dbad12 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -501,6 +501,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -3298,6 +3299,18 @@ static struct config_string ConfigureNamesString[] =
},
{
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
+ {
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
gettext_noop("Sets the current role."),
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000000..997c504e41
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,510 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport_common.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index a31b3979d8..2851d3eea0 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7bde744d51..0f9ff6b88c 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,19 +191,22 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+ void *ssl;
+ int ssl_buffered;
#endif
} Port;
#ifdef USE_SSL
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
extern int be_tls_init(bool isServerStart);
extern void be_tls_destroy(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 7a05c7e5b8..4051ec32cc 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -844,6 +844,9 @@
/* Define to use OpenSSL for random number generation */
#undef USE_OPENSSL_RANDOM
+/* Define to build with Apple Secure Transport support. (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index f3b35297d1..48cc363803 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -166,11 +166,15 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 87f22d242f..ad615420d6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,11 @@ else
OBJS += sha2.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -112,7 +117,6 @@ ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
-
distprep: libpq-dist.rc
libpq.rc libpq-dist.rc: libpq.rc.in
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 5a964bf0b5..f255ee5487 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -323,6 +323,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000000..f5593b7ab2
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1342 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readiblitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *certificate,
+ SecIdentityRef *identity,
+ SecKeychainRef keychain);
+static OSStatus import_pem(const char *path, int size, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * Exported function to allow application to tell us it's already initialized
+ * Secure Transport and/or libcrypto.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ return;
+}
+
+/*
+ * Begin or continue negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin(conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection(conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs(conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we dont have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ SSLSetPeerDomainName(conn->ssl, conn->pghost, strlen(conn->pghost));
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption(conn->ssl, kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake(conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust(conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ /*
+ * TODO: return a better error code than SSLInternalError
+ */
+ if (!trusted)
+ return errSecInternalError;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize(conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead(conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite(conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite(conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+/*
+ * Initialize SSL system, in particular creating the SSL_context object
+ * that will be shared by all SSL-using connections in this process.
+ *
+ * In threadsafe mode, this includes setting up libcrypto callback functions
+ * to do thread locking.
+ *
+ * If the caller has told us (through PQinitOpenSSL) that he's taking care
+ * of libcrypto, we expect that callbacks are already set, and won't try to
+ * override it.
+ *
+ * The conn parameter is only used to be able to pass back an error
+ * message - no connection-local setup is made here.
+ *
+ * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose(conn->ssl);
+ CFRelease(conn->ssl);
+
+ /* TODO: Release any certificates loaded */
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_certificate_keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * certificate with the passed identity. Keychains are searched by creating a
+ * dictionary of key/value pairs with the search criteria and then asking for a
+ * copy of the matching entry/entries to the search criteria.
+ */
+static OSStatus
+import_certificate_keychain(const char *certificate, SecIdentityRef *identity,
+ SecKeychainRef keychain)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ query = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, certificate, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychain)
+ CFDictionaryAddValue(query, kSecUseKeychain, keychain);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(false, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, int size, char *passphrase, CFArrayRef *certificate)
+{
+ OSStatus status;
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+
+ Assert(path && strlen(path) > 0);
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInternalError;
+
+ certdata = malloc(size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, size, fp) != size)
+ {
+ fclose(fp);
+ return errSecInternalError;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, size);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecInternalError;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ status = SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+
+ return status;
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ char sebuf[256];
+ bool have_homedir;
+ bool have_cert = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching.
+ */
+ if (conn->keychain)
+ {
+ SecKeychainRef kcref[2];
+
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr)
+ {
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the
+ * files could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Prepare the filename for reading the certificate/ A configured sslcert
+ * has priority
+ */
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ /* If there is a keychain explicitly configured it trumps defaults */
+ else if (have_homedir && !conn->keychain)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a file matching the path, the certificate must be loaded
+ * from this file for us to continue.
+ */
+ if (fnbuf[0] != '\0' && stat(fnbuf, &buf) == 0)
+ {
+ status = import_pem(fnbuf, buf.st_size, NULL, cert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ have_cert = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in order
+ * to create the identity. There are two places where we can find it:
+ * in a file on the filesystem or in a Keychain. If the sslkey config
+ * isn't set it can mean two things, either the user wants the default
+ * or the key is in a Keychain in which case there is sslkey name to
+ * reference since the key is located by the certificate. At this point
+ * we don't know which, so we look for the default file first and fall
+ * back to a Keychain lookup.
+ *
+ * First we look for the presence of a file.
+ */
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the key to be
+ * loaded from that file.
+ */
+ if (fnbuf[0] != '\0' && stat(fnbuf, &buf) == 0)
+ {
+ status = import_pem(fnbuf, buf.st_size, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ /*
+ * There is no matching file, so we attempt to load the key from a
+ * Keychain by referencing the certificate.
+ */
+ else
+ {
+ /*
+ * Search for the private key matching the cert_ref in the default
+ * Keychain. If found, we get the identity returned.
+ */
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref, &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found on disk or in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ }
+ /*
+ * If the certificate file is not present, it might be a reference to an
+ * identity in a Keychain instead so don't treat that as an error. Any
+ * other error, however, is grounds for complaint.
+ */
+ else
+ {
+ if (errno != ENOENT && errno != ENOTDIR)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not open certificate file \"%s\": %s\n"),
+ fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
+ return errSecInternalError;
+ }
+ }
+
+ if (!identity)
+ {
+ /*
+ * The certificate is not located in a file, this means that we must
+ * look for the complete identity in a Keychain instead.
+ */
+ if (keychains)
+ {
+ import_certificate_keychain(conn->sslcert ? conn->sslcert : USER_CERT_FILE, &identity,
+ (SecKeychainRef) CFArrayGetValueAtIndex(keychains, 0));
+ if (!identity)
+ import_certificate_keychain(conn->sslcert ? conn->sslcert : USER_CERT_FILE, &identity,
+ (SecKeychainRef) CFArrayGetValueAtIndex(keychains, 1));
+ }
+ else
+ import_certificate_keychain(conn->sslcert ? conn->sslcert : USER_CERT_FILE, &identity, NULL);
+
+ if (identity)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+
+ if (identity)
+ {
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (have_cert)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate(conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+ }
+
+ /* Try to load the root cert */
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, buf.st_size, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+int
+PQsslInUse(PGconn *conn)
+{
+ if (!conn)
+ return 0;
+ return conn->ssl_in_use;
+}
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher(conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion(conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState(conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 42913604e3..0cf96c1f04 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -80,6 +80,14 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_SECURETRANSPORT
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#undef Size
+#undef uint64
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -467,8 +475,18 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+
+ SSLContextRef ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
--
2.13.0.rc0.45.ge2cb6ab.dirty
0001-Allow-running-SSL-tests-against-different-binaries.patchapplication/octet-stream; name=0001-Allow-running-SSL-tests-against-different-binaries.patchDownload
From dc4f937e1d7bd9ebabef056fb9b702e8fac06e40 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 2 Aug 2017 23:02:24 +0200
Subject: [PATCH 1/2] Allow running SSL tests against different binaries
In order to be able to test work on SSL frontends, or backends,
against a stock installation, allow a pgpath setting to override
the path to the binaries. The SSLTEST_SERVER_BIN environment var
will be used, if set, for which set of server binaries to run.
SSLTEST_SERVER_BIN=/path/bin/ make -C src/test/ssl/ check
This is for testing during development in order to test a new
frontend against a known backend for example.
---
src/test/perl/PostgresNode.pm | 77 ++++++++++++++++++++++++++++++++----------
src/test/ssl/t/001_ssltests.pl | 2 +-
2 files changed, 60 insertions(+), 19 deletions(-)
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index bf982101a5..86236bbd91 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -132,7 +132,7 @@ INIT
=over
-=item PostgresNode::new($class, $name, $pghost, $pgport)
+=item PostgresNode::new($class, $name, $pghost, $pgport, $pgpath)
Create a new PostgresNode instance. Does not initdb or start it.
@@ -143,12 +143,14 @@ of finding port numbers, registering instances for cleanup, etc.
sub new
{
- my ($class, $name, $pghost, $pgport) = @_;
+ my ($class, $name, $pghost, $pgport, $pgpath) = @_;
my $testname = basename($0);
$testname =~ s/\.[^.]+$//;
+ $pgpath = '' unless defined $pgpath;
my $self = {
_port => $pgport,
_host => $pghost,
+ _pgpath => $pgpath,
_basedir => TestLib::tempdir("data_" . $name),
_name => $name,
_logfile => "$TestLib::log_path/${testname}_${name}.log" };
@@ -223,6 +225,21 @@ sub name
=pod
+=item $node->pgpath()
+
+Path to the PostgreSQL binaries to run, if empty then the installation in PATH
+is assumed.
+
+=cut
+
+sub pgpath
+{
+ my ($self) = @_;
+ return $self->{_pgpath};
+}
+
+=pod
+
=item $node->logfile()
Path to the PostgreSQL log file for this instance.
@@ -323,6 +340,7 @@ sub info
my $_info = '';
open my $fh, '>', \$_info or die;
print $fh "Name: " . $self->name . "\n";
+ print $fh "Binary directory: " . $self->pgpath . "\n";
print $fh "Data directory: " . $self->data_dir . "\n";
print $fh "Backup directory: " . $self->backup_dir . "\n";
print $fh "Archive directory: " . $self->archive_dir . "\n";
@@ -398,6 +416,7 @@ sub init
my $port = $self->port;
my $pgdata = $self->data_dir;
my $host = $self->host;
+ my $pgpath = $self->pgpath;
$params{allows_streaming} = 0 unless defined $params{allows_streaming};
$params{has_archiving} = 0 unless defined $params{has_archiving};
@@ -405,7 +424,7 @@ sub init
mkdir $self->backup_dir;
mkdir $self->archive_dir;
- TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+ TestLib::system_or_bail($pgpath . 'initdb', '-D', $pgdata, '-A', 'trust', '-N',
@{ $params{extra} });
TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata);
@@ -499,9 +518,10 @@ sub backup
my $backup_path = $self->backup_dir . '/' . $backup_name;
my $port = $self->port;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "# Taking pg_basebackup $backup_name from node \"$name\"\n";
- TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-p', $port,
+ TestLib::system_or_bail($pgpath . 'pg_basebackup', '-D', $backup_path, '-p', $port,
'--no-sync');
print "# Backup finished\n";
}
@@ -657,8 +677,9 @@ sub start
my $pgdata = $self->data_dir;
my $name = $self->name;
BAIL_OUT("node \"$name\" is already running") if defined $self->{_pid};
+ my $pgpath = $self->pgpath;
print("### Starting node \"$name\"\n");
- my $ret = TestLib::system_log('pg_ctl', '-D', $self->data_dir, '-l',
+ my $ret = TestLib::system_log($pgpath . 'pg_ctl', '-D', $self->data_dir, '-l',
$self->logfile, 'start');
if ($ret != 0)
@@ -689,10 +710,11 @@ sub stop
my $port = $self->port;
my $pgdata = $self->data_dir;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
$mode = 'fast' unless defined $mode;
return unless defined $self->{_pid};
print "### Stopping node \"$name\" using mode $mode\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-m', $mode, 'stop');
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-m', $mode, 'stop');
$self->_update_pid(0);
}
@@ -710,8 +732,10 @@ sub reload
my $port = $self->port;
my $pgdata = $self->data_dir;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Reloading node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, 'reload');
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, 'reload');
+
}
=pod
@@ -729,8 +753,9 @@ sub restart
my $pgdata = $self->data_dir;
my $logfile = $self->logfile;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Restarting node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-l', $logfile,
'restart');
$self->_update_pid(1);
}
@@ -750,8 +775,9 @@ sub promote
my $pgdata = $self->data_dir;
my $logfile = $self->logfile;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Promoting node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-l', $logfile,
'promote');
}
@@ -855,12 +881,15 @@ sub _update_pid
=pod
-=item PostgresNode->get_new_node(node_name)
+=item PostgresNode->get_new_node(node_name, pgpath)
Build a new object of class C<PostgresNode> (or of a subclass, if you have
one), assigning a free port number. Remembers the node, to prevent its port
number from being reused for another node, and to ensure that it gets
-shut down when the test script exits.
+shut down when the test script exits. If the pgpath parameter is defined then
+the path is tested for containing a PostgreSQL installation with the correct
+permissions for execution.
+
You should generally use this instead of C<PostgresNode::new(...)>.
@@ -872,8 +901,8 @@ which can only create objects of class C<PostgresNode>.
sub get_new_node
{
my $class = 'PostgresNode';
- $class = shift if 1 < scalar @_;
- my $name = shift;
+ $class = shift if 2 < scalar @_;
+ my ($name, $pgpath) = @_;
my $found = 0;
my $port = $last_port_assigned;
@@ -916,8 +945,19 @@ sub get_new_node
print "# Found free port $port\n";
+ # If a separate path to the binaries was specified then we do rudimentary
+ # checks for a sane installation. If an executable copy of initdb is there
+ # then let's go ahead.
+ if (defined $pgpath)
+ {
+ $pgpath =~ s!/*$!/!;
+ die "initdb missing in $pgpath" unless -x $pgpath . 'initdb';
+
+ print "# Found PostgreSQL installation in $pgpath\n";
+ }
+
# Lock port number found by creating a new node
- my $node = $class->new($name, $test_pghost, $port);
+ my $node = $class->new($name, $test_pghost, $port, $pgpath);
# Add node to list of nodes
push(@all_nodes, $node);
@@ -1087,8 +1127,9 @@ sub psql
my $stderr = $params{stderr};
my $timeout = undef;
my $timeout_exception = 'psql timed out';
+ my $pgpath = $self->pgpath;
my @psql_params =
- ('psql', '-XAtq', '-d', $self->connstr($dbname), '-f', '-');
+ ($pgpath . 'psql', '-XAtq', '-d', $self->connstr($dbname), '-f', '-');
# If the caller wants an array and hasn't passed stdout/stderr
# references, allocate temporary ones to capture them so we
@@ -1230,12 +1271,12 @@ Returns 1 if successful, 0 if timed out.
sub poll_query_until
{
- my ($self, $dbname, $query, $expected) = @_;
+ my ($self, $dbname, $query, $expected, $pgpath) = @_;
$expected = 't' unless defined($expected); # default value
my $cmd =
- [ 'psql', '-XAt', '-c', $query, '-d', $self->connstr($dbname) ];
+ [ $pgpath . 'psql', '-XAt', '-c', $query, '-d', $self->connstr($dbname) ];
my ($stdout, $stderr);
my $max_attempts = 180 * 10;
my $attempts = 0;
@@ -1448,7 +1489,7 @@ sub wait_for_catchup
. $self->name . "\n";
my $query =
qq[SELECT '$target_lsn' <= ${mode}_lsn FROM pg_catalog.pg_stat_replication WHERE application_name = '$standby_name';];
- $self->poll_query_until('postgres', $query)
+ $self->poll_query_until('postgres', $query, $self->pgpath)
or die "timed out waiting for catchup, current location is "
. ($self->safe_psql('postgres', $query) || '(unknown)');
print "done\n";
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 32df273929..f4daf46e44 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -59,7 +59,7 @@ chmod 0600, "ssl/client_tmp.key";
#### Part 0. Set up the server.
note "setting up data directory";
-my $node = get_new_node('master');
+my $node = get_new_node('master', $ENV{SSLTEST_SERVER_BIN});
$node->init;
# PGHOST is enforced here to set up the node, subsequent connections
--
2.13.0.rc0.45.ge2cb6ab.dirty
securetransport_test.diffapplication/octet-stream; name=securetransport_test.diffDownload
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index e4437d19c3..586711468c 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -123,6 +123,11 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### Keychains
+PWD=$(shell pwd)
+ssl/client.keychain: ssl/client.crt ssl/client.key
+ certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p=
+
.PHONY: sslfiles-clean
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
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index f63c81cfc6..f48a53eb5a 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -106,7 +106,7 @@ sub switch_server_cert
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 "ssl_crl_file=''\n";
close $sslconf;
$node->reload;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index f4daf46e44..57fb593a4e 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -2,7 +2,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 40;
+use Test::More tests => 38;
use ServerSetup;
use File::Copy;
@@ -115,20 +115,20 @@ test_connect_ok("sslrootcert=ssl/root+server_ca.crt sslmode=verify-full");
test_connect_ok("sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca");
test_connect_ok("sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca");
-note "testing sslcrl option with a non-revoked cert";
+#note "testing sslcrl option with a non-revoked cert";
# Invalid CRL filename is the same as no CRL, succeeds
-test_connect_ok(
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid");
+#test_connect_ok(
+# "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid");
# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
-"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl");
+#test_connect_fails(
+#"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl");
# With the correct CRL, succeeds (this cert is not revoked)
-test_connect_ok(
-"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
-);
+#test_connect_ok(
+#"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+#);
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
@@ -197,9 +197,9 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok("sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca");
-test_connect_fails(
-"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
-);
+#test_connect_fails(
+#"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+#);
### Part 2. Server-side tests.
###
@@ -220,10 +220,17 @@ test_connect_ok(
test_connect_fails(
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key");
+# empty keychain
+test_connect_fails("user=ssltestuser keychain=invalid");
+
+# correct client cert in keychain with and without proper label
+test_connect_fails("user=ssltestuser keychain=ssl/client.keychain");
+test_connect_ok("user=ssltestuser sslcert=ssltestuser keychain=ssl/client.keychain");
+
# revoked client cert
-test_connect_fails(
-"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
-);
+#test_connect_fails(
+#"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
+#);
# 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');
On 08/03/2017 01:02 PM, Daniel Gustafsson wrote:
In /messages/by-id/69DB7657-3F9D-4D30-8A4B-E06034251F61@yesql.se I
presented a WIP patch for adding support for the Apple Secure Transport SSL
library on macOS as, an alternative to OpenSSL. That patch got put on the
backburner for a bit, but I’ve now found the time to make enough progress to
warrant a new submission for discussions on this (and hopefully help hacking).
Hooray!
Keychains
=========
The frontend has support for using PEM files for certificates and keys. It can
also look up the key for the certificate in a Keychain, or both certificate and
key in a Keychain. The root certificate is still just read from a PEM file.
Why can't you have the root certificate in the keychain, too? Just not
implemented yet, or is there some fundamental problem with it?
The existence of an sslcert config trumps a keychain, but when a keychain is
used I’m currently using the sslcert parameter (awaiting a discussion on how to
properly do this) for the certificate label required to search the keychain.There is a new frontend parameter called “keychain” with which a path to a
custom Keychain file can be passed. If set, this Keychain will be searched as
well as the default. If not, only the default user Keychain is used. There is
nothing that modifies the Keychain in this patch, it can read identities
(certificates and its key) but not alter them in any way.
OpenSSL also has a mechanism somewhat similar to the keychain, called
"engines". You can e.g. keep the private key corresponding a certificate
on a smart card, and speak to it with an OpenSSL "smart card reader"
engine. If you do that, the 'sslkey' syntax is "<engine name>:<key
name>". Perhaps we should adopt that syntax here as well? For example,
to read the client certificate from the key chain, you would use libpq
options like "keychain=/home/heikki/my_keychain
sslcert=keychain:my_client_cert".
“keychain” is obviously a very Secure Transport specific name, and I personally
think we should avoid that. Any new configuration added here should take
future potential implementation into consideration such that avoid the need for
lots of backend specific knobs. “sslcertstore” comes to mind as an
alternative, but we’d also need parameters to point into the certstore for
finding what we need. Another option would be to do a URL based scheme
perhaps.
I wouldn't actually mind using implementation-specific terms like
"keychain" here. It makes it clear that it's implementation-specific. I
think it would be confusing, to use the same generic option name, like
"sslcertstore", for both a macOS keychain and e.g. the private key store
in Windows. Or GNU Keyring. In the worst case, you might even have
multiple such "key stores" on the same system, so you'd anyways need a
way to specify which one you mean.
Actually, perhaps it should be made even more explicit, and call it
"secure_transport_keychain". That's quite long, though.
Wrt. keychains, is there a system-global or per-user keychain in macOS?
And does this patch use them? If you load a CRL into a global keychain,
does it take effect?
Testing
=======
In order to test this we need to provide an alternative to the openssl calls in
src/test/ssl/Makefile for Secure Transport.
Those openssl commands are only needed to re-generate the test
certificates. The certificates are included in the git repository, so
you only need to re-generate them if you want to modify them or add new
ones. I think it's OK to require the openssl tool for that, it won't be
needed just to run the test suite.
Documentation
=============
I have started fiddling with this a little, but to avoid spending time on the
wrong thing I have done very little awaiting the outcome of discussions here.
I have tried to add lots of comments to the code however, to explain the quirks
of Secure Transport.
I think this patch in general is in very good shape, and the next step
is to write the documentation. In particular, I'd like to see
documentation on how the keychain stuff should work. It'll be easier to
discuss the behavior and the interactions, once it's documented.
In fact, better to write the documentation for that now, and not bother
to change the code, until after we've discussed and are happy with the
documented behavior.
I went into this thinking I would write a README for how to implement a new SSL
library. But in the end, turns out the refactoring that went into our SSL code
earlier made that part almost too easy to warrant that. It’s really quite
straightforward.
That's nice to hear!
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 03 Aug 2017, at 13:06, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
On 08/03/2017 01:02 PM, Daniel Gustafsson wrote:
The frontend has support for using PEM files for certificates and keys. It can
also look up the key for the certificate in a Keychain, or both certificate and
key in a Keychain. The root certificate is still just read from a PEM file.Why can't you have the root certificate in the keychain, too? Just not implemented yet, or is there some fundamental problem with it?
Just not implemented yet, awaiting Keychain discussions.
The existence of an sslcert config trumps a keychain, but when a keychain is
used I’m currently using the sslcert parameter (awaiting a discussion on how to
properly do this) for the certificate label required to search the keychain.
There is a new frontend parameter called “keychain” with which a path to a
custom Keychain file can be passed. If set, this Keychain will be searched as
well as the default. If not, only the default user Keychain is used. There is
nothing that modifies the Keychain in this patch, it can read identities
(certificates and its key) but not alter them in any way.OpenSSL also has a mechanism somewhat similar to the keychain, called "engines". You can e.g. keep the private key corresponding a certificate on a smart card, and speak to it with an OpenSSL "smart card reader" engine. If you do that, the 'sslkey' syntax is "<engine name>:<key name>". Perhaps we should adopt that syntax here as well? For example, to read the client certificate from the key chain, you would use libpq options like "keychain=/home/heikki/my_keychain sslcert=keychain:my_client_cert”.
Thats definitely an option, although it carries a bit redudancy in this case
which can confuse users. With “keychain=/foo/bar.keychain sslcert=my_cert”,
did the user mean a file called my_cert or is it a reference to a cert in the
keychain? Nothing that strict parsing rules, good errormessages and
documentation can’t solve but needs careful thinking.
“keychain” is obviously a very Secure Transport specific name, and I personally
think we should avoid that. Any new configuration added here should take
future potential implementation into consideration such that avoid the need for
lots of backend specific knobs. “sslcertstore” comes to mind as an
alternative, but we’d also need parameters to point into the certstore for
finding what we need. Another option would be to do a URL based scheme
perhaps.I wouldn't actually mind using implementation-specific terms like "keychain" here. It makes it clear that it's implementation-specific. I think it would be confusing, to use the same generic option name, like "sslcertstore", for both a macOS keychain and e.g. the private key store in Windows. Or GNU Keyring. In the worst case, you might even have multiple such "key stores" on the same system, so you'd anyways need a way to specify which one you mean.
Thats a good point.
Actually, perhaps it should be made even more explicit, and call it "secure_transport_keychain". That's quite long, though.
Yeah, secure_transport_ is a pretty long prefix.
Wrt. keychains, is there a system-global or per-user keychain in macOS? And does this patch use them? If you load a CRL into a global keychain, does it take effect?
Each user has a default Keychain , and on top of that you can create as many
Keychains as you want (a Keychain is really just a database file containing
secret things). The frontend use the default one for lookups unless the
keychain parameter is set in which case it uses both.
When evaluating trust, Secure Transport will use the default Keychain even if
not explicitly opened (as in the backend code). It does however only search
for intermediate certificates and not root certs/CRLs. Adding a policy object
for using CRLs should force it to afaik, but we’d need to support additional
keychains (if only to be able to test without polluting the default).
Testing
=======
In order to test this we need to provide an alternative to the openssl calls in
src/test/ssl/Makefile for Secure Transport.Those openssl commands are only needed to re-generate the test certificates. The certificates are included in the git repository, so you only need to re-generate them if you want to modify them or add new ones. I think it's OK to require the openssl tool for that, it won't be needed just to run the test suite.
Ah, of course. We still need support for re-generating Keychains (or at all
generate them in case we don’t want binaries in the repository).
Documentation
=============
I have started fiddling with this a little, but to avoid spending time on the
wrong thing I have done very little awaiting the outcome of discussions here.
I have tried to add lots of comments to the code however, to explain the quirks
of Secure Transport.I think this patch in general is in very good shape, and the next step is to write the documentation. In particular, I'd like to see documentation on how the keychain stuff should work. It'll be easier to discuss the behavior and the interactions, once it's documented.
I’ll try to polish off the patch I have documenting the current behavior.
In fact, better to write the documentation for that now, and not bother to change the code, until after we've discussed and are happy with the documented behavior.
Yeah, discussion -> documentation -> code was my plan.
cheers ./daniel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 3, 2017 at 12:02 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
In /messages/by-id/69DB7657-3F9D-4D30-8A4B-E06034251F61@yesql.se I
presented a WIP patch for adding support for the Apple Secure Transport SSL
library on macOS as, an alternative to OpenSSL. That patch got put on the
backburner for a bit, but I’ve now found the time to make enough progress to
warrant a new submission for discussions on this (and hopefully help hacking).It is a drop-in replacement for the OpenSSL code, and supports all the same
features and options, except for two things: compression is not supported and
the CRL cannot be loaded from a plain PEM file. A Keychain must be used for
that instead.
Is there a set of APIs to be able to get server certificate for the
frontend and the backend, and generate a hash of it? That matters for
channel binding support of SCRAM for tls-server-end-point. There were
no APIs to get the TLS finish message last time I looked at OSX stuff,
which mattered for tls-unique. It would be nice if we could get one.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 03 Aug 2017, at 19:27, Michael Paquier <michael.paquier@gmail.com> wrote:
On Thu, Aug 3, 2017 at 12:02 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
In /messages/by-id/69DB7657-3F9D-4D30-8A4B-E06034251F61@yesql.se I
presented a WIP patch for adding support for the Apple Secure Transport SSL
library on macOS as, an alternative to OpenSSL. That patch got put on the
backburner for a bit, but I’ve now found the time to make enough progress to
warrant a new submission for discussions on this (and hopefully help hacking).It is a drop-in replacement for the OpenSSL code, and supports all the same
features and options, except for two things: compression is not supported and
the CRL cannot be loaded from a plain PEM file. A Keychain must be used for
that instead.Is there a set of APIs to be able to get server certificate for the
frontend and the backend, and generate a hash of it? That matters for
channel binding support of SCRAM for tls-server-end-point.
I believe we can use SSLCopyPeerTrust() for that. Admittedly I haven’t looked
at that yet so need to get my head around channel binding, but it seems to fit
the bill.
There were no APIs to get the TLS finish message last time I looked at OSX
stuff, which mattered for tls-unique. It would be nice if we could get one.
Yeah, AFAICT there is no API for that.
cheers ./daniel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Aug 3, 2017 at 11:26 PM, Daniel Gustafsson <daniel@yesql.se> wrote:
On 03 Aug 2017, at 19:27, Michael Paquier <michael.paquier@gmail.com> wrote:
There were no APIs to get the TLS finish message last time I looked at OSX
stuff, which mattered for tls-unique. It would be nice if we could get one.Yeah, AFAICT there is no API for that.
Perhaps my last words were confusing. I meant that it would be nice to
get at least one type of channel binding working.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 03 Aug 2017, at 13:36, Daniel Gustafsson <daniel@yesql.se> wrote:
On 03 Aug 2017, at 13:06, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
Why can't you have the root certificate in the keychain, too? Just not implemented yet, or is there some fundamental problem with it?
Just not implemented yet, awaiting Keychain discussions.
Supported in this new patchset.
Yeah, discussion -> documentation -> code was my plan.
Attached is an updated set of patches, rebased on top of master, with bug fixes
and additional features missing in the first set. While not complete (yet), in
case anyone is testing this I’d rather send a fresh batch rather than sitting
on them too long while I keep hacking at the docs. While not every part of
this rather large changeset has been touched, this includes all the patches for
completeness sake.
This patchset has certificate revocation as the main thing left. I’ve done
work on supporting CRLs via a Keychain and a CRL policy but that needs more
work (any help is much welcome).
DH parameters are loaded via the GUC, and instead of DER format (which is what
Secure Transport wants) it uses PEM such that it can load the same precomputed
parameter as be-secure-openssl.c and the same files.
Keychains are supported for root certificates in the frontend as well and are
added to the backend for identities as a first test for how it can be
integrated. Referencing an identity in a keychain is done by prefixing the
certificate parameter with keychain: and specifying the common name rather than
filename. The current code doesn’t support setting the passphrase for a
Keychain, it will try with a blank password (since thats handy for testing) and
if that fails it will bring up a GUI element for the passphrase. How to set a
passphrase in the server is open for discussion, a Keychain cannot be created
without a passphrase (it can be blank, but you still need to pass ‘’ to it).
There is a first stab at documentation included, it needs a lot more work to
fully separate generic SSL information and backend specific information but
it’s a WIP.
Additionally, a fix for an issue in the SSL tests is included which only
surface in the Secure Transport tests since it uses connstring parameters with
spaces in them.
0001: Adds support for running the SSL tests against another set of server
binaries. Not changed from previous submission, except rebased on top of master.
0002: Secure Transport support for frontend & backend.
0003: A rough first stab at updating the docs to split SSL documentation into
generic SSL information, and backend specific information. Lot’s to do still
here but it’s a start.
0004: A fix for an issue in the SSL tests which broke Secure Transport testing.
The SELECT ‘$connstr’ clause in the test doesn’t play nice with connstrings
containing sslcert:'keychain:common name’ parameters.
cheers ./daniel
Attachments:
0001-Allow-running-SSL-tests-against-different-binar-v2.patchapplication/octet-stream; name=0001-Allow-running-SSL-tests-against-different-binar-v2.patchDownload
From 9b1672696b78c648a6f5f30e49f6a04e601d9e80 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 17 Aug 2017 14:18:49 +0200
Subject: [PATCH 1/4] Allow running SSL tests against different binaries
In order to be able to test work on SSL frontends, or backends,
against a stock installation, allow a pgpath setting to override
the path to the binaries. The SSLTEST_SERVER_BIN environment var
will be used, if set, for which set of server binaries to run.
SSLTEST_SERVER_BIN=/path/bin/ make -C src/test/ssl/ check
This is for testing during development in order to test a new
frontend against a known backend for example.
---
src/test/perl/PostgresNode.pm | 77 ++++++++++++++++++++++++++++++++----------
src/test/ssl/t/001_ssltests.pl | 2 +-
2 files changed, 60 insertions(+), 19 deletions(-)
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index d9aeb277d9..ef88e7c9d8 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -132,7 +132,7 @@ INIT
=over
-=item PostgresNode::new($class, $name, $pghost, $pgport)
+=item PostgresNode::new($class, $name, $pghost, $pgport, $pgpath)
Create a new PostgresNode instance. Does not initdb or start it.
@@ -143,12 +143,14 @@ of finding port numbers, registering instances for cleanup, etc.
sub new
{
- my ($class, $name, $pghost, $pgport) = @_;
+ my ($class, $name, $pghost, $pgport, $pgpath) = @_;
my $testname = basename($0);
$testname =~ s/\.[^.]+$//;
+ $pgpath = '' unless defined $pgpath;
my $self = {
_port => $pgport,
_host => $pghost,
+ _pgpath => $pgpath,
_basedir => TestLib::tempdir("data_" . $name),
_name => $name,
_logfile => "$TestLib::log_path/${testname}_${name}.log" };
@@ -223,6 +225,21 @@ sub name
=pod
+=item $node->pgpath()
+
+Path to the PostgreSQL binaries to run, if empty then the installation in PATH
+is assumed.
+
+=cut
+
+sub pgpath
+{
+ my ($self) = @_;
+ return $self->{_pgpath};
+}
+
+=pod
+
=item $node->logfile()
Path to the PostgreSQL log file for this instance.
@@ -323,6 +340,7 @@ sub info
my $_info = '';
open my $fh, '>', \$_info or die;
print $fh "Name: " . $self->name . "\n";
+ print $fh "Binary directory: " . $self->pgpath . "\n";
print $fh "Data directory: " . $self->data_dir . "\n";
print $fh "Backup directory: " . $self->backup_dir . "\n";
print $fh "Archive directory: " . $self->archive_dir . "\n";
@@ -398,6 +416,7 @@ sub init
my $port = $self->port;
my $pgdata = $self->data_dir;
my $host = $self->host;
+ my $pgpath = $self->pgpath;
$params{allows_streaming} = 0 unless defined $params{allows_streaming};
$params{has_archiving} = 0 unless defined $params{has_archiving};
@@ -405,7 +424,7 @@ sub init
mkdir $self->backup_dir;
mkdir $self->archive_dir;
- TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+ TestLib::system_or_bail($pgpath . 'initdb', '-D', $pgdata, '-A', 'trust', '-N',
@{ $params{extra} });
TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata);
@@ -499,9 +518,10 @@ sub backup
my $backup_path = $self->backup_dir . '/' . $backup_name;
my $port = $self->port;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "# Taking pg_basebackup $backup_name from node \"$name\"\n";
- TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-p', $port,
+ TestLib::system_or_bail($pgpath . 'pg_basebackup', '-D', $backup_path, '-p', $port,
'--no-sync');
print "# Backup finished\n";
}
@@ -657,8 +677,9 @@ sub start
my $pgdata = $self->data_dir;
my $name = $self->name;
BAIL_OUT("node \"$name\" is already running") if defined $self->{_pid};
+ my $pgpath = $self->pgpath;
print("### Starting node \"$name\"\n");
- my $ret = TestLib::system_log('pg_ctl', '-D', $self->data_dir, '-l',
+ my $ret = TestLib::system_log($pgpath . 'pg_ctl', '-D', $self->data_dir, '-l',
$self->logfile, 'start');
if ($ret != 0)
@@ -689,10 +710,11 @@ sub stop
my $port = $self->port;
my $pgdata = $self->data_dir;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
$mode = 'fast' unless defined $mode;
return unless defined $self->{_pid};
print "### Stopping node \"$name\" using mode $mode\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-m', $mode, 'stop');
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-m', $mode, 'stop');
$self->_update_pid(0);
}
@@ -710,8 +732,10 @@ sub reload
my $port = $self->port;
my $pgdata = $self->data_dir;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Reloading node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, 'reload');
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, 'reload');
+
}
=pod
@@ -729,8 +753,9 @@ sub restart
my $pgdata = $self->data_dir;
my $logfile = $self->logfile;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Restarting node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-l', $logfile,
'restart');
$self->_update_pid(1);
}
@@ -750,8 +775,9 @@ sub promote
my $pgdata = $self->data_dir;
my $logfile = $self->logfile;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Promoting node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-l', $logfile,
'promote');
}
@@ -855,12 +881,15 @@ sub _update_pid
=pod
-=item PostgresNode->get_new_node(node_name)
+=item PostgresNode->get_new_node(node_name, pgpath)
Build a new object of class C<PostgresNode> (or of a subclass, if you have
one), assigning a free port number. Remembers the node, to prevent its port
number from being reused for another node, and to ensure that it gets
-shut down when the test script exits.
+shut down when the test script exits. If the pgpath parameter is defined then
+the path is tested for containing a PostgreSQL installation with the correct
+permissions for execution.
+
You should generally use this instead of C<PostgresNode::new(...)>.
@@ -872,8 +901,8 @@ which can only create objects of class C<PostgresNode>.
sub get_new_node
{
my $class = 'PostgresNode';
- $class = shift if 1 < scalar @_;
- my $name = shift;
+ $class = shift if 2 < scalar @_;
+ my ($name, $pgpath) = @_;
my $found = 0;
my $port = $last_port_assigned;
@@ -916,8 +945,19 @@ sub get_new_node
print "# Found free port $port\n";
+ # If a separate path to the binaries was specified then we do rudimentary
+ # checks for a sane installation. If an executable copy of initdb is there
+ # then let's go ahead.
+ if (defined $pgpath)
+ {
+ $pgpath =~ s!/*$!/!;
+ die "initdb missing in $pgpath" unless -x $pgpath . 'initdb';
+
+ print "# Found PostgreSQL installation in $pgpath\n";
+ }
+
# Lock port number found by creating a new node
- my $node = $class->new($name, $test_pghost, $port);
+ my $node = $class->new($name, $test_pghost, $port, $pgpath);
# Add node to list of nodes
push(@all_nodes, $node);
@@ -1087,8 +1127,9 @@ sub psql
my $stderr = $params{stderr};
my $timeout = undef;
my $timeout_exception = 'psql timed out';
+ my $pgpath = $self->pgpath;
my @psql_params =
- ('psql', '-XAtq', '-d', $self->connstr($dbname), '-f', '-');
+ ($pgpath . 'psql', '-XAtq', '-d', $self->connstr($dbname), '-f', '-');
# If the caller wants an array and hasn't passed stdout/stderr
# references, allocate temporary ones to capture them so we
@@ -1230,11 +1271,11 @@ Returns 1 if successful, 0 if timed out.
sub poll_query_until
{
- my ($self, $dbname, $query, $expected) = @_;
+ my ($self, $dbname, $query, $expected, $pgpath) = @_;
$expected = 't' unless defined($expected); # default value
- my $cmd = [ 'psql', '-XAt', '-c', $query, '-d', $self->connstr($dbname) ];
+ my $cmd = [ $pgpath . 'psql', '-XAt', '-c', $query, '-d', $self->connstr($dbname) ];
my ($stdout, $stderr);
my $max_attempts = 180 * 10;
my $attempts = 0;
@@ -1447,7 +1488,7 @@ sub wait_for_catchup
. $self->name . "\n";
my $query =
qq[SELECT '$target_lsn' <= ${mode}_lsn FROM pg_catalog.pg_stat_replication WHERE application_name = '$standby_name';];
- $self->poll_query_until('postgres', $query)
+ $self->poll_query_until('postgres', $query, $self->pgpath)
or die "timed out waiting for catchup, current location is "
. ($self->safe_psql('postgres', $query) || '(unknown)');
print "done\n";
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 32df273929..f4daf46e44 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -59,7 +59,7 @@ chmod 0600, "ssl/client_tmp.key";
#### Part 0. Set up the server.
note "setting up data directory";
-my $node = get_new_node('master');
+my $node = get_new_node('master', $ENV{SSLTEST_SERVER_BIN});
$node->init;
# PGHOST is enforced here to set up the node, subsequent connections
--
2.14.1.145.gb3622a4ee
0002-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v2.patchapplication/octet-stream; name=0002-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v2.patchDownload
From 3ff493649db46e9fcf14fb2551c7df7a3f6c4726 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 17 Aug 2017 14:26:02 +0200
Subject: [PATCH 2/4] WIP: Add support for Apple Secure Transport SSL library
This adds frontend and backend support for the Secure Transport SSL
library on macOS. The patch is not in a final state, the remaining
issues being mainly certificate revocation and full configuration
compatability with the OpenSSL backend. See email to -hackers for
full details.
---
configure | 57 +
configure.in | 14 +
src/Makefile.global.in | 1 +
src/backend/Makefile | 4 +
src/backend/libpq/Makefile | 5 +
src/backend/libpq/be-secure-securetransport.c | 1340 +++++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 25 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/common/securetransport.h | 510 ++++++++
src/include/common/sha2.h | 4 +-
src/include/libpq/libpq-be.h | 17 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 10 +-
src/interfaces/libpq/Makefile | 6 +-
src/interfaces/libpq/fe-connect.c | 6 +
src/interfaces/libpq/fe-secure-securetransport.c | 1488 ++++++++++++++++++++++
src/interfaces/libpq/libpq-int.h | 22 +-
19 files changed, 3508 insertions(+), 11 deletions(-)
create mode 100644 src/backend/libpq/be-secure-securetransport.c
create mode 100644 src/include/common/securetransport.h
create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c
diff --git a/configure b/configure
index a2f9a256b4..ce675cbe80 100755
--- a/configure
+++ b/configure
@@ -709,6 +709,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
krb_srvtab
with_python
@@ -838,6 +839,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1534,6 +1536,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -6051,6 +6054,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
#
# SELinux
#
@@ -11015,6 +11053,25 @@ else
fi
+fi
+
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.in b/configure.in
index e94fba5235..49d8be5ce9 100644
--- a/configure.in
+++ b/configure.in
@@ -734,6 +734,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
#
# SELinux
#
@@ -1255,6 +1264,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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index e8b3a519cb..4e05c5af79 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_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_libxml = @with_libxml@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index bce9d2c3eb..9c4b248185 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -49,6 +49,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LDFLAGS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-schemapg postgres $(POSTGRES_IMP)
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 7fa2b02743..3a835235b9 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000000..4e6ce381da
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1340 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - Load DH keys from file
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future).
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interested to investigate.
+ *
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "common/base64.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#define bool pg_bool
+#include <Security/cssmerr.h>
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef bool
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_bool bool
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err. Since we
+ * may have encountered an error due to memory pressure, we don't want to rely
+ * on dynamically allocating memory for this error message.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+/*
+ * SecIdentityCreate is not an exported Secure Transport API. There is however
+ * no exported Secure Transport function that can create an identity from a
+ * SecCertificateRef and a SecKeyRef without having either of them in a
+ * Keychain. This function is commonly used in open source projects (such as
+ * ffmpeg and mono f.e), but finding an alternative is a TODO.
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static void load_key(char *name, CFArrayRef *out);
+static OSStatus load_keychain(char *name, CFArrayRef *keychains);
+static OSStatus load_certificate_file(char *name, CFArrayRef *cert_array);
+static OSStatus load_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+
+static int load_dh_file(char *filename, char **buf);
+static void load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl);
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+
+/* ------------------------------------------------------------ */
+/* Hardcoded DH parameters */
+/* ------------------------------------------------------------ */
+
+/*
+ * Pre-computed DH param to use in case the user didn't specify a dhparam file
+ * with a custom param in it. This is to avoid the very lengthy calculation
+ * process that Secure Transport will otherwise do per connection.
+ *
+ * TODO: This is the same param as in the OpenSSL backend, and duplicating it
+ * is clearly not a terribly good thing. This should probably be moved to a
+ * common header file, or the creation of it should be re-thought in case we
+ * want to keep separate precomputed params per SSL backend.
+ */
+static const char file_dh2048[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV\n\
+89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50\n\
+T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb\n\
+zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX\n\
+Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT\n\
+CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
+-----END DH PARAMETERS-----\n";
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize global Secure Transport structures (if any)
+ *
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ *
+ * Another reason to defer this until when in the backend is that Keychains
+ * are SQLite3 backed, and sqlite does not allow access across a fork. See
+ * https://sqlite.org/faq.html#q6 for more information.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * bt_tls_open_server
+ * Attempt to negotiate a secure connection
+ *
+ * Unlike the OpenSSL backend, this function is responsible for loading keys
+ * and certificates for use in the connection. See the comment on be_tls_init
+ * for further reasoning around this.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFArrayRef keychains;
+ CFMutableArrayRef chain;
+ char *dh_buf;
+ int dh_len;
+
+ Assert(!port->ssl);
+
+ if (ssl_keychain_file[0])
+ load_keychain(ssl_keychain_file, &keychains);
+
+ /*
+ * If the ssl_cert_file is prefixed with a keychain reference, we will try
+ * to load a complete identity from the Keychain.
+ */
+ if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains);
+ else
+ {
+ status = load_certificate_file(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": \"%s\"",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ load_key(ssl_key_file, &keys);
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ }
+
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate_file(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": \"%s\"",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API
+ */
+ if (ssl_crl_file[0])
+ ereport(FATAL,
+ (errmsg("CRL files not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ /*
+ * We use kTryAuthenticate here since we don't know which sslmode the
+ * client is using. If we were to use kAlwaysAuthenticate then sslmode
+ * require won't work as intended.
+ */
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * In case the user hasn't configured a DH parameters file, we load a pre-
+ * computed DH parameter to avoid having Secure Transport computing one for
+ * us (which is done by default unless one is set).
+ */
+ dh_buf = NULL;
+ if (ssl_dh_params_file[0])
+ dh_len = load_dh_file(ssl_dh_params_file, &dh_buf);
+ if (!dh_buf || dh_len == 0)
+ {
+ dh_buf = pstrdup(file_dh2048);
+ dh_len = sizeof(dh_buf);
+ }
+
+ load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl);
+
+ /*
+ * Set Tlsv1.2 as the minimum protocol version we allow for the connection
+ */
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ if (status == errSSLWouldBlock)
+ continue;
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+
+ CFStringRef usercert_cn;
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+ port->peer_cn = pstrdup(CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8));
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ *
+ * Loads a private key from the specified filename. Unless the key loads this
+ * will not return but will error out.
+ */
+static void
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+
+ ret = stat(name, &stat_buf);
+ if (ret != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to stat",
+ name)));
+
+ if (!S_ISREG(stat_buf.st_mode))
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": not a regular file",
+ name)));
+
+ /*
+ * Require no public access to the key file. If the file is owned by us,
+ * require mode 0600 or less. If owned by root, require 0640 or less to
+ * allow read access through our gid, or a supplementary gid that allows to
+ * read system-wide certificates.
+ */
+ if ((stat_buf.st_uid == geteuid() && stat_buf.st_mode & (S_IRWXG | S_IRWXO)) ||
+ (stat_buf.st_uid == 0 && stat_buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" has group or world access",
+ name),
+ errdetail("File must have permissions u=rw (0600) or less "
+ "if owned by the database user, or permissions "
+ "u=rw,g=r (0640) or less if owned by root.")));
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ SecItemImportExportKeyParameters params;
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": \"%s\"",
+ name, pg_SSLerrmessage(status))));
+}
+
+/*
+ * load_keychain
+ * Open the specified, and default, Keychain
+ *
+ * Operations on keychains other than the default take the keychain references
+ * in an array. We need to copy a reference to the default keychain into the
+ * array to include it in the search.
+ *
+ * For server applications, we don't want modal dialog boxes opened for
+ * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE)
+ * will turn off all GUI interaction with the user, which may seem like what we
+ * want server side. This however has the side effect to turn off all GUI
+ * elements for all applications until some application calls
+ * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus
+ * remove wanted GUI interaction from another app, or another app might
+ * introduce it for
+ * us.
+ */
+static OSStatus
+load_keychain(char *name, CFArrayRef *keychains)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ SecKeychainRef kc[2];
+
+ if (stat(name, &stat_buf) != 0)
+ return errSecInvalidValue;
+
+ status = SecKeychainOpen(name, &kc[0]);
+ if (status == noErr)
+ {
+ SecKeychainUnlock(kc[0], 0, "", TRUE);
+ /*
+ * We only need to open, and add, the default Keychain in case have a
+ * user keychain opened, else we will pass NULL to any keychain search
+ * which will use the default keychain by.. default.
+ */
+ SecKeychainCopyDefault(&kc[1]);
+ *keychains = CFArrayCreate(NULL, (const void **) kc, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ else
+ elog(LOG, "XXX: could not load keychain \"%s\" : %d", name, status);
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+load_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+/*
+ * load_certificate_file
+ * Extracts a certificate from a PEM file on the filesystem
+ *
+ * TODO: figure out better returncodes
+ */
+static OSStatus
+load_certificate_file(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ /*
+ * If the configured ssl_cert_file filename is set to a non-existing
+ * file, assume it's referencing a Keychain label and attempt to load
+ * the certificate from the Keychain instead.
+ */
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ /*
+ * TODO: Do we want to search keychains for certificates serverside
+ * like we do clientside, or do we want to stick to a single way to
+ * configure the server? Since CRL files aren't supported outside
+ * keychains I guess we need to, but worth a discussion.
+ */
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ return errSecInternalError;
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ return errSecInternalError;
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL, cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ return errSecInternalError;
+}
+
+/*
+ * XXX: Make this file loading and slurping only
+ */
+static int
+load_dh_file(char *filename, char **buf)
+{
+ FILE *dh;
+ struct stat stat_buf;
+ int ret;
+
+ /*
+ * Open the DH file and slurp the contents. If the file doesn't exist it's
+ * not an error, if it can't be opened it is however an error.
+ */
+ ret = stat(filename, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ return 0;
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((dh = AllocateFile(filename, "r")) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+
+ *buf = palloc(stat_buf.st_size);
+ ret = fread(*buf, 1, stat_buf.st_size, dh);
+ FreeFile(dh);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read DH parameters file \"%s\": %m",
+ filename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("DH parameters file \"%s\" is not a regular file",
+ filename)));
+
+ return ret;
+}
+
+/*
+ * load_dh_params
+ * Load the specified DH params for the connection
+ *
+ * Secure Transport requires the DH params to be in DER format, but to be
+ * compatible with the OpenSSL code we also support PEM and convert to DER
+ * before loading. Conversion does rudimentary PEM parsing, if we miss the
+ * data being correct, the Secure Transport API will give an error anyways so
+ * we're just checking basic integrity.
+ *
+ * This function may scribble on the dh parameter so if that's required so stay
+ * intact in the caller, a copy should be sent.
+ */
+#define DH_HEADER "-----BEGIN DH PARAMETERS-----"
+#define DH_FOOTER "-----END DH PARAMETERS-----"
+static void
+load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl)
+{
+ OSStatus status;
+ char *der;
+ int der_len;
+
+ Assert(dh);
+
+ /*
+ * Convert PEM to DER
+ */
+ if (is_pem)
+ {
+ char *head;
+ char *tail;
+ int pem_len = 0;
+
+ if (strstr(dh, DH_HEADER) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, header missing")));
+
+ dh += strlen(DH_HEADER);
+ tail = strstr(dh, DH_FOOTER);
+ if (!tail)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, footer missing")));
+ *tail = '\0';
+
+ /* In order to PEM convert it we need to remove all newlines */
+ head = dh;
+ tail = dh;
+ while (*head != '\0')
+ {
+ if (*head != '\n')
+ {
+ *tail++ = *head++;
+ pem_len++;
+ }
+ else
+ head++;
+ }
+ *tail = '\0';
+
+ der = palloc(pg_b64_dec_len(strlen(dh)) + 1);
+ der_len = pg_b64_decode(dh, strlen(dh), der);
+ der[der_len] = '\0';
+ }
+ else
+ {
+ der = dh;
+ der_len = len;
+ }
+
+ status = SSLSetDiffieHellmanParams(ssl, der, der_len);
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unable to load DH parameters: %s",
+ pg_SSLerrmessage(status))));
+}
+
+/*
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+void
+be_tls_get_version(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (ptr == NULL || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ strlcpy(ptr, "TLSv1.1", len);
+ break;
+ case kTLSProtocol12:
+ strlcpy(ptr, "TLSv1.2", len);
+ break;
+ default:
+ strlcpy(ptr, "unknown", len);
+ break;
+ }
+ }
+}
+
+/*
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ *
+ * Returns NULL in case we weren't able to either get the negotiated cipher, or
+ * translate it into a human readable string.
+ */
+void
+be_tls_get_cipher(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+ const char *cipher_name;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return;
+
+ cipher_name = pg_SSLciphername(cipher);
+ if (cipher_name != NULL)
+ strlcpy(ptr, cipher_name, len);
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ /*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there still is no translation for it in 10.11 El Capitan, so we
+ * maintain our own
+ */
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 53fefd1b29..aa6f0ec63e 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -45,6 +45,9 @@ char *ssl_key_file;
char *ssl_ca_file;
char *ssl_crl_file;
char *ssl_dh_params_file;
+#ifdef USE_SECURETRANSPORT
+char *ssl_keychain_file;
+#endif
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 246fea8693..727638219c 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -501,6 +501,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -3297,6 +3298,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
{
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
@@ -3544,6 +3557,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ gettext_noop("Location of the Keychain file."),
+ NULL
+ },
+ &ssl_keychain_file,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index df5d2f3f22..ceb4790c5d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,6 +85,7 @@
#ssl_key_file = 'server.key'
#ssl_ca_file = ''
#ssl_crl_file = ''
+#ssl_keychain_file = ''
#password_encryption = md5 # md5, scram-sha-256, or plain
#db_user_namespace = off
#row_security = on
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000000..997c504e41
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,510 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport_common.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index a31b3979d8..2851d3eea0 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7bde744d51..0f9ff6b88c 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,19 +191,22 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+ void *ssl;
+ int ssl_buffered;
#endif
} Port;
#ifdef USE_SSL
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
extern int be_tls_init(bool isServerStart);
extern void be_tls_destroy(void);
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index fd2dd5853c..79572b93f7 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -80,6 +80,9 @@ extern char *ssl_key_file;
extern char *ssl_ca_file;
extern char *ssl_crl_file;
extern char *ssl_dh_params_file;
+#ifdef USE_SECURETRANSPORT
+extern char *ssl_keychain_file;
+#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 dcb7a1a320..82fa467585 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -841,6 +841,9 @@
/* Define to use OpenSSL for random number generation */
#undef USE_OPENSSL_RANDOM
+/* Define to build with Apple Secure Transport support. (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index f3b35297d1..48cc363803 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -166,11 +166,15 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 87f22d242f..ad615420d6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,11 @@ else
OBJS += sha2.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -112,7 +117,6 @@ ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
-
distprep: libpq-dist.rc
libpq.rc libpq-dist.rc: libpq.rc.in
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index d0e97ecdd4..6a3c67aae0 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -323,6 +323,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000000..79a054a039
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1488 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readiblitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *common_name,
+ SecCertificateRef *certificate,
+ CFArrayRef keychains,
+ char *hostname);
+static OSStatus import_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+static OSStatus import_pem(const char *path, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * Exported function to allow application to tell us it's already initialized
+ * Secure Transport and/or libcrypto.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ return;
+}
+
+/*
+ * Begin or continue negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin(conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection(conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs(conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we dont have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ SSLSetPeerDomainName(conn->ssl, conn->pghost, strlen(conn->pghost));
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption(conn->ssl, kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake(conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust(conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ /*
+ * TODO: return a better error code than SSLInternalError
+ */
+ if (!trusted)
+ return errSecInternalError;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize(conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead(conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite(conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite(conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+/*
+ * Initialize SSL system, in particular creating the SSL_context object
+ * that will be shared by all SSL-using connections in this process.
+ *
+ * In threadsafe mode, this includes setting up libcrypto callback functions
+ * to do thread locking.
+ *
+ * If the caller has told us (through PQinitOpenSSL) that he's taking care
+ * of libcrypto, we expect that callbacks are already set, and won't try to
+ * override it.
+ *
+ * The conn parameter is only used to be able to pass back an error
+ * message - no connection-local setup is made here.
+ *
+ * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose(conn->ssl);
+ CFRelease(conn->ssl);
+
+ /* TODO: Release any certificates loaded */
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+import_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_certificate_keychain(const char *common_name, SecCertificateRef *certificate,
+ CFArrayRef keychains, char *hostname)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFStringRef host = NULL;
+ CFArrayRef temp;
+ SecPolicyRef ssl_policy;
+ int i;
+
+ /*
+ * Make sure the user didn't just specify the keychain prefix as the
+ * certificate config. The passed certificate will have the keychain
+ * prefix stripped so in that case the string is expected to be empty.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ /*
+ * If we didn't get a set of Keychains passed, skip adding it to the
+ * dictionary thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * Specifying a hostname requires it to match the hostname in the leaf
+ * certificate.
+ */
+ if (hostname)
+ host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8);
+ ssl_policy = SecPolicyCreateSSL(true, host);
+ CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label has been reported to not work (revisions of 10.12), we request
+ * all and find the one we want. Copy all the results to a temp array and
+ * scan it for the certificate we are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i);
+
+ if (search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ *certificate = (SecCertificateRef) CFRetain(search_cert);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(ssl_policy);
+ CFRelease(query);
+ CFRelease(cert);
+ if (host)
+ CFRelease(host);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, char *passphrase, CFArrayRef *certificate)
+{
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+ struct stat buf;
+
+ if (!path || strlen(path) == 0)
+ return errSecInvalidValue;
+
+ if (stat(path, &buf) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return -1;
+
+ return errSecInvalidValue;
+ }
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInvalidValue;
+
+ certdata = malloc(buf.st_size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, buf.st_size, fp) != buf.st_size)
+ {
+ fclose(fp);
+ free(certdata);
+ return errSSLBadCert;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, buf.st_size);
+ free(certdata);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecAllocate;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ return SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ bool have_homedir;
+ bool cert_from_file = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecCertificateRef root_ref[1];
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+ SecKeychainRef kcref[2];
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching. If no additional keychain is specified we don't need to open
+ * the default keychain and pass to searches since Secure Transport will
+ * use the default when passing NULL instead of an array of Keychain refs.
+ */
+ if (conn->keychain)
+ {
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr && kcref[0] != NULL)
+ {
+ SecKeychainStatus kcstatus;
+ status = SecKeychainGetStatus(kcref[0], &kcstatus);
+ if (status == noErr)
+ {
+ switch (kcstatus)
+ {
+ /*
+ * If the Keychain is already unlocked, readable or
+ * writeable, we don't need to do more. If not, try to
+ * unlock it.
+ */
+ case kSecUnlockStateStatus:
+ case kSecReadPermStatus:
+ case kSecWritePermStatus:
+ break;
+ default:
+ /* XXX: fix passphrase param */
+ SecKeychainUnlock(kcref[0], 0, "", TRUE);
+ break;
+ }
+ }
+
+ /*
+ * We only need to open, and add, the default Keychain in case
+ * have a user keychain opened, else we will pass NULL to any
+ * keychain search which will use the default keychain by..
+ * default.
+ */
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the files
+ * could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Try to load the root cert from either a user defined keychain or the
+ * default Keychain in case none is specified
+ */
+ if (conn->sslrootcert &&
+ pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ root_ref[0] = NULL;
+ strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL);
+
+ if (root_ref[0])
+ conn->st_rootcert = (void *) CFArrayCreate(NULL,
+ (const void **) root_ref,
+ 1, &kCFTypeArrayCallBacks);
+ }
+
+ if (!conn->st_rootcert)
+ {
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+ }
+
+ /*
+ * If the sslcert config is prefixed with a keychain identifier, the cert
+ * must be located in either the default or the specified keychain.
+ */
+ if (conn->sslcert &&
+ pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_identity_keychain(fnbuf, &identity, keychains);
+
+ if (identity)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ /*
+ * No prefix on the sslcert config, the certificate must thus reside in a
+ * file on disk.
+ */
+ else
+ {
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, cert_array);
+ if (status != noErr)
+ {
+ if (status == -1)
+ return noErr;
+
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ cert_from_file = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in
+ * order to create the identity.
+ */
+
+ /*
+ * The sslkey config is prefixed with keychain: indicating that the
+ * key should be loaded from Keychain instead of the filesystem.
+ * Search for the private key matching the cert_ref in the opened
+ * Keychains. If found, we get the identity returned.
+ */
+ if (conn->sslkey &&
+ pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref,
+ &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the key to be
+ * loaded from that file.
+ */
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+ }
+ }
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ if (key_ref)
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ }
+
+ if (!identity)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create identity from certificate/key\n"));
+ return errSecInvalidValue;
+ }
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (cert_from_file)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate(conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+int
+PQsslInUse(PGconn *conn)
+{
+ if (!conn)
+ return 0;
+ return conn->ssl_in_use;
+}
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher(conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion(conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState(conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 42913604e3..0cf96c1f04 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -80,6 +80,14 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_SECURETRANSPORT
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#undef Size
+#undef uint64
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -467,8 +475,18 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+
+ SSLContextRef ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
--
2.14.1.145.gb3622a4ee
0003-WIP-A-first-stab-at-documentation-for-Secure-Tran-v2.patchapplication/octet-stream; name=0003-WIP-A-first-stab-at-documentation-for-Secure-Tran-v2.patchDownload
From ef9e113d60eae797327ce5a9d7b1553afc743720 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 17 Aug 2017 14:43:53 +0200
Subject: [PATCH 3/4] WIP: A first stab at documentation for Secure Transport
This is a rough first stab at amending the documentation to support
having multiple SSL backends, and adding Secure Transport specific
information.
---
doc/src/sgml/config.sgml | 20 ++++++++
doc/src/sgml/libpq.sgml | 52 ++++++++++++++-----
doc/src/sgml/runtime.sgml | 127 +++++++++++++++++++++++++++++++++-------------
3 files changed, 152 insertions(+), 47 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 2b6255ed95..22942d5680 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1046,6 +1046,26 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-ssl-keychain-file" xreflabel="ssl_keychain_file">
+ <term><varname>ssl_keychain_file</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>ssl_keychain_file</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the name of the file containing the a Keychain. Relative
+ paths are relative to the current users system Keychain folder
+ (typically <filename>Library/Keychains</>).
+ Absolute paths can be used to use a Keychain located elsewhere.
+ This parameter can only be set in the <filename>postgresql.conf</>
+ file or on the server command line.
+ This parameter is only available when the server has been configured
+ with support for Secure Transport.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-ssl-ciphers" xreflabel="ssl_ciphers">
<term><varname>ssl_ciphers</varname> (<type>string</type>)
<indexterm>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index ad5e9b95b4..0f204be205 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1338,13 +1338,14 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>sslcompression</literal></term>
<listitem>
<para>
+ This requires <productname>OpenSSL</> 1.0.0 or later, when using
+ other SSL libraries this setting will be ignored.
If set to 1 (default), data sent over SSL connections will be
compressed.
- If set to 0, compression will be disabled (this requires
- <productname>OpenSSL</> 1.0.0 or later).
+ If set to 0, compression will be disabled.
This parameter is ignored if a connection without SSL is made,
- or if the version of <productname>OpenSSL</> used does not support
- it.
+ if the version of <productname>OpenSSL</> used does not support
+ it or an SSL library other than <productname>OpenSSL</> is used.
</para>
<para>
Compression uses CPU time, but can improve throughput if
@@ -1405,7 +1406,11 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
revocation list (CRL). Certificates listed in this file, if it
exists, will be rejected while attempting to authenticate the
server's certificate. The default is
- <filename>~/.postgresql/root.crl</>.
+ <filename>~/.postgresql/root.crl</>. This setting is invalid
+ when using <productname>Secure Transport</>, which require the
+ CRL to be imported into the default Keychain. In order to not
+ cause unintentional silent ignore, the server will abort if
+ this parameter is configured.
</para>
</listitem>
</varlistentry>
@@ -2041,8 +2046,8 @@ const char *PQsslAttribute(const PGconn *conn, const char *attribute_name);
<term><literal>library</literal></term>
<listitem>
<para>
- Name of the SSL implementation in use. (Currently, only
- <literal>"OpenSSL"</literal> is implemented)
+ Name of the SSL implementation in use. Possible values are
+ <literal>"OpenSSL"</literal> and <literal>"SecureTransport"</literal>.
</para>
</listitem>
</varlistentry>
@@ -7524,9 +7529,14 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
<productname>PostgreSQL</> has native support for using <acronym>SSL</>
connections to encrypt client/server communications for increased
security. See <xref linkend="ssl-tcp"> for details about the server-side
- <acronym>SSL</> functionality.
+ <acronym>SSL</> functionality. Multiple SSL libraries are supported, one
+ of which is enabled at build time.
+ Table <xref linkend="supported-ssl-libraries"> lists the supported SSL
+ libraries.
</para>
+ <sect2>
+ <title>Using OpenSSL</title>
<para>
<application>libpq</application> reads the system-wide
<productname>OpenSSL</productname> configuration file. By default, this
@@ -7536,6 +7546,20 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
<envar>OPENSSL_CONF</envar> to the name of the desired configuration
file.
</para>
+ </sect2>
+
+ <sect2>
+ <title>Using Secure Transport</title>
+ <para>
+ libpq will use the default <productname>Keychain</productname>, as well
+ as a custom one if configured, when using
+ <productname>Secure Transport</productname>. Certificates and keys can be
+ loaded from either PEM files, or from the Keychain. To load an identity
+ from a Keychain, the <literal>ssl_cert</> parameter is prefixed with
+ <literal>keychain:</> followed by the certificate common name rather than
+ the filename.
+ </para>
+ </sect2>
<sect2 id="libq-ssl-certificates">
<title>Client Verification of Server Certificates</title>
@@ -7582,10 +7606,13 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</para>
<para>
- Certificate Revocation List (CRL) entries are also checked
- if the file <filename>~/.postgresql/root.crl</filename> exists
+ Certificate Revocation List (<acronym>CRL</>) entries are also checked. When libpq
+ is built using <productname>OpenSSL</>
+ the file <filename>~/.postgresql/root.crl</filename> is used
(<filename>%APPDATA%\postgresql\root.crl</filename> on Microsoft
- Windows).
+ Windows). When using <productname>Secure Transport</> the CRL must be
+ imported in the Keychain holding the server certificate to perform certificate
+ revocation checks automatically.
</para>
<para>
@@ -7863,7 +7890,8 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
<row>
<entry><filename>~/.postgresql/root.crl</></entry>
<entry>certificates revoked by certificate authorities</entry>
- <entry>server certificate must not be on this list</entry>
+ <entry>any certificate in this list will be rejected; server certificate
+ must not be on this list</entry>
</row>
</tbody>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 6d57525515..a23715b8c6 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2169,12 +2169,42 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<para>
<productname>PostgreSQL</> has native support for using
<acronym>SSL</> connections to encrypt client/server communications
- for increased security. This requires that
- <productname>OpenSSL</productname> is installed on both client and
+ for increased security. This requires that a supported SSL library
+ is installed on both client and
server systems and that support in <productname>PostgreSQL</> is
- enabled at build time (see <xref linkend="installation">).
+ enabled at build time (see <xref linkend="installation">). Clients are
+ not required to use the same SSL library as the server, all supported
+ libraries are compatible.
+ Table <xref linkend="supported-ssl-libraries"> lists the supported SSL
+ libraries.
</para>
+ <table id="supported-ssl-libraries">
+ <title>Supported SSL libraries</title>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>Library</entry>
+ <entry>Platform</entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry><productname>OpenSSL</productname></entry>
+ <entry>All platforms supported by <productname>PostgreSQL</productname></entry>
+ </row>
+
+ <row>
+ <entry><productname>Secure Transport</productname></entry>
+ <entry><productname>Apple macOS 10.11 El Capitan+</productname></entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
<para>
With <acronym>SSL</> support compiled in, the
<productname>PostgreSQL</> server can be started with
@@ -2189,41 +2219,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- <productname>PostgreSQL</productname> reads the system-wide
- <productname>OpenSSL</productname> configuration file. By default, this
- file is named <filename>openssl.cnf</filename> and is located in the
- directory reported by <literal>openssl version -d</>.
- This default can be overridden by setting environment variable
- <envar>OPENSSL_CONF</envar> to the name of the desired configuration file.
- </para>
-
- <para>
- <productname>OpenSSL</productname> supports a wide range of ciphers
- and authentication algorithms, of varying strength. While a list of
- ciphers can be specified in the <productname>OpenSSL</productname>
- configuration file, you can specify ciphers specifically for use by
- the database server by modifying <xref linkend="guc-ssl-ciphers"> in
- <filename>postgresql.conf</>.
- </para>
-
- <note>
- <para>
- It is possible to have authentication without encryption overhead by
- using <literal>NULL-SHA</> or <literal>NULL-MD5</> ciphers. However,
- a man-in-the-middle could read and pass communications between client
- and server. Also, encryption overhead is minimal compared to the
- overhead of authentication. For these reasons NULL ciphers are not
- recommended.
- </para>
- </note>
-
- <para>
- To start in <acronym>SSL</> mode, files containing the server certificate
- and private key must exist. By default, these files are expected to be
+ To start in <acronym>SSL</> mode, a server certificate
+ and private key must exist. By default, these are expected to be in files
named <filename>server.crt</> and <filename>server.key</>, respectively, in
the server's data directory, but other names and locations can be specified
using the configuration parameters <xref linkend="guc-ssl-cert-file">
- and <xref linkend="guc-ssl-key-file">.
+ and <xref linkend="guc-ssl-key-file">. See the notes on the library used
+ for specifics on how certificates and keys can be specified.
</para>
<para>
@@ -2258,6 +2260,55 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<filename>root.crt</filename> files.
</para>
+ <note>
+ <para>
+ It is possible to have authentication without encryption overhead by
+ using NULL ciphers (<literal>NULL-SHA</> or <literal>NULL-MD5</> in
+ <productname>OpenSSL</productname> for example). However,
+ a man-in-the-middle could read and pass communications between client
+ and server. Also, encryption overhead is minimal compared to the
+ overhead of authentication. For these reasons NULL ciphers are not
+ recommended.
+ </para>
+ </note>
+
+ <sect2 id="ssl-library-openssl">
+ <title>Using OpenSSL</title>
+
+ <para>
+ <productname>PostgreSQL</productname> reads the system-wide
+ <productname>OpenSSL</productname> configuration file. By default, this
+ file is named <filename>openssl.cnf</filename> and is located in the
+ directory reported by <literal>openssl version -d</>.
+ This default can be overridden by setting environment variable
+ <envar>OPENSSL_CONF</envar> to the name of the desired configuration file.
+ </para>
+
+ <para>
+ <productname>OpenSSL</productname> supports a wide range of ciphers
+ and authentication algorithms, of varying strength. While a list of
+ ciphers can be specified in the <productname>OpenSSL</productname>
+ configuration file, you can specify ciphers specifically for use by
+ the database server by modifying <xref linkend="guc-ssl-ciphers"> in
+ <filename>postgresql.conf</>.
+ </para>
+ </sect2>
+
+ <sect2 id="ssl-library-secure-transport">
+ <title>Using Secure Transport</title>
+
+ <para>
+ <productname>Secure Transport</productname> can use certificates and
+ keys either stored in files, or load them as an identity from a
+ <productname>Keychain</productname>. To load an identity, the default
+ <productname>Keychain</productname> as well as the
+ <productname>Keychain</productname> specified by the
+ <xref linkeend="guc-ssl-keychain-file"> parameter is searched. The identity
+ is specified by prefixing <xref linkend="guc-ssl-cert-file"> with
+ <literal>keychain:</> and specifying the common name of the certificate.
+ </para>
+ </sect2>
+
<sect2 id="ssl-client-certificates">
<title>Using Client Certificates</title>
@@ -2363,6 +2414,12 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<entry>client certificate must not be on this list</entry>
</row>
+ <row>
+ <entry><xref linkend="guc-ssl-keychain-file"></entry>
+ <entry>server identities</entry>
+ <entry>searched for server identities (certificate and key)</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
--
2.14.1.145.gb3622a4ee
0004-Fix-SSL-tests-for-connstrings-with-spaces-v2.patchapplication/octet-stream; name=0004-Fix-SSL-tests-for-connstrings-with-spaces-v2.patchDownload
From b8e98e46720c2f9043ee58e4f4a2ad05bb61931a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Thu, 17 Aug 2017 14:48:08 +0200
Subject: [PATCH 4/4] Fix SSL tests for connstrings with spaces
Connectionstrings can have items with spaces in them, wrapped in
quotes. The tests however ran a SELECT '$connstr' upon connection
which broke on the embedded quotes. Use doubledollar quotes on the
connstr to protect against this.
---
src/test/ssl/t/001_ssltests.pl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index f4daf46e44..066c84a4c2 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -23,7 +23,7 @@ sub run_test_psql
my $logstring = $_[1];
my $cmd = [
- 'psql', '-X', '-A', '-t', '-c', "SELECT 'connected with $connstr'",
+ 'psql', '-X', '-A', '-t', '-c', "SELECT \$\$connected with $connstr\$\$",
'-d', "$connstr" ];
my $result = run_log($cmd);
--
2.14.1.145.gb3622a4ee
On Fri, Aug 18, 2017 at 2:14 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
Attached is an updated set of patches, rebased on top of master, with bug fixes
and additional features missing in the first set. While not complete (yet), in
case anyone is testing this I’d rather send a fresh batch rather than sitting
on them too long while I keep hacking at the docs. While not every part of
this rather large changeset has been touched, this includes all the patches for
completeness sake.
Hi,
+#if defined(USE_OPENSSL) || defined(USE_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
#endif
If you configure with neither --with-securetransport nor
--with-openssl then SSL_LIBRARY finishes up undefined, and then guc.c
doesn't compile:
ccache gcc -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels
-Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
-fwrapv -fexcess-precision=standard -g -O2 -I. -I.
-I../../../../src/include -D_GNU_SOURCE -c -o guc.o guc.c
guc.c:3309:3: error: ‘SSL_LIBRARY’ undeclared here (not in a function)
SSL_LIBRARY,
^~~~~~~~~~~
I guess it should have a fallback definition, though I don't know what
it should be.
--
Thomas Munro
http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sun, Aug 20, 2017 at 8:10 AM, Thomas Munro
<thomas.munro@enterprisedb.com> wrote:
On Fri, Aug 18, 2017 at 2:14 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
Attached is an updated set of patches, rebased on top of master, with bug fixes
and additional features missing in the first set. While not complete (yet), in
case anyone is testing this I’d rather send a fresh batch rather than sitting
on them too long while I keep hacking at the docs. While not every part of
this rather large changeset has been touched, this includes all the patches for
completeness sake.Hi,
+#if defined(USE_OPENSSL) || defined(USE_SECURETRANSPORT) #define USE_SSL +#if defined(USE_OPENSSL) +#define SSL_LIBRARY "OpenSSL" +#elif defined(USE_SECURETRANSPORT) +#define SSL_LIBRARY "Secure Transport" +#endif #endifIf you configure with neither --with-securetransport nor
--with-openssl then SSL_LIBRARY finishes up undefined, and then guc.c
doesn't compile:ccache gcc -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels
-Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
-fwrapv -fexcess-precision=standard -g -O2 -I. -I.
-I../../../../src/include -D_GNU_SOURCE -c -o guc.o guc.c
guc.c:3309:3: error: ‘SSL_LIBRARY’ undeclared here (not in a function)
SSL_LIBRARY,
^~~~~~~~~~~I guess it should have a fallback definition, though I don't know what
it should be.
Or maybe the guc should only exist if SSL_LIBRARY is defined? I mean
#if defined(SSL_LIBRARY) around this:
+ {
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
--
Thomas Munro
http://www.enterprisedb.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 19 Aug 2017, at 23:13, Thomas Munro <thomas.munro@enterprisedb.com> wrote:
On Sun, Aug 20, 2017 at 8:10 AM, Thomas Munro
<thomas.munro@enterprisedb.com <mailto:thomas.munro@enterprisedb.com>> wrote:On Fri, Aug 18, 2017 at 2:14 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
Attached is an updated set of patches, rebased on top of master, with bug fixes
and additional features missing in the first set. While not complete (yet), in
case anyone is testing this I’d rather send a fresh batch rather than sitting
on them too long while I keep hacking at the docs. While not every part of
this rather large changeset has been touched, this includes all the patches for
completeness sake.Hi,
+#if defined(USE_OPENSSL) || defined(USE_SECURETRANSPORT) #define USE_SSL +#if defined(USE_OPENSSL) +#define SSL_LIBRARY "OpenSSL" +#elif defined(USE_SECURETRANSPORT) +#define SSL_LIBRARY "Secure Transport" +#endif #endifIf you configure with neither --with-securetransport nor
--with-openssl then SSL_LIBRARY finishes up undefined, and then guc.c
doesn't compile:ccache gcc -Wall -Wmissing-prototypes -Wpointer-arith
-Wdeclaration-after-statement -Wendif-labels
-Wmissing-format-attribute -Wformat-security -fno-strict-aliasing
-fwrapv -fexcess-precision=standard -g -O2 -I. -I.
-I../../../../src/include -D_GNU_SOURCE -c -o guc.o guc.c
guc.c:3309:3: error: ‘SSL_LIBRARY’ undeclared here (not in a function)
SSL_LIBRARY,
^~~~~~~~~~~I guess it should have a fallback definition, though I don't know what
it should be.Or maybe the guc should only exist if SSL_LIBRARY is defined?
I think the intended use case of the GUC should drive the decision on fallback.
If the GUC isn’t supposed to be a way to figure out if the server was built
with SSL support, then not existing in non-SSL backends is fine. If, however,
we want to allow using the GUC to see if the server has SSL support, then there
needs to be a “None” or similar value for that case.
Personally I think there is risk of regrets down the line if this GUC is used
for two things, but thats more of a gut feeling than scientifically studied.
Clearly there shouldn’t be a compilation error in either case, sorry about
missing that in the submission.
cheers ./daniel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Aug 21, 2017 at 6:21 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
On 19 Aug 2017, at 23:13, Thomas Munro <thomas.munro@enterprisedb.com> wrote:
I guess it should have a fallback definition, though I don't know what
it should be.Or maybe the guc should only exist if SSL_LIBRARY is defined?
I think the intended use case of the GUC should drive the decision on fallback.
If the GUC isn’t supposed to be a way to figure out if the server was built
with SSL support, then not existing in non-SSL backends is fine. If, however,
we want to allow using the GUC to see if the server has SSL support, then there
needs to be a “None” or similar value for that case.
Only GUCs related to debugging have their existence defined based on a
#define, so it seems to me that if Postgres is compiled without any
SSL support, this parameter should still be visible, but set to
"none".
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Aug 21, 2017 at 9:46 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Mon, Aug 21, 2017 at 6:21 AM, Daniel Gustafsson <daniel@yesql.se> wrote:
I think the intended use case of the GUC should drive the decision on fallback.
If the GUC isn’t supposed to be a way to figure out if the server was built
with SSL support, then not existing in non-SSL backends is fine. If, however,
we want to allow using the GUC to see if the server has SSL support, then there
needs to be a “None” or similar value for that case.Only GUCs related to debugging have their existence defined based on a
#define, so it seems to me that if Postgres is compiled without any
SSL support, this parameter should still be visible, but set to
"none".
The last set of patches available here does not apply:
/messages/by-id/B5E2B87D-3E8A-4597-9A7F-8489B3B67556@yesql.se
The SSL test refactoring is one cause. I think as well that this is
crashing when attempting to use SCRAM authentication with the SSL
brand of macos and SCRAM's channel binding. I am going to send a patch
which allows handling of no support for channel bindings for a given
SSL implementation, something needed as well by the gnutls patch.
Please make sure that you define at least be_tls_get_peer_finished()
and pgtls_get_finished() with a NULL result and a length of 0 as
return results as, as far as I can see, macos does not give direct
access to the TLS finish message bytes. At least that's not
documented.
--
Michael
On Mon, Nov 20, 2017 at 11:35 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
The last set of patches available here does not apply:
/messages/by-id/B5E2B87D-3E8A-4597-9A7F-8489B3B67556@yesql.se
The SSL test refactoring is one cause. I think as well that this is
crashing when attempting to use SCRAM authentication with the SSL
brand of macos and SCRAM's channel binding. I am going to send a patch
which allows handling of no support for channel bindings for a given
SSL implementation, something needed as well by the gnutls patch.
Please make sure that you define at least be_tls_get_peer_finished()
and pgtls_get_finished() with a NULL result and a length of 0 as
return results as, as far as I can see, macos does not give direct
access to the TLS finish message bytes. At least that's not
documented.
This last comment is from last week, so I am marking the patch as
returned with feedback. This also needs more thoughts for channel
binding support with SCRAM.
--
Michael
Here’s an attempt at reviving an old patch that I’ve neglected for too long.
The attached patchset rebases Secure Transport support over HEAD and adds stub
functions for that the SCRAM support added to make everything compile and run
the SSL testsuite. There are no new features or bugfixes over the previously
posted patches.
Wrt SCRAM, I’m probably thick but I can’t really see what I need to do to
handle SCRAM, so I wouldn’t mind some cluesticks on that. The Secure Transport
API doesn’t allow for getting the TLS Finished message (at least I haven’t been
able to find a way), so channel binding can’t be supported afaict.
The testcode has been updated to handle Secure Transport, but it’s not
in a clean form, rather a quick hack to get something running while the project
settles on how to handle multiple SSL implementations.
I have for now excluded the previous doc changes awating the discussion on the
patch in 1f34fa82-52a0-1682-87ba-4c3c3d0afcc0@2ndquadrant.com, once that
settles I’ll revive and write the documentation. The same goes for GUCs etc
which are discussed in other threads.
As per before, my patch for running tests against another set of binaries is
included as well as a fix for connstrings with spaces, but with the recent
hacking by Peter I assume this is superfluous. It was handy for development so
I’ve kept it around though.
cheers ./daniel
Attachments:
0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v3.patchapplication/octet-stream; name=0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v3.patchDownload
From c20c4529f738764db55c10463ae9888144943b9f Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 21 Jan 2018 23:40:04 +0100
Subject: [PATCH 1/3] WIP: Add support for Apple Secure Transport SSL library
This adds frontend and backend support for the Secure Transport SSL
library on macOS, but currently lacks SCRAM support. Thus, the patch
is not in a final state but is provided for hacking and testing.
---
configure | 57 +
configure.in | 14 +
src/Makefile.global.in | 1 +
src/backend/Makefile | 6 +-
src/backend/libpq/Makefile | 5 +
src/backend/libpq/be-secure-securetransport.c | 1363 ++++++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 25 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/common/securetransport.h | 510 ++++++++
src/include/common/sha2.h | 4 +-
src/include/libpq/libpq-be.h | 17 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 12 +-
src/interfaces/libpq/Makefile | 6 +-
src/interfaces/libpq/fe-connect.c | 6 +
src/interfaces/libpq/fe-secure-securetransport.c | 1505 ++++++++++++++++++++++
src/interfaces/libpq/libpq-int.h | 22 +-
src/test/ssl/Makefile | 14 +
src/test/ssl/ServerSetup.pm | 9 +-
src/test/ssl/t/001_ssltests.pl | 111 +-
src/test/ssl/t/002_scram.pl | 30 +-
23 files changed, 3686 insertions(+), 41 deletions(-)
create mode 100644 src/backend/libpq/be-secure-securetransport.c
create mode 100644 src/include/common/securetransport.h
create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c
diff --git a/configure b/configure
index 7dcca506f8..b9db714127 100755
--- a/configure
+++ b/configure
@@ -707,6 +707,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
krb_srvtab
with_python
@@ -835,6 +836,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1529,6 +1531,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -5995,6 +5998,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
#
# SELinux
#
@@ -10978,6 +11016,25 @@ else
fi
+fi
+
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.in b/configure.in
index 4d26034579..20f3d55c07 100644
--- a/configure.in
+++ b/configure.in
@@ -703,6 +703,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
#
# SELinux
#
@@ -1225,6 +1234,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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index d980f81046..9f60a3dd0d 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_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_libxml = @with_libxml@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a28267339..fc11b7271c 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -49,6 +49,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LDFLAGS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-schemapg postgres $(POSTGRES_IMP)
@@ -58,7 +62,7 @@ ifneq ($(PORTNAME), win32)
ifneq ($(PORTNAME), aix)
postgres: $(OBJS)
- $(CC) $(CFLAGS) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@
+ $(CC) $(CFLAGS) -framework CoreFoundation -framework Security $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@
endif
endif
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 7fa2b02743..3a835235b9 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000000..6c8d88b401
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1363 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - Load DH keys from file
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future).
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interested to investigate.
+ *
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "common/base64.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#define bool pg_bool
+#include <Security/cssmerr.h>
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef bool
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_bool bool
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err. Since we
+ * may have encountered an error due to memory pressure, we don't want to rely
+ * on dynamically allocating memory for this error message.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+/*
+ * SecIdentityCreate is not an exported Secure Transport API. There is however
+ * no exported Secure Transport function that can create an identity from a
+ * SecCertificateRef and a SecKeyRef without having either of them in a
+ * Keychain. This function is commonly used in open source projects (such as
+ * ffmpeg and mono f.e), but finding an alternative is a TODO.
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static void load_key(char *name, CFArrayRef *out);
+static OSStatus load_keychain(char *name, CFArrayRef *keychains);
+static OSStatus load_certificate_file(char *name, CFArrayRef *cert_array);
+static OSStatus load_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+
+static int load_dh_file(char *filename, char **buf);
+static void load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl);
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+
+/* ------------------------------------------------------------ */
+/* Hardcoded DH parameters */
+/* ------------------------------------------------------------ */
+
+/*
+ * Pre-computed DH param to use in case the user didn't specify a dhparam file
+ * with a custom param in it. This is to avoid the very lengthy calculation
+ * process that Secure Transport will otherwise do per connection.
+ *
+ * TODO: This is the same param as in the OpenSSL backend, and duplicating it
+ * is clearly not a terribly good thing. This should probably be moved to a
+ * common header file, or the creation of it should be re-thought in case we
+ * want to keep separate precomputed params per SSL backend.
+ */
+static const char file_dh2048[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV\n\
+89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50\n\
+T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb\n\
+zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX\n\
+Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT\n\
+CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
+-----END DH PARAMETERS-----\n";
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize global Secure Transport structures (if any)
+ *
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ *
+ * Another reason to defer this until when in the backend is that Keychains
+ * are SQLite3 backed, and sqlite does not allow access across a fork. See
+ * https://sqlite.org/faq.html#q6 for more information.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * bt_tls_open_server
+ * Attempt to negotiate a secure connection
+ *
+ * Unlike the OpenSSL backend, this function is responsible for loading keys
+ * and certificates for use in the connection. See the comment on be_tls_init
+ * for further reasoning around this.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFArrayRef keychains;
+ CFMutableArrayRef chain;
+ char *dh_buf;
+ int dh_len;
+
+ Assert(!port->ssl);
+
+ if (ssl_keychain_file[0])
+ load_keychain(ssl_keychain_file, &keychains);
+
+ /*
+ * If the ssl_cert_file is prefixed with a keychain reference, we will try
+ * to load a complete identity from the Keychain.
+ */
+ if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains);
+ else
+ {
+ status = load_certificate_file(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": \"%s\"",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ load_key(ssl_key_file, &keys);
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ }
+
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate_file(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": \"%s\"",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API
+ */
+ if (ssl_crl_file[0])
+ ereport(FATAL,
+ (errmsg("CRL files not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ /*
+ * We use kTryAuthenticate here since we don't know which sslmode the
+ * client is using. If we were to use kAlwaysAuthenticate then sslmode
+ * require won't work as intended.
+ */
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * In case the user hasn't configured a DH parameters file, we load a pre-
+ * computed DH parameter to avoid having Secure Transport computing one for
+ * us (which is done by default unless one is set).
+ */
+ dh_buf = NULL;
+ if (ssl_dh_params_file[0])
+ dh_len = load_dh_file(ssl_dh_params_file, &dh_buf);
+ if (!dh_buf || dh_len == 0)
+ {
+ dh_buf = pstrdup(file_dh2048);
+ dh_len = sizeof(dh_buf);
+ }
+
+ load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl);
+
+ /*
+ * Set Tlsv1.2 as the minimum protocol version we allow for the connection
+ */
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ if (status == errSSLWouldBlock)
+ continue;
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+
+ CFStringRef usercert_cn;
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+ port->peer_cn = pstrdup(CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8));
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ *
+ * Loads a private key from the specified filename. Unless the key loads this
+ * will not return but will error out.
+ */
+static void
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+
+ ret = stat(name, &stat_buf);
+ if (ret != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to stat",
+ name)));
+
+ if (!S_ISREG(stat_buf.st_mode))
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": not a regular file",
+ name)));
+
+ /*
+ * Require no public access to the key file. If the file is owned by us,
+ * require mode 0600 or less. If owned by root, require 0640 or less to
+ * allow read access through our gid, or a supplementary gid that allows to
+ * read system-wide certificates.
+ */
+ if ((stat_buf.st_uid == geteuid() && stat_buf.st_mode & (S_IRWXG | S_IRWXO)) ||
+ (stat_buf.st_uid == 0 && stat_buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" has group or world access",
+ name),
+ errdetail("File must have permissions u=rw (0600) or less "
+ "if owned by the database user, or permissions "
+ "u=rw,g=r (0640) or less if owned by root.")));
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ SecItemImportExportKeyParameters params;
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": \"%s\"",
+ name, pg_SSLerrmessage(status))));
+}
+
+/*
+ * load_keychain
+ * Open the specified, and default, Keychain
+ *
+ * Operations on keychains other than the default take the keychain references
+ * in an array. We need to copy a reference to the default keychain into the
+ * array to include it in the search.
+ *
+ * For server applications, we don't want modal dialog boxes opened for
+ * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE)
+ * will turn off all GUI interaction with the user, which may seem like what we
+ * want server side. This however has the side effect to turn off all GUI
+ * elements for all applications until some application calls
+ * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus
+ * remove wanted GUI interaction from another app, or another app might
+ * introduce it for
+ * us.
+ */
+static OSStatus
+load_keychain(char *name, CFArrayRef *keychains)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ SecKeychainRef kc[2];
+
+ if (stat(name, &stat_buf) != 0)
+ return errSecInvalidValue;
+
+ status = SecKeychainOpen(name, &kc[0]);
+ if (status == noErr)
+ {
+ SecKeychainUnlock(kc[0], 0, "", TRUE);
+ /*
+ * We only need to open, and add, the default Keychain in case have a
+ * user keychain opened, else we will pass NULL to any keychain search
+ * which will use the default keychain by.. default.
+ */
+ SecKeychainCopyDefault(&kc[1]);
+ *keychains = CFArrayCreate(NULL, (const void **) kc, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ else
+ elog(LOG, "XXX: could not load keychain \"%s\" : %d", name, status);
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+load_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+/*
+ * load_certificate_file
+ * Extracts a certificate from a PEM file on the filesystem
+ *
+ * TODO: figure out better returncodes
+ */
+static OSStatus
+load_certificate_file(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ /*
+ * If the configured ssl_cert_file filename is set to a non-existing
+ * file, assume it's referencing a Keychain label and attempt to load
+ * the certificate from the Keychain instead.
+ */
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ /*
+ * TODO: Do we want to search keychains for certificates serverside
+ * like we do clientside, or do we want to stick to a single way to
+ * configure the server? Since CRL files aren't supported outside
+ * keychains I guess we need to, but worth a discussion.
+ */
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ return errSecInternalError;
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ return errSecInternalError;
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL, cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ return errSecInternalError;
+}
+
+/*
+ * XXX: Make this file loading and slurping only
+ */
+static int
+load_dh_file(char *filename, char **buf)
+{
+ FILE *dh;
+ struct stat stat_buf;
+ int ret;
+
+ /*
+ * Open the DH file and slurp the contents. If the file doesn't exist it's
+ * not an error, if it can't be opened it is however an error.
+ */
+ ret = stat(filename, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ return 0;
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((dh = AllocateFile(filename, "r")) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+
+ *buf = palloc(stat_buf.st_size);
+ ret = fread(*buf, 1, stat_buf.st_size, dh);
+ FreeFile(dh);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read DH parameters file \"%s\": %m",
+ filename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("DH parameters file \"%s\" is not a regular file",
+ filename)));
+
+ return ret;
+}
+
+/*
+ * load_dh_params
+ * Load the specified DH params for the connection
+ *
+ * Secure Transport requires the DH params to be in DER format, but to be
+ * compatible with the OpenSSL code we also support PEM and convert to DER
+ * before loading. Conversion does rudimentary PEM parsing, if we miss the
+ * data being correct, the Secure Transport API will give an error anyways so
+ * we're just checking basic integrity.
+ *
+ * This function may scribble on the dh parameter so if that's required so stay
+ * intact in the caller, a copy should be sent.
+ */
+#define DH_HEADER "-----BEGIN DH PARAMETERS-----"
+#define DH_FOOTER "-----END DH PARAMETERS-----"
+static void
+load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl)
+{
+ OSStatus status;
+ char *der;
+ int der_len;
+
+ Assert(dh);
+
+ /*
+ * Convert PEM to DER
+ */
+ if (is_pem)
+ {
+ char *head;
+ char *tail;
+ int pem_len = 0;
+
+ if (strstr(dh, DH_HEADER) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, header missing")));
+
+ dh += strlen(DH_HEADER);
+ tail = strstr(dh, DH_FOOTER);
+ if (!tail)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, footer missing")));
+ *tail = '\0';
+
+ /* In order to PEM convert it we need to remove all newlines */
+ head = dh;
+ tail = dh;
+ while (*head != '\0')
+ {
+ if (*head != '\n')
+ {
+ *tail++ = *head++;
+ pem_len++;
+ }
+ else
+ head++;
+ }
+ *tail = '\0';
+
+ der = palloc(pg_b64_dec_len(strlen(dh)) + 1);
+ der_len = pg_b64_decode(dh, strlen(dh), der);
+ der[der_len] = '\0';
+ }
+ else
+ {
+ der = dh;
+ der_len = len;
+ }
+
+ status = SSLSetDiffieHellmanParams(ssl, der, der_len);
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unable to load DH parameters: %s",
+ pg_SSLerrmessage(status))));
+}
+
+/*
+ * This function should return the expected TLS Finished message information
+ * from the client, but there is no API for this in Secure Transport. As a
+ * result, channel binding is not supported by Secure Transport.
+ */
+char *
+be_tls_get_peer_finished(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+/*
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+void
+be_tls_get_version(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (ptr == NULL || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ strlcpy(ptr, "TLSv1.1", len);
+ break;
+ case kTLSProtocol12:
+ strlcpy(ptr, "TLSv1.2", len);
+ break;
+ default:
+ strlcpy(ptr, "unknown", len);
+ break;
+ }
+ }
+}
+
+/*
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ *
+ * Returns NULL in case we weren't able to either get the negotiated cipher, or
+ * translate it into a human readable string.
+ */
+void
+be_tls_get_cipher(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+ const char *cipher_name;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return;
+
+ cipher_name = pg_SSLciphername(cipher);
+ if (cipher_name != NULL)
+ strlcpy(ptr, cipher_name, len);
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ /*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there still is no translation for it in 10.11 El Capitan, so we
+ * maintain our own
+ */
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index eb42ea1a1e..ac1edf9bb8 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -45,6 +45,9 @@ char *ssl_key_file;
char *ssl_ca_file;
char *ssl_crl_file;
char *ssl_dh_params_file;
+#ifdef USE_SECURETRANSPORT
+char *ssl_keychain_file;
+#endif
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d03ba234b5..3f43647227 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -501,6 +501,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -3322,6 +3323,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
{
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
@@ -3569,6 +3582,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ gettext_noop("Location of the Keychain file."),
+ NULL
+ },
+ &ssl_keychain_file,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 69f40f04b0..3c20baf424 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,6 +85,7 @@
#ssl_key_file = 'server.key'
#ssl_ca_file = ''
#ssl_crl_file = ''
+#ssl_keychain_file = ''
#password_encryption = md5 # md5 or scram-sha-256
#db_user_namespace = off
#row_security = on
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000000..997c504e41
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,510 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport_common.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0d28..6d35f47864 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 49cb263110..6adf6d5794 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,19 +191,22 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+ void *ssl;
+ int ssl_buffered;
#endif
} Port;
#ifdef USE_SSL
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
extern int be_tls_init(bool isServerStart);
extern void be_tls_destroy(void);
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 2e7725db21..1323dc2549 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -80,6 +80,9 @@ extern char *ssl_key_file;
extern char *ssl_ca_file;
extern char *ssl_crl_file;
extern char *ssl_dh_params_file;
+#ifdef USE_SECURETRANSPORT
+extern char *ssl_keychain_file;
+#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 f98f773ff0..a1d71a6473 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -856,6 +856,9 @@
/* Define to use OpenSSL for random number generation */
#undef USE_OPENSSL_RANDOM
+/* Define to build with Apple Secure Transport support. (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index b309395f11..5469473d72 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -162,11 +162,17 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
+#else
+#define SSL_LIBRARY "None"
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0bf1e7ef04..fe8662d1b6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,11 @@ else
OBJS += sha2.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -112,7 +117,6 @@ ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
-
distprep: libpq-dist.rc
libpq.rc libpq-dist.rc: libpq.rc.in
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 77eebb0ba1..ede910f250 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -330,6 +330,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000000..bec3335d8e
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1505 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readiblitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *common_name,
+ SecCertificateRef *certificate,
+ CFArrayRef keychains,
+ char *hostname);
+static OSStatus import_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+static OSStatus import_pem(const char *path, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * Exported function to allow application to tell us it's already initialized
+ * Secure Transport and/or libcrypto.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ return;
+}
+
+/*
+ * Begin or continue negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin(conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection(conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs(conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we dont have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ SSLSetPeerDomainName(conn->ssl, conn->pghost, strlen(conn->pghost));
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption(conn->ssl, kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake(conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust(conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ /*
+ * TODO: return a better error code than SSLInternalError
+ */
+ if (!trusted)
+ return errSecInternalError;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize(conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead(conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite(conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite(conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+/*
+ */
+char *
+pgtls_get_finished(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding is not supported by this build\n"));
+ return NULL;
+}
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding is not supported by this build\n"));
+ return NULL;
+}
+
+/*
+ * Initialize SSL system, in particular creating the SSL_context object
+ * that will be shared by all SSL-using connections in this process.
+ *
+ * In threadsafe mode, this includes setting up libcrypto callback functions
+ * to do thread locking.
+ *
+ * If the caller has told us (through PQinitOpenSSL) that he's taking care
+ * of libcrypto, we expect that callbacks are already set, and won't try to
+ * override it.
+ *
+ * The conn parameter is only used to be able to pass back an error
+ * message - no connection-local setup is made here.
+ *
+ * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose(conn->ssl);
+ CFRelease(conn->ssl);
+
+ /* TODO: Release any certificates loaded */
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+import_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_certificate_keychain(const char *common_name, SecCertificateRef *certificate,
+ CFArrayRef keychains, char *hostname)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFStringRef host = NULL;
+ CFArrayRef temp;
+ SecPolicyRef ssl_policy;
+ int i;
+
+ /*
+ * Make sure the user didn't just specify the keychain prefix as the
+ * certificate config. The passed certificate will have the keychain
+ * prefix stripped so in that case the string is expected to be empty.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ /*
+ * If we didn't get a set of Keychains passed, skip adding it to the
+ * dictionary thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * Specifying a hostname requires it to match the hostname in the leaf
+ * certificate.
+ */
+ if (hostname)
+ host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8);
+ ssl_policy = SecPolicyCreateSSL(true, host);
+ CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label has been reported to not work (revisions of 10.12), we request
+ * all and find the one we want. Copy all the results to a temp array and
+ * scan it for the certificate we are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i);
+
+ if (search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ *certificate = (SecCertificateRef) CFRetain(search_cert);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(ssl_policy);
+ CFRelease(query);
+ CFRelease(cert);
+ if (host)
+ CFRelease(host);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, char *passphrase, CFArrayRef *certificate)
+{
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+ struct stat buf;
+
+ if (!path || strlen(path) == 0)
+ return errSecInvalidValue;
+
+ if (stat(path, &buf) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return -1;
+
+ return errSecInvalidValue;
+ }
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInvalidValue;
+
+ certdata = malloc(buf.st_size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, buf.st_size, fp) != buf.st_size)
+ {
+ fclose(fp);
+ free(certdata);
+ return errSSLBadCert;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, buf.st_size);
+ free(certdata);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecAllocate;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ return SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ bool have_homedir;
+ bool cert_from_file = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecCertificateRef root_ref[1];
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+ SecKeychainRef kcref[2];
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching. If no additional keychain is specified we don't need to open
+ * the default keychain and pass to searches since Secure Transport will
+ * use the default when passing NULL instead of an array of Keychain refs.
+ */
+ if (conn->keychain)
+ {
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr && kcref[0] != NULL)
+ {
+ SecKeychainStatus kcstatus;
+ status = SecKeychainGetStatus(kcref[0], &kcstatus);
+ if (status == noErr)
+ {
+ switch (kcstatus)
+ {
+ /*
+ * If the Keychain is already unlocked, readable or
+ * writeable, we don't need to do more. If not, try to
+ * unlock it.
+ */
+ case kSecUnlockStateStatus:
+ case kSecReadPermStatus:
+ case kSecWritePermStatus:
+ break;
+ default:
+ /* XXX: fix passphrase param */
+ SecKeychainUnlock(kcref[0], 0, "", TRUE);
+ break;
+ }
+ }
+
+ /*
+ * We only need to open, and add, the default Keychain in case
+ * have a user keychain opened, else we will pass NULL to any
+ * keychain search which will use the default keychain by..
+ * default.
+ */
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the files
+ * could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Try to load the root cert from either a user defined keychain or the
+ * default Keychain in case none is specified
+ */
+ if (conn->sslrootcert &&
+ pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ root_ref[0] = NULL;
+ strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL);
+
+ if (root_ref[0])
+ conn->st_rootcert = (void *) CFArrayCreate(NULL,
+ (const void **) root_ref,
+ 1, &kCFTypeArrayCallBacks);
+ }
+
+ if (!conn->st_rootcert)
+ {
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+ }
+
+ /*
+ * If the sslcert config is prefixed with a keychain identifier, the cert
+ * must be located in either the default or the specified keychain.
+ */
+ if (conn->sslcert &&
+ pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_identity_keychain(fnbuf, &identity, keychains);
+
+ if (identity)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ /*
+ * No prefix on the sslcert config, the certificate must thus reside in a
+ * file on disk.
+ */
+ else
+ {
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, cert_array);
+ if (status != noErr)
+ {
+ if (status == -1)
+ return noErr;
+
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ cert_from_file = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in
+ * order to create the identity.
+ */
+
+ /*
+ * The sslkey config is prefixed with keychain: indicating that the
+ * key should be loaded from Keychain instead of the filesystem.
+ * Search for the private key matching the cert_ref in the opened
+ * Keychains. If found, we get the identity returned.
+ */
+ if (conn->sslkey &&
+ pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref,
+ &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the key to be
+ * loaded from that file.
+ */
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+ }
+ }
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ if (key_ref)
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ }
+
+ if (!identity)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create identity from certificate/key\n"));
+ return errSecInvalidValue;
+ }
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (cert_from_file)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate(conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+int
+PQsslInUse(PGconn *conn)
+{
+ if (!conn)
+ return 0;
+ return conn->ssl_in_use;
+}
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher(conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion(conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState(conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4e354098b3..9cf7d44ab3 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -80,6 +80,14 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_SECURETRANSPORT
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#undef Size
+#undef uint64
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -470,8 +478,18 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+
+ SSLContextRef ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 4886e901d0..872415a42a 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -123,6 +123,20 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### Keychains
+
+ifeq ($(with_securetransport),yes)
+
+KEYCHAINS := ssl/client.keychain
+
+# This target generates all Keychains
+keychains: $(KEYCHAINS)
+
+PWD=$(shell pwd)
+ssl/client.keychain: ssl/client.crt ssl/client.key
+ certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p=
+endif
+
.PHONY: sslfiles-clean
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
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index 02f8028b2b..c645ab2bc4 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -159,7 +159,14 @@ sub switch_server_cert
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";
+ if (check_pg_config("#define USE_SECURETRANSPORT 1"))
+ {
+ print $sslconf "ssl_crl_file=''\n";
+ }
+ else
+ {
+ print $sslconf "ssl_crl_file='root+client.crl'\n";
+ }
close $sslconf;
$node->reload;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 28837a1391..6061838a20 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -2,7 +2,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 40;
+use Test::More;
use ServerSetup;
use File::Copy;
@@ -21,10 +21,31 @@ my $common_connstr;
copy("ssl/client.key", "ssl/client_tmp.key");
chmod 0600, "ssl/client_tmp.key";
+# macOS Secure Transport does not support loading CRL files, so skip CRL
+# tests when built with Secure Transport support.
+my $supports_crl_files = ! check_pg_config("#define USE_SECURETRANSPORT 1");
+
+# Test for support of Secure Transport Keychain secure archives
+# This check is currently the same as $supports_crl_files, but clarity when
+# reading code is more important than avoiding the use of an extra variable
+my $supports_keychains = check_pg_config("#define USE_SECURETRANSPORT 1");
+
+#### Setup
+
+if ($supports_keychains)
+{
+ plan tests => 43;
+}
+else
+{
+ plan tests => 40;
+}
+
+
#### Part 0. Set up the server.
note "setting up data directory";
-my $node = get_new_node('master');
+my $node = get_new_node('master', $ENV{SSLTEST_SERVER_BIN});
$node->init;
# PGHOST is enforced here to set up the node, subsequent connections
@@ -91,18 +112,37 @@ test_connect_ok($common_connstr,
note "testing sslcrl option with a non-revoked cert";
-# 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");
-
-# 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");
-
-# 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"
-);
+if ($supports_crl_files)
+{
+ # 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");
+
+ # 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"
+ );
+
+ # 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");
+}
+else
+{
+ # Invalid CRL filename is the same as no CRL, succeeds
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid");
+
+ # With the correct CRL, succeeds (this cert is not revoked)
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+
+ # A CRL belonging to a different CA is not accepted, this should fail iff
+ # CRL files are supported
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl");
+}
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
@@ -176,9 +216,18 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok($common_connstr,
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca");
-test_connect_fails($common_connstr,
-"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
-);
+if ($supports_crl_files)
+{
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+}
+else
+{
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+}
### Part 2. Server-side tests.
###
@@ -199,10 +248,20 @@ test_connect_ok($common_connstr,
test_connect_fails($common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key");
-# revoked client cert
-test_connect_fails($common_connstr,
-"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
-);
+if ($supports_crl_files)
+{
+ # revoked client cert
+ test_connect_fails($common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
+ );
+}
+else
+{
+ # revoked client cert
+ test_connect_ok($common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
+ );
+}
# 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');
@@ -213,5 +272,15 @@ test_connect_ok($common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt");
test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt");
+if ($supports_keychains)
+{
+ # empty keychain
+ test_connect_fails("user=ssltestuser keychain=invalid");
+
+ # correct client cert in keychain with and without proper label
+ test_connect_fails("user=ssltestuser keychain=ssl/client.keychain");
+ test_connect_ok("user=ssltestuser sslcert=ssltestuser keychain=ssl/client.keychain");
+}
+
# clean up
unlink "ssl/client_tmp.key";
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 67c1409a6e..03a5533e29 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -15,6 +15,9 @@ my $SERVERHOSTADDR = '127.0.0.1';
my $supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+my $supports_tls_unique =
+ ! check_pg_config("#define USE_SECURETRANSPORT 1");
+
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -38,14 +41,27 @@ $ENV{PGPASSWORD} = "pass";
$common_connstr =
"user=ssltestuser dbname=trustdb sslmode=require hostaddr=$SERVERHOSTADDR";
-# Default settings
-test_connect_ok($common_connstr, '',
- "SCRAM authentication with default channel binding");
-
# Channel binding settings
-test_connect_ok($common_connstr,
- "scram_channel_binding=tls-unique",
- "SCRAM authentication with tls-unique as channel binding");
+if ($supports_tls_unique)
+{
+ # Default settings
+ test_connect_ok($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ test_connect_ok($common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
+else
+{
+ # Default settings
+ test_connect_fails($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ test_connect_fails($common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
test_connect_ok($common_connstr,
"scram_channel_binding=''",
"SCRAM authentication without channel binding");
--
2.14.1.145.gb3622a4ee
0002-Allow-running-SSL-tests-against-different-binar-v3.patchapplication/octet-stream; name=0002-Allow-running-SSL-tests-against-different-binar-v3.patchDownload
From a1b67419ebfcdc54adc2a7170901337ffc0813e1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 21 Jan 2018 23:41:35 +0100
Subject: [PATCH 2/3] Allow running SSL tests against different binaries
In order to be able to test work on SSL frontends, or backends,
against a stock installation, allow a pgpath setting to override
the path to the binaries. The SSLTEST_SERVER_BIN environment var
will be used, if set, for which set of server binaries to run.
SSLTEST_SERVER_BIN=/path/bin/ make -C src/test/ssl/ check
This is intended for testing during development in order to test
a new frontend against a known backend for example.
---
src/test/perl/PostgresNode.pm | 77 +++++++++++++++++++++++++++++++++----------
1 file changed, 59 insertions(+), 18 deletions(-)
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 1d5ac4ee35..41c980ee0c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -133,7 +133,7 @@ INIT
=over
-=item PostgresNode::new($class, $name, $pghost, $pgport)
+=item PostgresNode::new($class, $name, $pghost, $pgport, $pgpath)
Create a new PostgresNode instance. Does not initdb or start it.
@@ -144,12 +144,14 @@ of finding port numbers, registering instances for cleanup, etc.
sub new
{
- my ($class, $name, $pghost, $pgport) = @_;
+ my ($class, $name, $pghost, $pgport, $pgpath) = @_;
my $testname = basename($0);
$testname =~ s/\.[^.]+$//;
+ $pgpath = '' unless defined $pgpath;
my $self = {
_port => $pgport,
_host => $pghost,
+ _pgpath => $pgpath,
_basedir => "$TestLib::tmp_check/t_${testname}_${name}_data",
_name => $name,
_logfile => "$TestLib::log_path/${testname}_${name}.log" };
@@ -227,6 +229,21 @@ sub name
=pod
+=item $node->pgpath()
+
+Path to the PostgreSQL binaries to run, if empty then the installation in PATH
+is assumed.
+
+=cut
+
+sub pgpath
+{
+ my ($self) = @_;
+ return $self->{_pgpath};
+}
+
+=pod
+
=item $node->logfile()
Path to the PostgreSQL log file for this instance.
@@ -327,6 +344,7 @@ sub info
my $_info = '';
open my $fh, '>', \$_info or die;
print $fh "Name: " . $self->name . "\n";
+ print $fh "Binary directory: " . $self->pgpath . "\n";
print $fh "Data directory: " . $self->data_dir . "\n";
print $fh "Backup directory: " . $self->backup_dir . "\n";
print $fh "Archive directory: " . $self->archive_dir . "\n";
@@ -402,6 +420,7 @@ sub init
my $port = $self->port;
my $pgdata = $self->data_dir;
my $host = $self->host;
+ my $pgpath = $self->pgpath;
$params{allows_streaming} = 0 unless defined $params{allows_streaming};
$params{has_archiving} = 0 unless defined $params{has_archiving};
@@ -409,7 +428,7 @@ sub init
mkdir $self->backup_dir;
mkdir $self->archive_dir;
- TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+ TestLib::system_or_bail($pgpath . 'initdb', '-D', $pgdata, '-A', 'trust', '-N',
@{ $params{extra} });
TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata);
@@ -504,9 +523,10 @@ sub backup
my $backup_path = $self->backup_dir . '/' . $backup_name;
my $port = $self->port;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "# Taking pg_basebackup $backup_name from node \"$name\"\n";
- TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-p', $port,
+ TestLib::system_or_bail($pgpath . 'pg_basebackup', '-D', $backup_path, '-p', $port,
'--no-sync');
print "# Backup finished\n";
}
@@ -662,8 +682,9 @@ sub start
my $pgdata = $self->data_dir;
my $name = $self->name;
BAIL_OUT("node \"$name\" is already running") if defined $self->{_pid};
+ my $pgpath = $self->pgpath;
print("### Starting node \"$name\"\n");
- my $ret = TestLib::system_log('pg_ctl', '-D', $self->data_dir, '-l',
+ my $ret = TestLib::system_log($pgpath . 'pg_ctl', '-D', $self->data_dir, '-l',
$self->logfile, 'start');
if ($ret != 0)
@@ -694,10 +715,11 @@ sub stop
my $port = $self->port;
my $pgdata = $self->data_dir;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
$mode = 'fast' unless defined $mode;
return unless defined $self->{_pid};
print "### Stopping node \"$name\" using mode $mode\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-m', $mode, 'stop');
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-m', $mode, 'stop');
$self->_update_pid(0);
}
@@ -715,8 +737,10 @@ sub reload
my $port = $self->port;
my $pgdata = $self->data_dir;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Reloading node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, 'reload');
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, 'reload');
+
}
=pod
@@ -734,8 +758,9 @@ sub restart
my $pgdata = $self->data_dir;
my $logfile = $self->logfile;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Restarting node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-l', $logfile,
'restart');
$self->_update_pid(1);
}
@@ -755,8 +780,9 @@ sub promote
my $pgdata = $self->data_dir;
my $logfile = $self->logfile;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Promoting node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-l', $logfile,
'promote');
}
@@ -860,12 +886,15 @@ sub _update_pid
=pod
-=item PostgresNode->get_new_node(node_name)
+=item PostgresNode->get_new_node(node_name, pgpath)
Build a new object of class C<PostgresNode> (or of a subclass, if you have
one), assigning a free port number. Remembers the node, to prevent its port
number from being reused for another node, and to ensure that it gets
-shut down when the test script exits.
+shut down when the test script exits. If the pgpath parameter is defined then
+the path is tested for containing a PostgreSQL installation with the correct
+permissions for execution.
+
You should generally use this instead of C<PostgresNode::new(...)>.
@@ -877,8 +906,8 @@ which can only create objects of class C<PostgresNode>.
sub get_new_node
{
my $class = 'PostgresNode';
- $class = shift if 1 < scalar @_;
- my $name = shift;
+ $class = shift if 2 < scalar @_;
+ my ($name, $pgpath) = @_;
my $found = 0;
my $port = $last_port_assigned;
@@ -921,8 +950,19 @@ sub get_new_node
print "# Found free port $port\n";
+ # If a separate path to the binaries was specified then we do rudimentary
+ # checks for a sane installation. If an executable copy of initdb is there
+ # then let's go ahead.
+ if (defined $pgpath)
+ {
+ $pgpath =~ s!/*$!/!;
+ die "initdb missing in $pgpath" unless -x $pgpath . 'initdb';
+
+ print "# Found PostgreSQL installation in $pgpath\n";
+ }
+
# Lock port number found by creating a new node
- my $node = $class->new($name, $test_pghost, $port);
+ my $node = $class->new($name, $test_pghost, $port, $pgpath);
# Add node to list of nodes
push(@all_nodes, $node);
@@ -1129,8 +1169,9 @@ sub psql
my $stderr = $params{stderr};
my $timeout = undef;
my $timeout_exception = 'psql timed out';
+ my $pgpath = $self->pgpath;
my @psql_params =
- ('psql', '-XAtq', '-d', $self->connstr($dbname), '-f', '-');
+ ($pgpath . 'psql', '-XAtq', '-d', $self->connstr($dbname), '-f', '-');
# If the caller wants an array and hasn't passed stdout/stderr
# references, allocate temporary ones to capture them so we
@@ -1272,11 +1313,11 @@ Returns 1 if successful, 0 if timed out.
sub poll_query_until
{
- my ($self, $dbname, $query, $expected) = @_;
+ my ($self, $dbname, $query, $expected, $pgpath) = @_;
$expected = 't' unless defined($expected); # default value
- my $cmd = [ 'psql', '-XAt', '-c', $query, '-d', $self->connstr($dbname) ];
+ my $cmd = [ $pgpath . 'psql', '-XAt', '-c', $query, '-d', $self->connstr($dbname) ];
my ($stdout, $stderr);
my $max_attempts = 180 * 10;
my $attempts = 0;
@@ -1516,7 +1557,7 @@ sub wait_for_catchup
. $self->name . "\n";
my $query =
qq[SELECT $lsn_expr <= ${mode}_lsn FROM pg_catalog.pg_stat_replication WHERE application_name = '$standby_name';];
- $self->poll_query_until('postgres', $query)
+ $self->poll_query_until('postgres', $query, $self->pgpath)
or die "timed out waiting for catchup";
print "done\n";
}
--
2.14.1.145.gb3622a4ee
0003-Allow-spaces-in-connectionstrings-v3.patchapplication/octet-stream; name=0003-Allow-spaces-in-connectionstrings-v3.patchDownload
From 4074815dcd82c293a55e6d38d25f90b33144ad86 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 21 Jan 2018 23:42:20 +0100
Subject: [PATCH 3/3] Allow spaces in connectionstrings
Connectionstrings can have items with spaces in them, wrapped in
quotes. The tests however ran a SELECT '$connstr' upon connection
which broke on the embedded quotes. Use doubledollar quotes on the
connstr to protect against this. This was hit during the test of
the macOS Secure Transport patch, but is independent of it.
---
src/test/ssl/ServerSetup.pm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index c645ab2bc4..a419d29c94 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -42,7 +42,7 @@ sub run_test_psql
my $logstring = $_[1];
my $cmd = [
- 'psql', '-X', '-A', '-t', '-c', "SELECT 'connected with $connstr'",
+ 'psql', '-X', '-A', '-t', '-c', "SELECT \$\$connected with $connstr\$\$",
'-d', "$connstr" ];
my $result = run_log($cmd);
--
2.14.1.145.gb3622a4ee
On Mon, Jan 22, 2018 at 12:08:03AM +0100, Daniel Gustafsson wrote:
The attached patchset rebases Secure Transport support over HEAD and adds stub
functions for that the SCRAM support added to make everything compile and run
the SSL testsuite. There are no new features or bugfixes over the previously
posted patches.Wrt SCRAM, I’m probably thick but I can’t really see what I need to do to
handle SCRAM, so I wouldn’t mind some cluesticks on that. The Secure Transport
API doesn’t allow for getting the TLS Finished message (at least I haven’t been
able to find a way), so channel binding can’t be supported afaict.
OK, thanks. That sucks, perhaps Apple will improve things in the future,
this is the kind of areas where there is always interest.
The testcode has been updated to handle Secure Transport, but it’s not
in a clean form, rather a quick hack to get something running while the project
settles on how to handle multiple SSL implementations.I have for now excluded the previous doc changes awating the discussion on the
patch in 1f34fa82-52a0-1682-87ba-4c3c3d0afcc0@2ndquadrant.com, once that
settles I’ll revive and write the documentation. The same goes for GUCs etc
which are discussed in other threads.As per before, my patch for running tests against another set of binaries is
included as well as a fix for connstrings with spaces, but with the recent
hacking by Peter I assume this is superfluous. It was handy for development so
I’ve kept it around though.
Yes that looks useful to test.
be-secure-securetransport.c is quite a long name by the way, perhaps we
could just live with be-secure-osx.c or be-secure-mac.c?
Here are some comments about the SCRAM/channel binding part..
+be_tls_get_peer_finished(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
Those should mention the channel binding type.
In CheckSCRAMAuth(), you are missing the fact that SCRAM-SHA-256-PLUS is
still published to the client. I think that this is a mistake as no
channel binding types are supported. We may want to add an API for each
SSL implementation to help with that as I want to keep the code paths
fetching the channel binding data only invoked when necessary. So adding
a new API which returns a list of channel binding types supported by a
given backend would make the most sense to me. If the list is empty,
then -PLUS is not published by the backend. This is not a problem
related to your patch, more a problem that I need to solve as gnutls
only supports tls-unique. So I'll create a new thread on the matter with
a proper patch.
note "setting up data directory";
-my $node = get_new_node('master');
+my $node = get_new_node('master', $ENV{SSLTEST_SERVER_BIN});
$node->init;
This bit is in 0001, but this concept is introduced in 0002. (Not sure
how to feel about that yet, there are similar use-cases with
pg_upgrade's TAP tests where you may want to enforce a PATH.)
Nit: patch has some whitespaces. You may want to run a git diff --check
or such before sending.
--
Michael
On 22 Jan 2018, at 02:46, Michael Paquier <michael.paquier@gmail.com> wrote:
On Mon, Jan 22, 2018 at 12:08:03AM +0100, Daniel Gustafsson wrote:
The attached patchset rebases Secure Transport support over HEAD and adds stub
functions for that the SCRAM support added to make everything compile and run
the SSL testsuite. There are no new features or bugfixes over the previously
posted patches.Wrt SCRAM, I’m probably thick but I can’t really see what I need to do to
handle SCRAM, so I wouldn’t mind some cluesticks on that. The Secure Transport
API doesn’t allow for getting the TLS Finished message (at least I haven’t been
able to find a way), so channel binding can’t be supported afaict.OK, thanks. That sucks, perhaps Apple will improve things in the future,
this is the kind of areas where there is always interest.The testcode has been updated to handle Secure Transport, but it’s not
in a clean form, rather a quick hack to get something running while the project
settles on how to handle multiple SSL implementations.I have for now excluded the previous doc changes awating the discussion on the
patch in 1f34fa82-52a0-1682-87ba-4c3c3d0afcc0@2ndquadrant.com, once that
settles I’ll revive and write the documentation. The same goes for GUCs etc
which are discussed in other threads.As per before, my patch for running tests against another set of binaries is
included as well as a fix for connstrings with spaces, but with the recent
hacking by Peter I assume this is superfluous. It was handy for development so
I’ve kept it around though.Yes that looks useful to test.
be-secure-securetransport.c is quite a long name by the way, perhaps we
could just live with be-secure-osx.c or be-secure-mac.c?
Since OpenSSL supports macOS, naming it be-secure-mac.c seems like it can add
confusion. On a philosophical level, since Secure Transport is available on
multiple operating systems (macOS, iOS, tvOS and watchOS) it also seems odd to
name the file after a platform even though postgres isn’t supported on the
others. That being said, I don’t really have any strong opinions. Perhaps
-stransport.c could be an option?
Here are some comments about the SCRAM/channel binding part..
+be_tls_get_peer_finished(Port *port, size_t *len) +{ + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("channel binding is not supported by this build"))); + return NULL; Those should mention the channel binding type.
Good point, fixed.
In CheckSCRAMAuth(), you are missing the fact that SCRAM-SHA-256-PLUS is
still published to the client. I think that this is a mistake as no
channel binding types are supported. We may want to add an API for each
SSL implementation to help with that as I want to keep the code paths
fetching the channel binding data only invoked when necessary. So adding
a new API which returns a list of channel binding types supported by a
given backend would make the most sense to me. If the list is empty,
then -PLUS is not published by the backend. This is not a problem
related to your patch, more a problem that I need to solve as gnutls
only supports tls-unique. So I'll create a new thread on the matter with
a proper patch.
Aha, that does makes it clearer.
note "setting up data directory"; -my $node = get_new_node('master'); +my $node = get_new_node('master', $ENV{SSLTEST_SERVER_BIN}); $node->init; This bit is in 0001, but this concept is introduced in 0002. (Not sure how to feel about that yet, there are similar use-cases with pg_upgrade's TAP tests where you may want to enforce a PATH.)
Doh, that was a git add -p error on my part. Fixed in the attached patchset.
Although I do think there is value to being able to specify a PATH for a set of
binaries to test against, the 0002 patch is as mentioned mainly included as a
show-and-tell patch to show what I’ve used for testing. If could of course be
extended to other test suites should there be interest.
Nit: patch has some whitespaces. You may want to run a git diff --check
or such before sending.
Fixed.
Thanks for looking at this!
cheers ./daniel
Attachments:
0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v4.patchapplication/octet-stream; name=0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v4.patchDownload
From bcb273d6e9b4005c12196e6be96b4391e6fd32c5 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 21 Jan 2018 23:40:04 +0100
Subject: [PATCH 1/3] WIP: Add support for Apple Secure Transport SSL library
This adds frontend and backend support for the Secure Transport SSL
library on macOS, but currently lacks SCRAM support. Thus, the patch
is not in a final state but is provided for hacking and testing.
---
configure | 57 +
configure.in | 14 +
src/Makefile.global.in | 1 +
src/backend/Makefile | 6 +-
src/backend/libpq/Makefile | 5 +
src/backend/libpq/be-secure-securetransport.c | 1363 +++++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 25 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/common/securetransport.h | 514 ++++++++
src/include/common/sha2.h | 4 +-
src/include/libpq/libpq-be.h | 17 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 12 +-
src/interfaces/libpq/Makefile | 6 +-
src/interfaces/libpq/fe-connect.c | 6 +
src/interfaces/libpq/fe-secure-securetransport.c | 1507 ++++++++++++++++++++++
src/interfaces/libpq/libpq-int.h | 22 +-
src/test/ssl/Makefile | 14 +
src/test/ssl/ServerSetup.pm | 9 +-
src/test/ssl/t/001_ssltests.pl | 109 +-
src/test/ssl/t/002_scram.pl | 30 +-
23 files changed, 3691 insertions(+), 40 deletions(-)
create mode 100644 src/backend/libpq/be-secure-securetransport.c
create mode 100644 src/include/common/securetransport.h
create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c
diff --git a/configure b/configure
index 7dcca506f8..b9db714127 100755
--- a/configure
+++ b/configure
@@ -707,6 +707,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
krb_srvtab
with_python
@@ -835,6 +836,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1529,6 +1531,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -5995,6 +5998,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
#
# SELinux
#
@@ -10978,6 +11016,25 @@ else
fi
+fi
+
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.in b/configure.in
index 4d26034579..20f3d55c07 100644
--- a/configure.in
+++ b/configure.in
@@ -703,6 +703,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
#
# SELinux
#
@@ -1225,6 +1234,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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index d980f81046..9f60a3dd0d 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_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_libxml = @with_libxml@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a28267339..fc11b7271c 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -49,6 +49,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LDFLAGS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-schemapg postgres $(POSTGRES_IMP)
@@ -58,7 +62,7 @@ ifneq ($(PORTNAME), win32)
ifneq ($(PORTNAME), aix)
postgres: $(OBJS)
- $(CC) $(CFLAGS) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@
+ $(CC) $(CFLAGS) -framework CoreFoundation -framework Security $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@
endif
endif
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 7fa2b02743..3a835235b9 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000000..25bc84c78b
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1363 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - Load DH keys from file
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future).
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interested to investigate.
+ *
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "common/base64.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#define bool pg_bool
+#include <Security/cssmerr.h>
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef bool
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_bool bool
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err. Since we
+ * may have encountered an error due to memory pressure, we don't want to rely
+ * on dynamically allocating memory for this error message.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+/*
+ * SecIdentityCreate is not an exported Secure Transport API. There is however
+ * no exported Secure Transport function that can create an identity from a
+ * SecCertificateRef and a SecKeyRef without having either of them in a
+ * Keychain. This function is commonly used in open source projects (such as
+ * ffmpeg and mono f.e), but finding an alternative is a TODO.
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static void load_key(char *name, CFArrayRef *out);
+static OSStatus load_keychain(char *name, CFArrayRef *keychains);
+static OSStatus load_certificate_file(char *name, CFArrayRef *cert_array);
+static OSStatus load_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+
+static int load_dh_file(char *filename, char **buf);
+static void load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl);
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+
+/* ------------------------------------------------------------ */
+/* Hardcoded DH parameters */
+/* ------------------------------------------------------------ */
+
+/*
+ * Pre-computed DH param to use in case the user didn't specify a dhparam file
+ * with a custom param in it. This is to avoid the very lengthy calculation
+ * process that Secure Transport will otherwise do per connection.
+ *
+ * TODO: This is the same param as in the OpenSSL backend, and duplicating it
+ * is clearly not a terribly good thing. This should probably be moved to a
+ * common header file, or the creation of it should be re-thought in case we
+ * want to keep separate precomputed params per SSL backend.
+ */
+static const char file_dh2048[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV\n\
+89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50\n\
+T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb\n\
+zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX\n\
+Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT\n\
+CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
+-----END DH PARAMETERS-----\n";
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize global Secure Transport structures (if any)
+ *
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ *
+ * Another reason to defer this until when in the backend is that Keychains
+ * are SQLite3 backed, and sqlite does not allow access across a fork. See
+ * https://sqlite.org/faq.html#q6 for more information.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * bt_tls_open_server
+ * Attempt to negotiate a secure connection
+ *
+ * Unlike the OpenSSL backend, this function is responsible for loading keys
+ * and certificates for use in the connection. See the comment on be_tls_init
+ * for further reasoning around this.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFArrayRef keychains;
+ CFMutableArrayRef chain;
+ char *dh_buf;
+ int dh_len;
+
+ Assert(!port->ssl);
+
+ if (ssl_keychain_file[0])
+ load_keychain(ssl_keychain_file, &keychains);
+
+ /*
+ * If the ssl_cert_file is prefixed with a keychain reference, we will try
+ * to load a complete identity from the Keychain.
+ */
+ if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains);
+ else
+ {
+ status = load_certificate_file(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": \"%s\"",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ load_key(ssl_key_file, &keys);
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ }
+
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate_file(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": \"%s\"",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API
+ */
+ if (ssl_crl_file[0])
+ ereport(FATAL,
+ (errmsg("CRL files not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ /*
+ * We use kTryAuthenticate here since we don't know which sslmode the
+ * client is using. If we were to use kAlwaysAuthenticate then sslmode
+ * require won't work as intended.
+ */
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * In case the user hasn't configured a DH parameters file, we load a pre-
+ * computed DH parameter to avoid having Secure Transport computing one for
+ * us (which is done by default unless one is set).
+ */
+ dh_buf = NULL;
+ if (ssl_dh_params_file[0])
+ dh_len = load_dh_file(ssl_dh_params_file, &dh_buf);
+ if (!dh_buf || dh_len == 0)
+ {
+ dh_buf = pstrdup(file_dh2048);
+ dh_len = sizeof(dh_buf);
+ }
+
+ load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl);
+
+ /*
+ * Set Tlsv1.2 as the minimum protocol version we allow for the connection
+ */
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ if (status == errSSLWouldBlock)
+ continue;
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+
+ CFStringRef usercert_cn;
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+ port->peer_cn = pstrdup(CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8));
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ *
+ * Loads a private key from the specified filename. Unless the key loads this
+ * will not return but will error out.
+ */
+static void
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+
+ ret = stat(name, &stat_buf);
+ if (ret != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to stat",
+ name)));
+
+ if (!S_ISREG(stat_buf.st_mode))
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": not a regular file",
+ name)));
+
+ /*
+ * Require no public access to the key file. If the file is owned by us,
+ * require mode 0600 or less. If owned by root, require 0640 or less to
+ * allow read access through our gid, or a supplementary gid that allows to
+ * read system-wide certificates.
+ */
+ if ((stat_buf.st_uid == geteuid() && stat_buf.st_mode & (S_IRWXG | S_IRWXO)) ||
+ (stat_buf.st_uid == 0 && stat_buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" has group or world access",
+ name),
+ errdetail("File must have permissions u=rw (0600) or less "
+ "if owned by the database user, or permissions "
+ "u=rw,g=r (0640) or less if owned by root.")));
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ SecItemImportExportKeyParameters params;
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": \"%s\"",
+ name, pg_SSLerrmessage(status))));
+}
+
+/*
+ * load_keychain
+ * Open the specified, and default, Keychain
+ *
+ * Operations on keychains other than the default take the keychain references
+ * in an array. We need to copy a reference to the default keychain into the
+ * array to include it in the search.
+ *
+ * For server applications, we don't want modal dialog boxes opened for
+ * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE)
+ * will turn off all GUI interaction with the user, which may seem like what we
+ * want server side. This however has the side effect to turn off all GUI
+ * elements for all applications until some application calls
+ * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus
+ * remove wanted GUI interaction from another app, or another app might
+ * introduce it for
+ * us.
+ */
+static OSStatus
+load_keychain(char *name, CFArrayRef *keychains)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ SecKeychainRef kc[2];
+
+ if (stat(name, &stat_buf) != 0)
+ return errSecInvalidValue;
+
+ status = SecKeychainOpen(name, &kc[0]);
+ if (status == noErr)
+ {
+ SecKeychainUnlock(kc[0], 0, "", TRUE);
+ /*
+ * We only need to open, and add, the default Keychain in case have a
+ * user keychain opened, else we will pass NULL to any keychain search
+ * which will use the default keychain by.. default.
+ */
+ SecKeychainCopyDefault(&kc[1]);
+ *keychains = CFArrayCreate(NULL, (const void **) kc, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ else
+ elog(LOG, "XXX: could not load keychain \"%s\" : %d", name, status);
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+load_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+/*
+ * load_certificate_file
+ * Extracts a certificate from a PEM file on the filesystem
+ *
+ * TODO: figure out better returncodes
+ */
+static OSStatus
+load_certificate_file(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ /*
+ * If the configured ssl_cert_file filename is set to a non-existing
+ * file, assume it's referencing a Keychain label and attempt to load
+ * the certificate from the Keychain instead.
+ */
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ /*
+ * TODO: Do we want to search keychains for certificates serverside
+ * like we do clientside, or do we want to stick to a single way to
+ * configure the server? Since CRL files aren't supported outside
+ * keychains I guess we need to, but worth a discussion.
+ */
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ return errSecInternalError;
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ return errSecInternalError;
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL, cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ return errSecInternalError;
+}
+
+/*
+ * XXX: Make this file loading and slurping only
+ */
+static int
+load_dh_file(char *filename, char **buf)
+{
+ FILE *dh;
+ struct stat stat_buf;
+ int ret;
+
+ /*
+ * Open the DH file and slurp the contents. If the file doesn't exist it's
+ * not an error, if it can't be opened it is however an error.
+ */
+ ret = stat(filename, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ return 0;
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((dh = AllocateFile(filename, "r")) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+
+ *buf = palloc(stat_buf.st_size);
+ ret = fread(*buf, 1, stat_buf.st_size, dh);
+ FreeFile(dh);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read DH parameters file \"%s\": %m",
+ filename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("DH parameters file \"%s\" is not a regular file",
+ filename)));
+
+ return ret;
+}
+
+/*
+ * load_dh_params
+ * Load the specified DH params for the connection
+ *
+ * Secure Transport requires the DH params to be in DER format, but to be
+ * compatible with the OpenSSL code we also support PEM and convert to DER
+ * before loading. Conversion does rudimentary PEM parsing, if we miss the
+ * data being correct, the Secure Transport API will give an error anyways so
+ * we're just checking basic integrity.
+ *
+ * This function may scribble on the dh parameter so if that's required so stay
+ * intact in the caller, a copy should be sent.
+ */
+#define DH_HEADER "-----BEGIN DH PARAMETERS-----"
+#define DH_FOOTER "-----END DH PARAMETERS-----"
+static void
+load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl)
+{
+ OSStatus status;
+ char *der;
+ int der_len;
+
+ Assert(dh);
+
+ /*
+ * Convert PEM to DER
+ */
+ if (is_pem)
+ {
+ char *head;
+ char *tail;
+ int pem_len = 0;
+
+ if (strstr(dh, DH_HEADER) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, header missing")));
+
+ dh += strlen(DH_HEADER);
+ tail = strstr(dh, DH_FOOTER);
+ if (!tail)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, footer missing")));
+ *tail = '\0';
+
+ /* In order to PEM convert it we need to remove all newlines */
+ head = dh;
+ tail = dh;
+ while (*head != '\0')
+ {
+ if (*head != '\n')
+ {
+ *tail++ = *head++;
+ pem_len++;
+ }
+ else
+ head++;
+ }
+ *tail = '\0';
+
+ der = palloc(pg_b64_dec_len(strlen(dh)) + 1);
+ der_len = pg_b64_decode(dh, strlen(dh), der);
+ der[der_len] = '\0';
+ }
+ else
+ {
+ der = dh;
+ der_len = len;
+ }
+
+ status = SSLSetDiffieHellmanParams(ssl, der, der_len);
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unable to load DH parameters: %s",
+ pg_SSLerrmessage(status))));
+}
+
+/*
+ * This function should return the expected TLS Finished message information
+ * from the client, but there is no API for this in Secure Transport. As a
+ * result, channel binding is not supported by Secure Transport.
+ */
+char *
+be_tls_get_peer_finished(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+/*
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+void
+be_tls_get_version(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (ptr == NULL || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ strlcpy(ptr, "TLSv1.1", len);
+ break;
+ case kTLSProtocol12:
+ strlcpy(ptr, "TLSv1.2", len);
+ break;
+ default:
+ strlcpy(ptr, "unknown", len);
+ break;
+ }
+ }
+}
+
+/*
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ *
+ * Returns NULL in case we weren't able to either get the negotiated cipher, or
+ * translate it into a human readable string.
+ */
+void
+be_tls_get_cipher(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+ const char *cipher_name;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return;
+
+ cipher_name = pg_SSLciphername(cipher);
+ if (cipher_name != NULL)
+ strlcpy(ptr, cipher_name, len);
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ /*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there still is no translation for it in 10.11 El Capitan, so we
+ * maintain our own
+ */
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index eb42ea1a1e..ac1edf9bb8 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -45,6 +45,9 @@ char *ssl_key_file;
char *ssl_ca_file;
char *ssl_crl_file;
char *ssl_dh_params_file;
+#ifdef USE_SECURETRANSPORT
+char *ssl_keychain_file;
+#endif
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d03ba234b5..3f43647227 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -501,6 +501,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -3322,6 +3323,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
{
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
@@ -3569,6 +3582,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SECURITY,
+ gettext_noop("Location of the Keychain file."),
+ NULL
+ },
+ &ssl_keychain_file,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 69f40f04b0..3c20baf424 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,6 +85,7 @@
#ssl_key_file = 'server.key'
#ssl_ca_file = ''
#ssl_crl_file = ''
+#ssl_keychain_file = ''
#password_encryption = md5 # md5 or scram-sha-256
#db_user_namespace = off
#row_security = on
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000000..d3a38e2a5c
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,514 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport_common.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+#ifndef FRONTEND
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement. This
+ * function is currently only used in the libpq backend.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0d28..6d35f47864 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 49cb263110..6adf6d5794 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,19 +191,22 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+ void *ssl;
+ int ssl_buffered;
#endif
} Port;
#ifdef USE_SSL
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
extern int be_tls_init(bool isServerStart);
extern void be_tls_destroy(void);
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 2e7725db21..1323dc2549 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -80,6 +80,9 @@ extern char *ssl_key_file;
extern char *ssl_ca_file;
extern char *ssl_crl_file;
extern char *ssl_dh_params_file;
+#ifdef USE_SECURETRANSPORT
+extern char *ssl_keychain_file;
+#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 f98f773ff0..a1d71a6473 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -856,6 +856,9 @@
/* Define to use OpenSSL for random number generation */
#undef USE_OPENSSL_RANDOM
+/* Define to build with Apple Secure Transport support. (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index b309395f11..5469473d72 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -162,11 +162,17 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
+#else
+#define SSL_LIBRARY "None"
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0bf1e7ef04..fe8662d1b6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,11 @@ else
OBJS += sha2.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -112,7 +117,6 @@ ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
-
distprep: libpq-dist.rc
libpq.rc libpq-dist.rc: libpq.rc.in
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 77eebb0ba1..ede910f250 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -330,6 +330,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000000..b0d74e6809
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1507 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readiblitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *common_name,
+ SecCertificateRef *certificate,
+ CFArrayRef keychains,
+ char *hostname);
+static OSStatus import_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+static OSStatus import_pem(const char *path, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * Exported function to allow application to tell us it's already initialized
+ * Secure Transport and/or libcrypto.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ return;
+}
+
+/*
+ * Begin or continue negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin(conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection(conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs(conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we dont have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ SSLSetPeerDomainName(conn->ssl, conn->pghost, strlen(conn->pghost));
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption(conn->ssl, kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake(conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust(conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ /*
+ * TODO: return a better error code than SSLInternalError
+ */
+ if (!trusted)
+ return errSecInternalError;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize(conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead(conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite(conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite(conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+/*
+ */
+char *
+pgtls_get_finished(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"%s\" is not supported by this build\n"),
+ conn->scram_channel_binding);
+ return NULL;
+}
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"%s\" is not supported by this build\n"),
+ conn->scram_channel_binding);
+ return NULL;
+}
+
+/*
+ * Initialize SSL system, in particular creating the SSL_context object
+ * that will be shared by all SSL-using connections in this process.
+ *
+ * In threadsafe mode, this includes setting up libcrypto callback functions
+ * to do thread locking.
+ *
+ * If the caller has told us (through PQinitOpenSSL) that he's taking care
+ * of libcrypto, we expect that callbacks are already set, and won't try to
+ * override it.
+ *
+ * The conn parameter is only used to be able to pass back an error
+ * message - no connection-local setup is made here.
+ *
+ * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose(conn->ssl);
+ CFRelease(conn->ssl);
+
+ /* TODO: Release any certificates loaded */
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+import_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_certificate_keychain(const char *common_name, SecCertificateRef *certificate,
+ CFArrayRef keychains, char *hostname)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFStringRef host = NULL;
+ CFArrayRef temp;
+ SecPolicyRef ssl_policy;
+ int i;
+
+ /*
+ * Make sure the user didn't just specify the keychain prefix as the
+ * certificate config. The passed certificate will have the keychain
+ * prefix stripped so in that case the string is expected to be empty.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ /*
+ * If we didn't get a set of Keychains passed, skip adding it to the
+ * dictionary thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * Specifying a hostname requires it to match the hostname in the leaf
+ * certificate.
+ */
+ if (hostname)
+ host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8);
+ ssl_policy = SecPolicyCreateSSL(true, host);
+ CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label has been reported to not work (revisions of 10.12), we request
+ * all and find the one we want. Copy all the results to a temp array and
+ * scan it for the certificate we are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i);
+
+ if (search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ *certificate = (SecCertificateRef) CFRetain(search_cert);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(ssl_policy);
+ CFRelease(query);
+ CFRelease(cert);
+ if (host)
+ CFRelease(host);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, char *passphrase, CFArrayRef *certificate)
+{
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+ struct stat buf;
+
+ if (!path || strlen(path) == 0)
+ return errSecInvalidValue;
+
+ if (stat(path, &buf) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return -1;
+
+ return errSecInvalidValue;
+ }
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInvalidValue;
+
+ certdata = malloc(buf.st_size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, buf.st_size, fp) != buf.st_size)
+ {
+ fclose(fp);
+ free(certdata);
+ return errSSLBadCert;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, buf.st_size);
+ free(certdata);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecAllocate;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ return SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ bool have_homedir;
+ bool cert_from_file = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecCertificateRef root_ref[1];
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+ SecKeychainRef kcref[2];
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching. If no additional keychain is specified we don't need to open
+ * the default keychain and pass to searches since Secure Transport will
+ * use the default when passing NULL instead of an array of Keychain refs.
+ */
+ if (conn->keychain)
+ {
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr && kcref[0] != NULL)
+ {
+ SecKeychainStatus kcstatus;
+ status = SecKeychainGetStatus(kcref[0], &kcstatus);
+ if (status == noErr)
+ {
+ switch (kcstatus)
+ {
+ /*
+ * If the Keychain is already unlocked, readable or
+ * writeable, we don't need to do more. If not, try to
+ * unlock it.
+ */
+ case kSecUnlockStateStatus:
+ case kSecReadPermStatus:
+ case kSecWritePermStatus:
+ break;
+ default:
+ /* XXX: fix passphrase param */
+ SecKeychainUnlock(kcref[0], 0, "", TRUE);
+ break;
+ }
+ }
+
+ /*
+ * We only need to open, and add, the default Keychain in case
+ * have a user keychain opened, else we will pass NULL to any
+ * keychain search which will use the default keychain by..
+ * default.
+ */
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the files
+ * could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Try to load the root cert from either a user defined keychain or the
+ * default Keychain in case none is specified
+ */
+ if (conn->sslrootcert &&
+ pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ root_ref[0] = NULL;
+ strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL);
+
+ if (root_ref[0])
+ conn->st_rootcert = (void *) CFArrayCreate(NULL,
+ (const void **) root_ref,
+ 1, &kCFTypeArrayCallBacks);
+ }
+
+ if (!conn->st_rootcert)
+ {
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+ }
+
+ /*
+ * If the sslcert config is prefixed with a keychain identifier, the cert
+ * must be located in either the default or the specified keychain.
+ */
+ if (conn->sslcert &&
+ pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_identity_keychain(fnbuf, &identity, keychains);
+
+ if (identity)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ /*
+ * No prefix on the sslcert config, the certificate must thus reside in a
+ * file on disk.
+ */
+ else
+ {
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, cert_array);
+ if (status != noErr)
+ {
+ if (status == -1)
+ return noErr;
+
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ cert_from_file = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in
+ * order to create the identity.
+ */
+
+ /*
+ * The sslkey config is prefixed with keychain: indicating that the
+ * key should be loaded from Keychain instead of the filesystem.
+ * Search for the private key matching the cert_ref in the opened
+ * Keychains. If found, we get the identity returned.
+ */
+ if (conn->sslkey &&
+ pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref,
+ &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the key to be
+ * loaded from that file.
+ */
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+ }
+ }
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ if (key_ref)
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ }
+
+ if (!identity)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create identity from certificate/key\n"));
+ return errSecInvalidValue;
+ }
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (cert_from_file)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate(conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+int
+PQsslInUse(PGconn *conn)
+{
+ if (!conn)
+ return 0;
+ return conn->ssl_in_use;
+}
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher(conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion(conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState(conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 4e354098b3..9cf7d44ab3 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -80,6 +80,14 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_SECURETRANSPORT
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#undef Size
+#undef uint64
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -470,8 +478,18 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+
+ SSLContextRef ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 4886e901d0..872415a42a 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -123,6 +123,20 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### Keychains
+
+ifeq ($(with_securetransport),yes)
+
+KEYCHAINS := ssl/client.keychain
+
+# This target generates all Keychains
+keychains: $(KEYCHAINS)
+
+PWD=$(shell pwd)
+ssl/client.keychain: ssl/client.crt ssl/client.key
+ certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p=
+endif
+
.PHONY: sslfiles-clean
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
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index 02f8028b2b..c645ab2bc4 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -159,7 +159,14 @@ sub switch_server_cert
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";
+ if (check_pg_config("#define USE_SECURETRANSPORT 1"))
+ {
+ print $sslconf "ssl_crl_file=''\n";
+ }
+ else
+ {
+ print $sslconf "ssl_crl_file='root+client.crl'\n";
+ }
close $sslconf;
$node->reload;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 28837a1391..a858344329 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -2,7 +2,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 40;
+use Test::More;
use ServerSetup;
use File::Copy;
@@ -21,6 +21,27 @@ my $common_connstr;
copy("ssl/client.key", "ssl/client_tmp.key");
chmod 0600, "ssl/client_tmp.key";
+# macOS Secure Transport does not support loading CRL files, so skip CRL
+# tests when built with Secure Transport support.
+my $supports_crl_files = ! check_pg_config("#define USE_SECURETRANSPORT 1");
+
+# Test for support of Secure Transport Keychain secure archives
+# This check is currently the same as $supports_crl_files, but clarity when
+# reading code is more important than avoiding the use of an extra variable
+my $supports_keychains = check_pg_config("#define USE_SECURETRANSPORT 1");
+
+#### Setup
+
+if ($supports_keychains)
+{
+ plan tests => 43;
+}
+else
+{
+ plan tests => 40;
+}
+
+
#### Part 0. Set up the server.
note "setting up data directory";
@@ -91,18 +112,37 @@ test_connect_ok($common_connstr,
note "testing sslcrl option with a non-revoked cert";
-# 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");
-
-# 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");
-
-# 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"
-);
+if ($supports_crl_files)
+{
+ # 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");
+
+ # 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"
+ );
+
+ # 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");
+}
+else
+{
+ # Invalid CRL filename is the same as no CRL, succeeds
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid");
+
+ # With the correct CRL, succeeds (this cert is not revoked)
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+
+ # A CRL belonging to a different CA is not accepted, this should fail iff
+ # CRL files are supported
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl");
+}
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
@@ -176,9 +216,18 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok($common_connstr,
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca");
-test_connect_fails($common_connstr,
-"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
-);
+if ($supports_crl_files)
+{
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+}
+else
+{
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+}
### Part 2. Server-side tests.
###
@@ -199,10 +248,20 @@ test_connect_ok($common_connstr,
test_connect_fails($common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key");
-# revoked client cert
-test_connect_fails($common_connstr,
-"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
-);
+if ($supports_crl_files)
+{
+ # revoked client cert
+ test_connect_fails($common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
+ );
+}
+else
+{
+ # revoked client cert
+ test_connect_ok($common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
+ );
+}
# 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');
@@ -213,5 +272,15 @@ test_connect_ok($common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt");
test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt");
+if ($supports_keychains)
+{
+ # empty keychain
+ test_connect_fails("user=ssltestuser keychain=invalid");
+
+ # correct client cert in keychain with and without proper label
+ test_connect_fails("user=ssltestuser keychain=ssl/client.keychain");
+ test_connect_ok("user=ssltestuser sslcert=ssltestuser keychain=ssl/client.keychain");
+}
+
# clean up
unlink "ssl/client_tmp.key";
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 67c1409a6e..03a5533e29 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -15,6 +15,9 @@ my $SERVERHOSTADDR = '127.0.0.1';
my $supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+my $supports_tls_unique =
+ ! check_pg_config("#define USE_SECURETRANSPORT 1");
+
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -38,14 +41,27 @@ $ENV{PGPASSWORD} = "pass";
$common_connstr =
"user=ssltestuser dbname=trustdb sslmode=require hostaddr=$SERVERHOSTADDR";
-# Default settings
-test_connect_ok($common_connstr, '',
- "SCRAM authentication with default channel binding");
-
# Channel binding settings
-test_connect_ok($common_connstr,
- "scram_channel_binding=tls-unique",
- "SCRAM authentication with tls-unique as channel binding");
+if ($supports_tls_unique)
+{
+ # Default settings
+ test_connect_ok($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ test_connect_ok($common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
+else
+{
+ # Default settings
+ test_connect_fails($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ test_connect_fails($common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
test_connect_ok($common_connstr,
"scram_channel_binding=''",
"SCRAM authentication without channel binding");
--
2.14.1.145.gb3622a4ee
0002-Allow-running-SSL-tests-against-different-binar-v4.patchapplication/octet-stream; name=0002-Allow-running-SSL-tests-against-different-binar-v4.patchDownload
From 720cac3ae8907d9bdc8eb09cbe2117c07d4e0053 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 21 Jan 2018 23:41:35 +0100
Subject: [PATCH 2/3] Allow running SSL tests against different binaries
In order to be able to test work on SSL frontends, or backends,
against a stock installation, allow a pgpath setting to override
the path to the binaries. The SSLTEST_SERVER_BIN environment var
will be used, if set, for which set of server binaries to run.
SSLTEST_SERVER_BIN=/path/bin/ make -C src/test/ssl/ check
This is intended for testing during development in order to test
a new frontend against a known backend for example.
---
src/test/perl/PostgresNode.pm | 77 ++++++++++++++++++++++++++++++++----------
src/test/ssl/t/001_ssltests.pl | 2 +-
2 files changed, 60 insertions(+), 19 deletions(-)
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 1d5ac4ee35..41c980ee0c 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -133,7 +133,7 @@ INIT
=over
-=item PostgresNode::new($class, $name, $pghost, $pgport)
+=item PostgresNode::new($class, $name, $pghost, $pgport, $pgpath)
Create a new PostgresNode instance. Does not initdb or start it.
@@ -144,12 +144,14 @@ of finding port numbers, registering instances for cleanup, etc.
sub new
{
- my ($class, $name, $pghost, $pgport) = @_;
+ my ($class, $name, $pghost, $pgport, $pgpath) = @_;
my $testname = basename($0);
$testname =~ s/\.[^.]+$//;
+ $pgpath = '' unless defined $pgpath;
my $self = {
_port => $pgport,
_host => $pghost,
+ _pgpath => $pgpath,
_basedir => "$TestLib::tmp_check/t_${testname}_${name}_data",
_name => $name,
_logfile => "$TestLib::log_path/${testname}_${name}.log" };
@@ -227,6 +229,21 @@ sub name
=pod
+=item $node->pgpath()
+
+Path to the PostgreSQL binaries to run, if empty then the installation in PATH
+is assumed.
+
+=cut
+
+sub pgpath
+{
+ my ($self) = @_;
+ return $self->{_pgpath};
+}
+
+=pod
+
=item $node->logfile()
Path to the PostgreSQL log file for this instance.
@@ -327,6 +344,7 @@ sub info
my $_info = '';
open my $fh, '>', \$_info or die;
print $fh "Name: " . $self->name . "\n";
+ print $fh "Binary directory: " . $self->pgpath . "\n";
print $fh "Data directory: " . $self->data_dir . "\n";
print $fh "Backup directory: " . $self->backup_dir . "\n";
print $fh "Archive directory: " . $self->archive_dir . "\n";
@@ -402,6 +420,7 @@ sub init
my $port = $self->port;
my $pgdata = $self->data_dir;
my $host = $self->host;
+ my $pgpath = $self->pgpath;
$params{allows_streaming} = 0 unless defined $params{allows_streaming};
$params{has_archiving} = 0 unless defined $params{has_archiving};
@@ -409,7 +428,7 @@ sub init
mkdir $self->backup_dir;
mkdir $self->archive_dir;
- TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+ TestLib::system_or_bail($pgpath . 'initdb', '-D', $pgdata, '-A', 'trust', '-N',
@{ $params{extra} });
TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata);
@@ -504,9 +523,10 @@ sub backup
my $backup_path = $self->backup_dir . '/' . $backup_name;
my $port = $self->port;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "# Taking pg_basebackup $backup_name from node \"$name\"\n";
- TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-p', $port,
+ TestLib::system_or_bail($pgpath . 'pg_basebackup', '-D', $backup_path, '-p', $port,
'--no-sync');
print "# Backup finished\n";
}
@@ -662,8 +682,9 @@ sub start
my $pgdata = $self->data_dir;
my $name = $self->name;
BAIL_OUT("node \"$name\" is already running") if defined $self->{_pid};
+ my $pgpath = $self->pgpath;
print("### Starting node \"$name\"\n");
- my $ret = TestLib::system_log('pg_ctl', '-D', $self->data_dir, '-l',
+ my $ret = TestLib::system_log($pgpath . 'pg_ctl', '-D', $self->data_dir, '-l',
$self->logfile, 'start');
if ($ret != 0)
@@ -694,10 +715,11 @@ sub stop
my $port = $self->port;
my $pgdata = $self->data_dir;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
$mode = 'fast' unless defined $mode;
return unless defined $self->{_pid};
print "### Stopping node \"$name\" using mode $mode\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-m', $mode, 'stop');
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-m', $mode, 'stop');
$self->_update_pid(0);
}
@@ -715,8 +737,10 @@ sub reload
my $port = $self->port;
my $pgdata = $self->data_dir;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Reloading node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, 'reload');
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, 'reload');
+
}
=pod
@@ -734,8 +758,9 @@ sub restart
my $pgdata = $self->data_dir;
my $logfile = $self->logfile;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Restarting node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-l', $logfile,
'restart');
$self->_update_pid(1);
}
@@ -755,8 +780,9 @@ sub promote
my $pgdata = $self->data_dir;
my $logfile = $self->logfile;
my $name = $self->name;
+ my $pgpath = $self->pgpath;
print "### Promoting node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ TestLib::system_or_bail($pgpath . 'pg_ctl', '-D', $pgdata, '-l', $logfile,
'promote');
}
@@ -860,12 +886,15 @@ sub _update_pid
=pod
-=item PostgresNode->get_new_node(node_name)
+=item PostgresNode->get_new_node(node_name, pgpath)
Build a new object of class C<PostgresNode> (or of a subclass, if you have
one), assigning a free port number. Remembers the node, to prevent its port
number from being reused for another node, and to ensure that it gets
-shut down when the test script exits.
+shut down when the test script exits. If the pgpath parameter is defined then
+the path is tested for containing a PostgreSQL installation with the correct
+permissions for execution.
+
You should generally use this instead of C<PostgresNode::new(...)>.
@@ -877,8 +906,8 @@ which can only create objects of class C<PostgresNode>.
sub get_new_node
{
my $class = 'PostgresNode';
- $class = shift if 1 < scalar @_;
- my $name = shift;
+ $class = shift if 2 < scalar @_;
+ my ($name, $pgpath) = @_;
my $found = 0;
my $port = $last_port_assigned;
@@ -921,8 +950,19 @@ sub get_new_node
print "# Found free port $port\n";
+ # If a separate path to the binaries was specified then we do rudimentary
+ # checks for a sane installation. If an executable copy of initdb is there
+ # then let's go ahead.
+ if (defined $pgpath)
+ {
+ $pgpath =~ s!/*$!/!;
+ die "initdb missing in $pgpath" unless -x $pgpath . 'initdb';
+
+ print "# Found PostgreSQL installation in $pgpath\n";
+ }
+
# Lock port number found by creating a new node
- my $node = $class->new($name, $test_pghost, $port);
+ my $node = $class->new($name, $test_pghost, $port, $pgpath);
# Add node to list of nodes
push(@all_nodes, $node);
@@ -1129,8 +1169,9 @@ sub psql
my $stderr = $params{stderr};
my $timeout = undef;
my $timeout_exception = 'psql timed out';
+ my $pgpath = $self->pgpath;
my @psql_params =
- ('psql', '-XAtq', '-d', $self->connstr($dbname), '-f', '-');
+ ($pgpath . 'psql', '-XAtq', '-d', $self->connstr($dbname), '-f', '-');
# If the caller wants an array and hasn't passed stdout/stderr
# references, allocate temporary ones to capture them so we
@@ -1272,11 +1313,11 @@ Returns 1 if successful, 0 if timed out.
sub poll_query_until
{
- my ($self, $dbname, $query, $expected) = @_;
+ my ($self, $dbname, $query, $expected, $pgpath) = @_;
$expected = 't' unless defined($expected); # default value
- my $cmd = [ 'psql', '-XAt', '-c', $query, '-d', $self->connstr($dbname) ];
+ my $cmd = [ $pgpath . 'psql', '-XAt', '-c', $query, '-d', $self->connstr($dbname) ];
my ($stdout, $stderr);
my $max_attempts = 180 * 10;
my $attempts = 0;
@@ -1516,7 +1557,7 @@ sub wait_for_catchup
. $self->name . "\n";
my $query =
qq[SELECT $lsn_expr <= ${mode}_lsn FROM pg_catalog.pg_stat_replication WHERE application_name = '$standby_name';];
- $self->poll_query_until('postgres', $query)
+ $self->poll_query_until('postgres', $query, $self->pgpath)
or die "timed out waiting for catchup";
print "done\n";
}
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index a858344329..6061838a20 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -45,7 +45,7 @@ else
#### Part 0. Set up the server.
note "setting up data directory";
-my $node = get_new_node('master');
+my $node = get_new_node('master', $ENV{SSLTEST_SERVER_BIN});
$node->init;
# PGHOST is enforced here to set up the node, subsequent connections
--
2.14.1.145.gb3622a4ee
0003-Allow-spaces-in-connectionstrings-v4.patchapplication/octet-stream; name=0003-Allow-spaces-in-connectionstrings-v4.patchDownload
From b8cefd9bfd46ab723b7044fdceb007a4d02ca65b Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 21 Jan 2018 23:42:20 +0100
Subject: [PATCH 3/3] Allow spaces in connectionstrings
Connectionstrings can have items with spaces in them, wrapped in
quotes. The tests however ran a SELECT '$connstr' upon connection
which broke on the embedded quotes. Use doubledollar quotes on the
connstr to protect against this. This was hit during the test of
the macOS Secure Transport patch, but is independent of it.
---
src/test/ssl/ServerSetup.pm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index c645ab2bc4..a419d29c94 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -42,7 +42,7 @@ sub run_test_psql
my $logstring = $_[1];
my $cmd = [
- 'psql', '-X', '-A', '-t', '-c', "SELECT 'connected with $connstr'",
+ 'psql', '-X', '-A', '-t', '-c', "SELECT \$\$connected with $connstr\$\$",
'-d', "$connstr" ];
my $result = run_log($cmd);
--
2.14.1.145.gb3622a4ee
On 1/21/18 18:08, Daniel Gustafsson wrote:
As per before, my patch for running tests against another set of binaries is
included as well as a fix for connstrings with spaces, but with the recent
hacking by Peter I assume this is superfluous. It was handy for development so
I’ve kept it around though.
0002-Allow-running-SSL-tests-against-different-binar-v4.patch should be
obsoleted by f5da5683a86e9fc42fdf3eae2da8b096bda76a8a.
0003-Allow-spaces-in-connectionstrings-v4.patch looks reasonable, but
I'm not sure what circumstance is triggering that. Is it specific to
your implementation?
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 23 Jan 2018, at 18:20, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
On 1/21/18 18:08, Daniel Gustafsson wrote:
As per before, my patch for running tests against another set of binaries is
included as well as a fix for connstrings with spaces, but with the recent
hacking by Peter I assume this is superfluous. It was handy for development so
I’ve kept it around though.0002-Allow-running-SSL-tests-against-different-binar-v4.patch should be
obsoleted by f5da5683a86e9fc42fdf3eae2da8b096bda76a8a.
Yes.
0003-Allow-spaces-in-connectionstrings-v4.patch looks reasonable, but
I'm not sure what circumstance is triggering that. Is it specific to
your implementation?
It’s not specific to the implementation per se, but it increases the likelyhood
of hitting it. In order to load certificates from Keychains the cert common
name must be specified in the connstr, when importing the testfiles into
keychains I ran into it for example src/test/ssl/client_ca.config.
cheers ./daniel
On 1/23/18 14:59, Daniel Gustafsson wrote:
It’s not specific to the implementation per se, but it increases the likelyhood
of hitting it. In order to load certificates from Keychains the cert common
name must be specified in the connstr, when importing the testfiles into
keychains I ran into it for example src/test/ssl/client_ca.config.
The change is
- 'psql', '-X', '-A', '-t', '-c', "SELECT 'connected with $connstr'",
+ 'psql', '-X', '-A', '-t', '-c', "SELECT \$\$connected with $connstr\$\$",
So the problem must have been a single quote in the connstr.
That can surely happen, but then so can having a $$. So without a
concrete example, I'm not sure how to proceed.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 23 Jan 2018, at 20:59, Daniel Gustafsson <daniel@yesql.se> wrote:
On 23 Jan 2018, at 18:20, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
On 1/21/18 18:08, Daniel Gustafsson wrote:
As per before, my patch for running tests against another set of binaries is
included as well as a fix for connstrings with spaces, but with the recent
hacking by Peter I assume this is superfluous. It was handy for development so
I’ve kept it around though.0002-Allow-running-SSL-tests-against-different-binar-v4.patch should be
obsoleted by f5da5683a86e9fc42fdf3eae2da8b096bda76a8a.Yes.
On the note of patches made obsolete, the attached patch is rebased and updated
for the recent commits that moved common SSL code into shared files.
cheers ./daniel
Attachments:
0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v5.patchapplication/octet-stream; name=0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v5.patchDownload
From 8ca4571baf3811e8da5c73f362f8666fd0178fbe Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 21 Jan 2018 23:40:04 +0100
Subject: [PATCH 1/3] WIP: Add support for Apple Secure Transport SSL library
This adds frontend and backend support for the Secure Transport SSL
library on macOS, but currently lacks SCRAM support. Thus, the patch
is not in a final state but is provided for hacking and testing.
---
configure | 57 +
configure.in | 14 +
src/Makefile.global.in | 1 +
src/backend/Makefile | 6 +-
src/backend/libpq/Makefile | 5 +
src/backend/libpq/be-secure-securetransport.c | 1319 +++++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 25 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/common/securetransport.h | 514 ++++++++
src/include/common/sha2.h | 4 +-
src/include/libpq/libpq-be.h | 17 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 12 +-
src/interfaces/libpq/Makefile | 6 +-
src/interfaces/libpq/fe-connect.c | 6 +
src/interfaces/libpq/fe-secure-securetransport.c | 1499 ++++++++++++++++++++++
src/interfaces/libpq/libpq-int.h | 22 +-
src/test/ssl/Makefile | 14 +
src/test/ssl/ServerSetup.pm | 9 +-
src/test/ssl/t/001_ssltests.pl | 109 +-
src/test/ssl/t/002_scram.pl | 30 +-
23 files changed, 3639 insertions(+), 40 deletions(-)
create mode 100644 src/backend/libpq/be-secure-securetransport.c
create mode 100644 src/include/common/securetransport.h
create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c
diff --git a/configure b/configure
index 7dcca506f8..b9db714127 100755
--- a/configure
+++ b/configure
@@ -707,6 +707,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
krb_srvtab
with_python
@@ -835,6 +836,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1529,6 +1531,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -5995,6 +5998,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
#
# SELinux
#
@@ -10978,6 +11016,25 @@ else
fi
+fi
+
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.in b/configure.in
index 4d26034579..20f3d55c07 100644
--- a/configure.in
+++ b/configure.in
@@ -703,6 +703,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
#
# SELinux
#
@@ -1225,6 +1234,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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index d980f81046..9f60a3dd0d 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_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_libxml = @with_libxml@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a28267339..fc11b7271c 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -49,6 +49,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LDFLAGS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-schemapg postgres $(POSTGRES_IMP)
@@ -58,7 +62,7 @@ ifneq ($(PORTNAME), win32)
ifneq ($(PORTNAME), aix)
postgres: $(OBJS)
- $(CC) $(CFLAGS) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@
+ $(CC) $(CFLAGS) -framework CoreFoundation -framework Security $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@
endif
endif
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 7fa2b02743..3a835235b9 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000000..ff8af56047
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1319 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - Load DH keys from file
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future).
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interested to investigate.
+ *
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "common/base64.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#define bool pg_bool
+#include <Security/cssmerr.h>
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef bool
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_bool bool
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err. Since we
+ * may have encountered an error due to memory pressure, we don't want to rely
+ * on dynamically allocating memory for this error message.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+/*
+ * SecIdentityCreate is not an exported Secure Transport API. There is however
+ * no exported Secure Transport function that can create an identity from a
+ * SecCertificateRef and a SecKeyRef without having either of them in a
+ * Keychain. This function is commonly used in open source projects (such as
+ * ffmpeg and mono f.e), but finding an alternative is a TODO.
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static void load_key(char *name, CFArrayRef *out);
+static OSStatus load_keychain(char *name, CFArrayRef *keychains);
+static OSStatus load_certificate_file(char *name, CFArrayRef *cert_array);
+static OSStatus load_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+
+static int load_dh_file(char *filename, char **buf);
+static void load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl);
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize global Secure Transport structures (if any)
+ *
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ *
+ * Another reason to defer this until when in the backend is that Keychains
+ * are SQLite3 backed, and sqlite does not allow access across a fork. See
+ * https://sqlite.org/faq.html#q6 for more information.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * bt_tls_open_server
+ * Attempt to negotiate a secure connection
+ *
+ * Unlike the OpenSSL backend, this function is responsible for loading keys
+ * and certificates for use in the connection. See the comment on be_tls_init
+ * for further reasoning around this.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFArrayRef keychains;
+ CFMutableArrayRef chain;
+ char *dh_buf;
+ int dh_len;
+
+ Assert(!port->ssl);
+
+ if (ssl_keychain_file[0])
+ load_keychain(ssl_keychain_file, &keychains);
+
+ /*
+ * If the ssl_cert_file is prefixed with a keychain reference, we will try
+ * to load a complete identity from the Keychain.
+ */
+ if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains);
+ else
+ {
+ status = load_certificate_file(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": \"%s\"",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ load_key(ssl_key_file, &keys);
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ }
+
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate_file(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": \"%s\"",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API
+ */
+ if (ssl_crl_file[0])
+ ereport(FATAL,
+ (errmsg("CRL files not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ /*
+ * We use kTryAuthenticate here since we don't know which sslmode the
+ * client is using. If we were to use kAlwaysAuthenticate then sslmode
+ * require won't work as intended.
+ */
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * In case the user hasn't configured a DH parameters file, we load a pre-
+ * computed DH parameter to avoid having Secure Transport computing one for
+ * us (which is done by default unless one is set).
+ */
+ dh_buf = NULL;
+ if (ssl_dh_params_file[0])
+ dh_len = load_dh_file(ssl_dh_params_file, &dh_buf);
+ if (!dh_buf || dh_len == 0)
+ {
+ dh_buf = pstrdup(FILE_DH2048);
+ dh_len = sizeof(FILE_DH2048);
+ }
+
+ load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl);
+
+ /*
+ * Set Tlsv1.2 as the minimum protocol version we allow for the connection
+ */
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ if (status == errSSLWouldBlock)
+ continue;
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+
+ CFStringRef usercert_cn;
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+ port->peer_cn = pstrdup(CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8));
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ *
+ * Loads a private key from the specified filename. Unless the key loads this
+ * will not return but will error out.
+ */
+static void
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+
+ if (!check_ssl_key_file_permissions(name, true))
+ return;
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ /*
+ * check_ssl_key_file_permissions() has already checked the file for
+ * existence and correct permissions, but we still need to stat it to
+ * get the filesize.
+ */
+ stat(name, &stat_buf);
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ SecItemImportExportKeyParameters params;
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": \"%s\"",
+ name, pg_SSLerrmessage(status))));
+}
+
+/*
+ * load_keychain
+ * Open the specified, and default, Keychain
+ *
+ * Operations on keychains other than the default take the keychain references
+ * in an array. We need to copy a reference to the default keychain into the
+ * array to include it in the search.
+ *
+ * For server applications, we don't want modal dialog boxes opened for
+ * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE)
+ * will turn off all GUI interaction with the user, which may seem like what we
+ * want server side. This however has the side effect to turn off all GUI
+ * elements for all applications until some application calls
+ * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus
+ * remove wanted GUI interaction from another app, or another app might
+ * introduce it for
+ * us.
+ */
+static OSStatus
+load_keychain(char *name, CFArrayRef *keychains)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ SecKeychainRef kc[2];
+
+ if (stat(name, &stat_buf) != 0)
+ return errSecInvalidValue;
+
+ status = SecKeychainOpen(name, &kc[0]);
+ if (status == noErr)
+ {
+ SecKeychainUnlock(kc[0], 0, "", TRUE);
+ /*
+ * We only need to open, and add, the default Keychain in case have a
+ * user keychain opened, else we will pass NULL to any keychain search
+ * which will use the default keychain by.. default.
+ */
+ SecKeychainCopyDefault(&kc[1]);
+ *keychains = CFArrayCreate(NULL, (const void **) kc, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ else
+ elog(LOG, "XXX: could not load keychain \"%s\" : %d", name, status);
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+load_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+/*
+ * load_certificate_file
+ * Extracts a certificate from a PEM file on the filesystem
+ *
+ * TODO: figure out better returncodes
+ */
+static OSStatus
+load_certificate_file(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ /*
+ * If the configured ssl_cert_file filename is set to a non-existing
+ * file, assume it's referencing a Keychain label and attempt to load
+ * the certificate from the Keychain instead.
+ */
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ /*
+ * TODO: Do we want to search keychains for certificates serverside
+ * like we do clientside, or do we want to stick to a single way to
+ * configure the server? Since CRL files aren't supported outside
+ * keychains I guess we need to, but worth a discussion.
+ */
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ return errSecInternalError;
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ return errSecInternalError;
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL, cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ return errSecInternalError;
+}
+
+/*
+ * XXX: Make this file loading and slurping only
+ */
+static int
+load_dh_file(char *filename, char **buf)
+{
+ FILE *dh;
+ struct stat stat_buf;
+ int ret;
+
+ /*
+ * Open the DH file and slurp the contents. If the file doesn't exist it's
+ * not an error, if it can't be opened it is however an error.
+ */
+ ret = stat(filename, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ return 0;
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((dh = AllocateFile(filename, "r")) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+
+ *buf = palloc(stat_buf.st_size);
+ ret = fread(*buf, 1, stat_buf.st_size, dh);
+ FreeFile(dh);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read DH parameters file \"%s\": %m",
+ filename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("DH parameters file \"%s\" is not a regular file",
+ filename)));
+
+ return ret;
+}
+
+/*
+ * load_dh_params
+ * Load the specified DH params for the connection
+ *
+ * Secure Transport requires the DH params to be in DER format, but to be
+ * compatible with the OpenSSL code we also support PEM and convert to DER
+ * before loading. Conversion does rudimentary PEM parsing, if we miss the
+ * data being correct, the Secure Transport API will give an error anyways so
+ * we're just checking basic integrity.
+ *
+ * This function may scribble on the dh parameter so if that's required so stay
+ * intact in the caller, a copy should be sent.
+ */
+#define DH_HEADER "-----BEGIN DH PARAMETERS-----"
+#define DH_FOOTER "-----END DH PARAMETERS-----"
+static void
+load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl)
+{
+ OSStatus status;
+ char *der;
+ int der_len;
+
+ Assert(dh);
+
+ /*
+ * Convert PEM to DER
+ */
+ if (is_pem)
+ {
+ char *head;
+ char *tail;
+ int pem_len = 0;
+
+ if (strstr(dh, DH_HEADER) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, header missing")));
+
+ dh += strlen(DH_HEADER);
+ tail = strstr(dh, DH_FOOTER);
+ if (!tail)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, footer missing")));
+ *tail = '\0';
+
+ /* In order to PEM convert it we need to remove all newlines */
+ head = dh;
+ tail = dh;
+ while (*head != '\0')
+ {
+ if (*head != '\n')
+ {
+ *tail++ = *head++;
+ pem_len++;
+ }
+ else
+ head++;
+ }
+ *tail = '\0';
+
+ der = palloc(pg_b64_dec_len(strlen(dh)) + 1);
+ der_len = pg_b64_decode(dh, strlen(dh), der);
+ der[der_len] = '\0';
+ }
+ else
+ {
+ der = dh;
+ der_len = len;
+ }
+
+ status = SSLSetDiffieHellmanParams(ssl, der, der_len);
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unable to load DH parameters: %s",
+ pg_SSLerrmessage(status))));
+}
+
+/*
+ * This function should return the expected TLS Finished message information
+ * from the client, but there is no API for this in Secure Transport. As a
+ * result, channel binding is not supported by Secure Transport.
+ */
+char *
+be_tls_get_peer_finished(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+/*
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+void
+be_tls_get_version(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (ptr == NULL || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ strlcpy(ptr, "TLSv1.1", len);
+ break;
+ case kTLSProtocol12:
+ strlcpy(ptr, "TLSv1.2", len);
+ break;
+ default:
+ strlcpy(ptr, "unknown", len);
+ break;
+ }
+ }
+}
+
+/*
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ *
+ * Returns NULL in case we weren't able to either get the negotiated cipher, or
+ * translate it into a human readable string.
+ */
+void
+be_tls_get_cipher(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+ const char *cipher_name;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return;
+
+ cipher_name = pg_SSLciphername(cipher);
+ if (cipher_name != NULL)
+ strlcpy(ptr, cipher_name, len);
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ /*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there still is no translation for it in 10.11 El Capitan, so we
+ * maintain our own
+ */
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 76c0a9e39b..5c292150aa 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -45,6 +45,9 @@ char *ssl_key_file;
char *ssl_ca_file;
char *ssl_crl_file;
char *ssl_dh_params_file;
+#ifdef USE_SECURETRANSPORT
+char *ssl_keychain_file;
+#endif
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5884fa905e..a694d218b0 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -501,6 +501,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -3324,6 +3325,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
{
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
@@ -3571,6 +3584,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the Keychain file."),
+ NULL
+ },
+ &ssl_keychain_file,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index abffde6b2b..4f65250ed1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -104,6 +104,7 @@
#ssl_prefer_server_ciphers = on
#ssl_ecdh_curve = 'prime256v1'
#ssl_dh_params_file = ''
+#ssl_keychain_file = ''
#------------------------------------------------------------------------------
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000000..d3a38e2a5c
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,514 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport_common.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+#ifndef FRONTEND
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement. This
+ * function is currently only used in the libpq backend.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0d28..6d35f47864 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 584f794b9e..800dc56a61 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,12 +191,15 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+ void *ssl;
+ int ssl_buffered;
#endif
} Port;
@@ -214,7 +225,7 @@ CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
/*
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 255222acd7..9cc91323f6 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -80,6 +80,9 @@ extern char *ssl_key_file;
extern char *ssl_ca_file;
extern char *ssl_crl_file;
extern char *ssl_dh_params_file;
+#ifdef USE_SECURETRANSPORT
+extern char *ssl_keychain_file;
+#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 f98f773ff0..a1d71a6473 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -856,6 +856,9 @@
/* Define to use OpenSSL for random number generation */
#undef USE_OPENSSL_RANDOM
+/* Define to build with Apple Secure Transport support. (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index b309395f11..5469473d72 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -162,11 +162,17 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
+#else
+#define SSL_LIBRARY "None"
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0bf1e7ef04..fe8662d1b6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,11 @@ else
OBJS += sha2.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -112,7 +117,6 @@ ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
-
distprep: libpq-dist.rc
libpq.rc libpq-dist.rc: libpq.rc.in
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 77eebb0ba1..ede910f250 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -330,6 +330,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000000..e8e1d68bcf
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1499 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readiblitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *common_name,
+ SecCertificateRef *certificate,
+ CFArrayRef keychains,
+ char *hostname);
+static OSStatus import_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+static OSStatus import_pem(const char *path, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * Exported function to allow application to tell us it's already initialized
+ * Secure Transport and/or libcrypto.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ return;
+}
+
+/*
+ * Begin or continue negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin(conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection(conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs(conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we dont have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ SSLSetPeerDomainName(conn->ssl, conn->pghost, strlen(conn->pghost));
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption(conn->ssl, kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake(conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust(conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ /*
+ * TODO: return a better error code than SSLInternalError
+ */
+ if (!trusted)
+ return errSecInternalError;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize(conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead(conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite(conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite(conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+/*
+ */
+char *
+pgtls_get_finished(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"%s\" is not supported by this build\n"),
+ conn->scram_channel_binding);
+ return NULL;
+}
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"%s\" is not supported by this build\n"),
+ conn->scram_channel_binding);
+ return NULL;
+}
+
+/*
+ * Initialize SSL system, in particular creating the SSL_context object
+ * that will be shared by all SSL-using connections in this process.
+ *
+ * In threadsafe mode, this includes setting up libcrypto callback functions
+ * to do thread locking.
+ *
+ * If the caller has told us (through PQinitOpenSSL) that he's taking care
+ * of libcrypto, we expect that callbacks are already set, and won't try to
+ * override it.
+ *
+ * The conn parameter is only used to be able to pass back an error
+ * message - no connection-local setup is made here.
+ *
+ * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose(conn->ssl);
+ CFRelease(conn->ssl);
+
+ /* TODO: Release any certificates loaded */
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+import_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_certificate_keychain(const char *common_name, SecCertificateRef *certificate,
+ CFArrayRef keychains, char *hostname)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFStringRef host = NULL;
+ CFArrayRef temp;
+ SecPolicyRef ssl_policy;
+ int i;
+
+ /*
+ * Make sure the user didn't just specify the keychain prefix as the
+ * certificate config. The passed certificate will have the keychain
+ * prefix stripped so in that case the string is expected to be empty.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ /*
+ * If we didn't get a set of Keychains passed, skip adding it to the
+ * dictionary thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * Specifying a hostname requires it to match the hostname in the leaf
+ * certificate.
+ */
+ if (hostname)
+ host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8);
+ ssl_policy = SecPolicyCreateSSL(true, host);
+ CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label has been reported to not work (revisions of 10.12), we request
+ * all and find the one we want. Copy all the results to a temp array and
+ * scan it for the certificate we are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i);
+
+ if (search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ *certificate = (SecCertificateRef) CFRetain(search_cert);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(ssl_policy);
+ CFRelease(query);
+ CFRelease(cert);
+ if (host)
+ CFRelease(host);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, char *passphrase, CFArrayRef *certificate)
+{
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+ struct stat buf;
+
+ if (!path || strlen(path) == 0)
+ return errSecInvalidValue;
+
+ if (stat(path, &buf) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return -1;
+
+ return errSecInvalidValue;
+ }
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInvalidValue;
+
+ certdata = malloc(buf.st_size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, buf.st_size, fp) != buf.st_size)
+ {
+ fclose(fp);
+ free(certdata);
+ return errSSLBadCert;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, buf.st_size);
+ free(certdata);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecAllocate;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ return SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ bool have_homedir;
+ bool cert_from_file = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecCertificateRef root_ref[1];
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+ SecKeychainRef kcref[2];
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching. If no additional keychain is specified we don't need to open
+ * the default keychain and pass to searches since Secure Transport will
+ * use the default when passing NULL instead of an array of Keychain refs.
+ */
+ if (conn->keychain)
+ {
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr && kcref[0] != NULL)
+ {
+ SecKeychainStatus kcstatus;
+ status = SecKeychainGetStatus(kcref[0], &kcstatus);
+ if (status == noErr)
+ {
+ switch (kcstatus)
+ {
+ /*
+ * If the Keychain is already unlocked, readable or
+ * writeable, we don't need to do more. If not, try to
+ * unlock it.
+ */
+ case kSecUnlockStateStatus:
+ case kSecReadPermStatus:
+ case kSecWritePermStatus:
+ break;
+ default:
+ /* XXX: fix passphrase param */
+ SecKeychainUnlock(kcref[0], 0, "", TRUE);
+ break;
+ }
+ }
+
+ /*
+ * We only need to open, and add, the default Keychain in case
+ * have a user keychain opened, else we will pass NULL to any
+ * keychain search which will use the default keychain by..
+ * default.
+ */
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the files
+ * could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Try to load the root cert from either a user defined keychain or the
+ * default Keychain in case none is specified
+ */
+ if (conn->sslrootcert &&
+ pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ root_ref[0] = NULL;
+ strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL);
+
+ if (root_ref[0])
+ conn->st_rootcert = (void *) CFArrayCreate(NULL,
+ (const void **) root_ref,
+ 1, &kCFTypeArrayCallBacks);
+ }
+
+ if (!conn->st_rootcert)
+ {
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+ }
+
+ /*
+ * If the sslcert config is prefixed with a keychain identifier, the cert
+ * must be located in either the default or the specified keychain.
+ */
+ if (conn->sslcert &&
+ pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_identity_keychain(fnbuf, &identity, keychains);
+
+ if (identity)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ /*
+ * No prefix on the sslcert config, the certificate must thus reside in a
+ * file on disk.
+ */
+ else
+ {
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, cert_array);
+ if (status != noErr)
+ {
+ if (status == -1)
+ return noErr;
+
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ cert_from_file = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in
+ * order to create the identity.
+ */
+
+ /*
+ * The sslkey config is prefixed with keychain: indicating that the
+ * key should be loaded from Keychain instead of the filesystem.
+ * Search for the private key matching the cert_ref in the opened
+ * Keychains. If found, we get the identity returned.
+ */
+ if (conn->sslkey &&
+ pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref,
+ &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the key to be
+ * loaded from that file.
+ */
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+ }
+ }
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ if (key_ref)
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ }
+
+ if (!identity)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create identity from certificate/key\n"));
+ return errSecInvalidValue;
+ }
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (cert_from_file)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate(conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher(conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion(conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState(conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index b3492b033a..08c3005d14 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -80,6 +80,14 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_SECURETRANSPORT
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#undef Size
+#undef uint64
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -470,8 +478,18 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+
+ SSLContextRef ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 4e9095529a..d62c6ba247 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -123,6 +123,20 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### Keychains
+
+ifeq ($(with_securetransport),yes)
+
+KEYCHAINS := ssl/client.keychain
+
+# This target generates all Keychains
+keychains: $(KEYCHAINS)
+
+PWD=$(shell pwd)
+ssl/client.keychain: ssl/client.crt ssl/client.key
+ certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p=
+endif
+
.PHONY: sslfiles-clean
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
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index 02f8028b2b..c645ab2bc4 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -159,7 +159,14 @@ sub switch_server_cert
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";
+ if (check_pg_config("#define USE_SECURETRANSPORT 1"))
+ {
+ print $sslconf "ssl_crl_file=''\n";
+ }
+ else
+ {
+ print $sslconf "ssl_crl_file='root+client.crl'\n";
+ }
close $sslconf;
$node->reload;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 28837a1391..a858344329 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -2,7 +2,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 40;
+use Test::More;
use ServerSetup;
use File::Copy;
@@ -21,6 +21,27 @@ my $common_connstr;
copy("ssl/client.key", "ssl/client_tmp.key");
chmod 0600, "ssl/client_tmp.key";
+# macOS Secure Transport does not support loading CRL files, so skip CRL
+# tests when built with Secure Transport support.
+my $supports_crl_files = ! check_pg_config("#define USE_SECURETRANSPORT 1");
+
+# Test for support of Secure Transport Keychain secure archives
+# This check is currently the same as $supports_crl_files, but clarity when
+# reading code is more important than avoiding the use of an extra variable
+my $supports_keychains = check_pg_config("#define USE_SECURETRANSPORT 1");
+
+#### Setup
+
+if ($supports_keychains)
+{
+ plan tests => 43;
+}
+else
+{
+ plan tests => 40;
+}
+
+
#### Part 0. Set up the server.
note "setting up data directory";
@@ -91,18 +112,37 @@ test_connect_ok($common_connstr,
note "testing sslcrl option with a non-revoked cert";
-# 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");
-
-# 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");
-
-# 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"
-);
+if ($supports_crl_files)
+{
+ # 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");
+
+ # 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"
+ );
+
+ # 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");
+}
+else
+{
+ # Invalid CRL filename is the same as no CRL, succeeds
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid");
+
+ # With the correct CRL, succeeds (this cert is not revoked)
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+
+ # A CRL belonging to a different CA is not accepted, this should fail iff
+ # CRL files are supported
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl");
+}
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
@@ -176,9 +216,18 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok($common_connstr,
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca");
-test_connect_fails($common_connstr,
-"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
-);
+if ($supports_crl_files)
+{
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+}
+else
+{
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+}
### Part 2. Server-side tests.
###
@@ -199,10 +248,20 @@ test_connect_ok($common_connstr,
test_connect_fails($common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key");
-# revoked client cert
-test_connect_fails($common_connstr,
-"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
-);
+if ($supports_crl_files)
+{
+ # revoked client cert
+ test_connect_fails($common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
+ );
+}
+else
+{
+ # revoked client cert
+ test_connect_ok($common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
+ );
+}
# 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');
@@ -213,5 +272,15 @@ test_connect_ok($common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt");
test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt");
+if ($supports_keychains)
+{
+ # empty keychain
+ test_connect_fails("user=ssltestuser keychain=invalid");
+
+ # correct client cert in keychain with and without proper label
+ test_connect_fails("user=ssltestuser keychain=ssl/client.keychain");
+ test_connect_ok("user=ssltestuser sslcert=ssltestuser keychain=ssl/client.keychain");
+}
+
# clean up
unlink "ssl/client_tmp.key";
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 67c1409a6e..03a5533e29 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -15,6 +15,9 @@ my $SERVERHOSTADDR = '127.0.0.1';
my $supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+my $supports_tls_unique =
+ ! check_pg_config("#define USE_SECURETRANSPORT 1");
+
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -38,14 +41,27 @@ $ENV{PGPASSWORD} = "pass";
$common_connstr =
"user=ssltestuser dbname=trustdb sslmode=require hostaddr=$SERVERHOSTADDR";
-# Default settings
-test_connect_ok($common_connstr, '',
- "SCRAM authentication with default channel binding");
-
# Channel binding settings
-test_connect_ok($common_connstr,
- "scram_channel_binding=tls-unique",
- "SCRAM authentication with tls-unique as channel binding");
+if ($supports_tls_unique)
+{
+ # Default settings
+ test_connect_ok($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ test_connect_ok($common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
+else
+{
+ # Default settings
+ test_connect_fails($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ test_connect_fails($common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
test_connect_ok($common_connstr,
"scram_channel_binding=''",
"SCRAM authentication without channel binding");
--
2.14.1.145.gb3622a4ee
On 23 Jan 2018, at 22:04, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
On 1/23/18 14:59, Daniel Gustafsson wrote:
It’s not specific to the implementation per se, but it increases the likelyhood
of hitting it. In order to load certificates from Keychains the cert common
name must be specified in the connstr, when importing the testfiles into
keychains I ran into it for example src/test/ssl/client_ca.config.The change is
- 'psql', '-X', '-A', '-t', '-c', "SELECT 'connected with $connstr'", + 'psql', '-X', '-A', '-t', '-c', "SELECT \$\$connected with $connstr\$\$",So the problem must have been a single quote in the connstr.
Right, looking back at B5E2B87D-3E8A-4597-9A7F-8489B3B67556@yesql.se I realized
I was misremembering, the issue was that I had sslcert:'keychain:common name’
parameters to encapsulate the whitespace into a string value. Sorry about that.
That can surely happen, but then so can having a $$. So without a
concrete example, I'm not sure how to proceed.
Awaiting with this until the discussion on how to handle configuration and
parameter per SSL implementation lands is probably best.
cheers ./daniel
On 1/23/18 16:18, Daniel Gustafsson wrote:
The change is
- 'psql', '-X', '-A', '-t', '-c', "SELECT 'connected with $connstr'", + 'psql', '-X', '-A', '-t', '-c', "SELECT \$\$connected with $connstr\$\$",So the problem must have been a single quote in the connstr.
Right, looking back at B5E2B87D-3E8A-4597-9A7F-8489B3B67556@yesql.se I realized
I was misremembering, the issue was that I had sslcert:'keychain:common name’
parameters to encapsulate the whitespace into a string value. Sorry about that.
OK, makes sense. Committed that.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 23 Jan 2018, at 22:08, Daniel Gustafsson <daniel@yesql.se> wrote:
On 23 Jan 2018, at 20:59, Daniel Gustafsson <daniel@yesql.se> wrote:
On 23 Jan 2018, at 18:20, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote:
On 1/21/18 18:08, Daniel Gustafsson wrote:
As per before, my patch for running tests against another set of binaries is
included as well as a fix for connstrings with spaces, but with the recent
hacking by Peter I assume this is superfluous. It was handy for development so
I’ve kept it around though.0002-Allow-running-SSL-tests-against-different-binar-v4.patch should be
obsoleted by f5da5683a86e9fc42fdf3eae2da8b096bda76a8a.Yes.
On the note of patches made obsolete, the attached patch is rebased and updated
for the recent commits that moved common SSL code into shared files.
And another rebase and update after the refactoring in c1869542b3a4da4b12ca.
Also fixed some typos in comments. The other patches originally posted in this
patchset are either committed or made redundant.
cheers ./daniel
Attachments:
0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v6.patchapplication/octet-stream; name=0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v6.patchDownload
From dfe2fda6bb8a78649419856396262532f5279d72 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Sun, 21 Jan 2018 23:40:04 +0100
Subject: [PATCH] WIP: Add support for Apple Secure Transport SSL library
This adds frontend and backend support for the Secure Transport SSL
library on macOS, but currently lacks SCRAM support. Thus, the patch
is not in a final state but is provided for hacking and testing.
---
configure | 57 +
configure.in | 14 +
src/Makefile.global.in | 1 +
src/backend/Makefile | 6 +-
src/backend/libpq/Makefile | 5 +
src/backend/libpq/be-secure-securetransport.c | 1306 +++++++++++++++++++
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 25 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/common/securetransport.h | 514 ++++++++
src/include/common/sha2.h | 4 +-
src/include/libpq/libpq-be.h | 17 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 12 +-
src/interfaces/libpq/Makefile | 6 +-
src/interfaces/libpq/fe-connect.c | 6 +
src/interfaces/libpq/fe-secure-securetransport.c | 1499 ++++++++++++++++++++++
src/interfaces/libpq/libpq-int.h | 22 +-
src/test/ssl/Makefile | 14 +
src/test/ssl/ServerSetup.pm | 9 +-
src/test/ssl/t/001_ssltests.pl | 109 +-
src/test/ssl/t/002_scram.pl | 30 +-
23 files changed, 3626 insertions(+), 40 deletions(-)
create mode 100644 src/backend/libpq/be-secure-securetransport.c
create mode 100644 src/include/common/securetransport.h
create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c
diff --git a/configure b/configure
index 7dcca506f8..b9db714127 100755
--- a/configure
+++ b/configure
@@ -707,6 +707,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
krb_srvtab
with_python
@@ -835,6 +836,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1529,6 +1531,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -5995,6 +5998,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
#
# SELinux
#
@@ -10978,6 +11016,25 @@ else
fi
+fi
+
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.in b/configure.in
index 4d26034579..20f3d55c07 100644
--- a/configure.in
+++ b/configure.in
@@ -703,6 +703,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
#
# SELinux
#
@@ -1225,6 +1234,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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index d980f81046..9f60a3dd0d 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_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_libxml = @with_libxml@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 4a28267339..fc11b7271c 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -49,6 +49,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LDFLAGS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-schemapg postgres $(POSTGRES_IMP)
@@ -58,7 +62,7 @@ ifneq ($(PORTNAME), win32)
ifneq ($(PORTNAME), aix)
postgres: $(OBJS)
- $(CC) $(CFLAGS) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@
+ $(CC) $(CFLAGS) -framework CoreFoundation -framework Security $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@
endif
endif
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 7fa2b02743..3a835235b9 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000000..80554bc17f
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1306 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - Load DH keys from file
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future).
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interested to investigate.
+ *
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "common/base64.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#define bool pg_bool
+#include <Security/cssmerr.h>
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef bool
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_bool bool
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err. Since we
+ * may have encountered an error due to memory pressure, we don't want to rely
+ * on dynamically allocating memory for this error message.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+/*
+ * SecIdentityCreate is not an exported Secure Transport API. There is however
+ * no exported Secure Transport function that can create an identity from a
+ * SecCertificateRef and a SecKeyRef without having either of them in a
+ * Keychain. This function is commonly used in open source projects (such as
+ * ffmpeg and mono f.e), but finding an alternative is a TODO.
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static void load_key(char *name, CFArrayRef *out);
+static OSStatus load_keychain(char *name, CFArrayRef *keychains);
+static OSStatus load_certificate_file(char *name, CFArrayRef *cert_array);
+static OSStatus load_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+
+static int load_dh_file(char *filename, char **buf);
+static void load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl);
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize global Secure Transport structures (if any)
+ *
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ *
+ * Another reason to defer this until when in the backend is that Keychains
+ * are SQLite3 backed, and sqlite does not allow access across a fork. See
+ * https://sqlite.org/faq.html#q6 for more information.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * be_tls_open_server
+ * Attempt to negotiate a secure connection
+ *
+ * Unlike the OpenSSL backend, this function is responsible for loading keys
+ * and certificates for use in the connection. See the comment on be_tls_init
+ * for further reasoning around this.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFArrayRef keychains;
+ CFMutableArrayRef chain;
+ char *dh_buf;
+ int dh_len;
+
+ Assert(!port->ssl);
+
+ if (ssl_keychain_file[0])
+ load_keychain(ssl_keychain_file, &keychains);
+
+ /*
+ * If the ssl_cert_file is prefixed with a keychain reference, we will try
+ * to load a complete identity from the Keychain.
+ */
+ if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains);
+ else
+ {
+ status = load_certificate_file(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": \"%s\"",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ load_key(ssl_key_file, &keys);
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ }
+
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate_file(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": \"%s\"",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API
+ */
+ if (ssl_crl_file[0])
+ ereport(FATAL,
+ (errmsg("CRL files not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ /*
+ * We use kTryAuthenticate here since we don't know which sslmode the
+ * client is using. If we were to use kAlwaysAuthenticate then sslmode
+ * require won't work as intended.
+ */
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * In case the user hasn't configured a DH parameters file, we load a pre-
+ * computed DH parameter to avoid having Secure Transport computing one for
+ * us (which is done by default unless one is set).
+ */
+ dh_buf = NULL;
+ if (ssl_dh_params_file[0])
+ dh_len = load_dh_file(ssl_dh_params_file, &dh_buf);
+ if (!dh_buf || dh_len == 0)
+ {
+ dh_buf = pstrdup(FILE_DH2048);
+ dh_len = sizeof(FILE_DH2048);
+ }
+
+ load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl);
+
+ /*
+ * Set Tlsv1.2 as the minimum protocol version we allow for the connection
+ */
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ if (status == errSSLWouldBlock)
+ continue;
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+
+ CFStringRef usercert_cn;
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+ port->peer_cn = pstrdup(CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8));
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ *
+ * Loads a private key from the specified filename. Unless the key loads this
+ * will not return but will error out.
+ */
+static void
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+
+ if (!check_ssl_key_file_permissions(name, true))
+ return;
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ /*
+ * check_ssl_key_file_permissions() has already checked the file for
+ * existence and correct permissions, but we still need to stat it to
+ * get the filesize.
+ */
+ stat(name, &stat_buf);
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ SecItemImportExportKeyParameters params;
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": \"%s\"",
+ name, pg_SSLerrmessage(status))));
+}
+
+/*
+ * load_keychain
+ * Open the specified, and default, Keychain
+ *
+ * Operations on keychains other than the default take the keychain references
+ * in an array. We need to copy a reference to the default keychain into the
+ * array to include it in the search.
+ *
+ * For server applications, we don't want modal dialog boxes opened for
+ * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE)
+ * will turn off all GUI interaction with the user, which may seem like what we
+ * want server side. This however has the side effect to turn off all GUI
+ * elements for all applications until some application calls
+ * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus
+ * remove wanted GUI interaction from another app, or another app might
+ * introduce it for
+ * us.
+ */
+static OSStatus
+load_keychain(char *name, CFArrayRef *keychains)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ SecKeychainRef kc[2];
+
+ if (stat(name, &stat_buf) != 0)
+ return errSecInvalidValue;
+
+ status = SecKeychainOpen(name, &kc[0]);
+ if (status == noErr)
+ {
+ SecKeychainUnlock(kc[0], 0, "", TRUE);
+ /*
+ * We only need to open, and add, the default Keychain in case have a
+ * user keychain opened, else we will pass NULL to any keychain search
+ * which will use the default keychain by.. default.
+ */
+ SecKeychainCopyDefault(&kc[1]);
+ *keychains = CFArrayCreate(NULL, (const void **) kc, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ else
+ elog(LOG, "XXX: could not load keychain \"%s\" : %d", name, status);
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+load_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+/*
+ * load_certificate_file
+ * Extracts a certificate from a PEM file on the filesystem
+ *
+ * TODO: figure out better returncodes
+ */
+static OSStatus
+load_certificate_file(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ /*
+ * If the configured ssl_cert_file filename is set to a non-existing
+ * file, assume it's referencing a Keychain label and attempt to load
+ * the certificate from the Keychain instead.
+ */
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ /*
+ * TODO: Do we want to search keychains for certificates serverside
+ * like we do clientside, or do we want to stick to a single way to
+ * configure the server? Since CRL files aren't supported outside
+ * keychains I guess we need to, but worth a discussion.
+ */
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ return errSecInternalError;
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ return errSecInternalError;
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL, cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ return errSecInternalError;
+}
+
+/*
+ * XXX: Make this file loading and slurping only
+ */
+static int
+load_dh_file(char *filename, char **buf)
+{
+ FILE *dh;
+ struct stat stat_buf;
+ int ret;
+
+ /*
+ * Open the DH file and slurp the contents. If the file doesn't exist it's
+ * not an error, if it can't be opened it is however an error.
+ */
+ ret = stat(filename, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ return 0;
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((dh = AllocateFile(filename, "r")) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+
+ *buf = palloc(stat_buf.st_size);
+ ret = fread(*buf, 1, stat_buf.st_size, dh);
+ FreeFile(dh);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read DH parameters file \"%s\": %m",
+ filename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("DH parameters file \"%s\" is not a regular file",
+ filename)));
+
+ return ret;
+}
+
+/*
+ * load_dh_params
+ * Load the specified DH params for the connection
+ *
+ * Secure Transport requires the DH params to be in DER format, but to be
+ * compatible with the OpenSSL code we also support PEM and convert to DER
+ * before loading. Conversion does rudimentary PEM parsing, if we miss the
+ * data being correct, the Secure Transport API will give an error anyways so
+ * we're just checking basic integrity.
+ *
+ * This function may scribble on the dh parameter so if that's required so stay
+ * intact in the caller, a copy should be sent.
+ */
+#define DH_HEADER "-----BEGIN DH PARAMETERS-----"
+#define DH_FOOTER "-----END DH PARAMETERS-----"
+static void
+load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl)
+{
+ OSStatus status;
+ char *der;
+ int der_len;
+
+ Assert(dh);
+
+ /*
+ * Convert PEM to DER
+ */
+ if (is_pem)
+ {
+ char *head;
+ char *tail;
+ int pem_len = 0;
+
+ if (strstr(dh, DH_HEADER) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, header missing")));
+
+ dh += strlen(DH_HEADER);
+ tail = strstr(dh, DH_FOOTER);
+ if (!tail)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, footer missing")));
+ *tail = '\0';
+
+ /* In order to PEM convert it we need to remove all newlines */
+ head = dh;
+ tail = dh;
+ while (*head != '\0')
+ {
+ if (*head != '\n')
+ {
+ *tail++ = *head++;
+ pem_len++;
+ }
+ else
+ head++;
+ }
+ *tail = '\0';
+
+ der = palloc(pg_b64_dec_len(strlen(dh)) + 1);
+ der_len = pg_b64_decode(dh, strlen(dh), der);
+ der[der_len] = '\0';
+ }
+ else
+ {
+ der = dh;
+ der_len = len;
+ }
+
+ status = SSLSetDiffieHellmanParams(ssl, der, der_len);
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unable to load DH parameters: %s",
+ pg_SSLerrmessage(status))));
+}
+
+/*
+ * This function should return the expected TLS Finished message information
+ * from the client, but there is no API for this in Secure Transport. As a
+ * result, channel binding is not supported by Secure Transport.
+ */
+char *
+be_tls_get_peer_finished(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+/*
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect TLS version for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ if (protocol == kTLSProtocol11)
+ return "TLSv1.1";
+ else if (protocol == kTLSProtocol12)
+ return "TLSv1.2";
+
+ return "unknown";
+}
+
+/*
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+ const char *cipher_name;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect cipher for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ cipher_name = pg_SSLciphername(cipher);
+ if (cipher_name == NULL)
+ elog(ERROR, "Unknown cipher detected");
+
+ return cipher_name;
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ /*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there still is no translation for it in 10.11 El Capitan, so we
+ * maintain our own
+ */
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 76c0a9e39b..5c292150aa 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -45,6 +45,9 @@ char *ssl_key_file;
char *ssl_ca_file;
char *ssl_crl_file;
char *ssl_dh_params_file;
+#ifdef USE_SECURETRANSPORT
+char *ssl_keychain_file;
+#endif
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5884fa905e..a694d218b0 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -501,6 +501,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -3324,6 +3325,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
{
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
@@ -3571,6 +3584,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the Keychain file."),
+ NULL
+ },
+ &ssl_keychain_file,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index abffde6b2b..4f65250ed1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -104,6 +104,7 @@
#ssl_prefer_server_ciphers = on
#ssl_ecdh_curve = 'prime256v1'
#ssl_dh_params_file = ''
+#ssl_keychain_file = ''
#------------------------------------------------------------------------------
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000000..d3a38e2a5c
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,514 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport_common.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+#ifndef FRONTEND
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement. This
+ * function is currently only used in the libpq backend.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0d28..6d35f47864 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7698cd1f88..9ba5b6f46c 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,12 +191,15 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+ void *ssl;
+ int ssl_buffered;
#endif
} Port;
@@ -214,7 +225,7 @@ CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
/*
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 255222acd7..9cc91323f6 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -80,6 +80,9 @@ extern char *ssl_key_file;
extern char *ssl_ca_file;
extern char *ssl_crl_file;
extern char *ssl_dh_params_file;
+#ifdef USE_SECURETRANSPORT
+extern char *ssl_keychain_file;
+#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 f98f773ff0..a1d71a6473 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -856,6 +856,9 @@
/* Define to use OpenSSL for random number generation */
#undef USE_OPENSSL_RANDOM
+/* Define to build with Apple Secure Transport support. (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index b309395f11..5469473d72 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -162,11 +162,17 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
+#else
+#define SSL_LIBRARY "None"
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 0bf1e7ef04..fe8662d1b6 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,11 @@ else
OBJS += sha2.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -112,7 +117,6 @@ ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
-
distprep: libpq-dist.rc
libpq.rc libpq-dist.rc: libpq.rc.in
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 77eebb0ba1..ede910f250 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -330,6 +330,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000000..e8e1d68bcf
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1499 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readiblitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *common_name,
+ SecCertificateRef *certificate,
+ CFArrayRef keychains,
+ char *hostname);
+static OSStatus import_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+static OSStatus import_pem(const char *path, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * Exported function to allow application to tell us it's already initialized
+ * Secure Transport and/or libcrypto.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ return;
+}
+
+/*
+ * Begin or continue negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin(conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection(conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs(conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we dont have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ SSLSetPeerDomainName(conn->ssl, conn->pghost, strlen(conn->pghost));
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption(conn->ssl, kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake(conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust(conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ /*
+ * TODO: return a better error code than SSLInternalError
+ */
+ if (!trusted)
+ return errSecInternalError;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize(conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead(conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite(conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite(conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+/*
+ */
+char *
+pgtls_get_finished(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"%s\" is not supported by this build\n"),
+ conn->scram_channel_binding);
+ return NULL;
+}
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"%s\" is not supported by this build\n"),
+ conn->scram_channel_binding);
+ return NULL;
+}
+
+/*
+ * Initialize SSL system, in particular creating the SSL_context object
+ * that will be shared by all SSL-using connections in this process.
+ *
+ * In threadsafe mode, this includes setting up libcrypto callback functions
+ * to do thread locking.
+ *
+ * If the caller has told us (through PQinitOpenSSL) that he's taking care
+ * of libcrypto, we expect that callbacks are already set, and won't try to
+ * override it.
+ *
+ * The conn parameter is only used to be able to pass back an error
+ * message - no connection-local setup is made here.
+ *
+ * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage).
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose(conn->ssl);
+ CFRelease(conn->ssl);
+
+ /* TODO: Release any certificates loaded */
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+import_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_certificate_keychain(const char *common_name, SecCertificateRef *certificate,
+ CFArrayRef keychains, char *hostname)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFStringRef host = NULL;
+ CFArrayRef temp;
+ SecPolicyRef ssl_policy;
+ int i;
+
+ /*
+ * Make sure the user didn't just specify the keychain prefix as the
+ * certificate config. The passed certificate will have the keychain
+ * prefix stripped so in that case the string is expected to be empty.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ /*
+ * If we didn't get a set of Keychains passed, skip adding it to the
+ * dictionary thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * Specifying a hostname requires it to match the hostname in the leaf
+ * certificate.
+ */
+ if (hostname)
+ host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8);
+ ssl_policy = SecPolicyCreateSSL(true, host);
+ CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label has been reported to not work (revisions of 10.12), we request
+ * all and find the one we want. Copy all the results to a temp array and
+ * scan it for the certificate we are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i);
+
+ if (search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ *certificate = (SecCertificateRef) CFRetain(search_cert);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(ssl_policy);
+ CFRelease(query);
+ CFRelease(cert);
+ if (host)
+ CFRelease(host);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, char *passphrase, CFArrayRef *certificate)
+{
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+ struct stat buf;
+
+ if (!path || strlen(path) == 0)
+ return errSecInvalidValue;
+
+ if (stat(path, &buf) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return -1;
+
+ return errSecInvalidValue;
+ }
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInvalidValue;
+
+ certdata = malloc(buf.st_size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, buf.st_size, fp) != buf.st_size)
+ {
+ fclose(fp);
+ free(certdata);
+ return errSSLBadCert;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, buf.st_size);
+ free(certdata);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecAllocate;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ return SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ bool have_homedir;
+ bool cert_from_file = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecCertificateRef root_ref[1];
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+ SecKeychainRef kcref[2];
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching. If no additional keychain is specified we don't need to open
+ * the default keychain and pass to searches since Secure Transport will
+ * use the default when passing NULL instead of an array of Keychain refs.
+ */
+ if (conn->keychain)
+ {
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr && kcref[0] != NULL)
+ {
+ SecKeychainStatus kcstatus;
+ status = SecKeychainGetStatus(kcref[0], &kcstatus);
+ if (status == noErr)
+ {
+ switch (kcstatus)
+ {
+ /*
+ * If the Keychain is already unlocked, readable or
+ * writeable, we don't need to do more. If not, try to
+ * unlock it.
+ */
+ case kSecUnlockStateStatus:
+ case kSecReadPermStatus:
+ case kSecWritePermStatus:
+ break;
+ default:
+ /* XXX: fix passphrase param */
+ SecKeychainUnlock(kcref[0], 0, "", TRUE);
+ break;
+ }
+ }
+
+ /*
+ * We only need to open, and add, the default Keychain in case
+ * have a user keychain opened, else we will pass NULL to any
+ * keychain search which will use the default keychain by..
+ * default.
+ */
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the files
+ * could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Try to load the root cert from either a user defined keychain or the
+ * default Keychain in case none is specified
+ */
+ if (conn->sslrootcert &&
+ pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ root_ref[0] = NULL;
+ strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL);
+
+ if (root_ref[0])
+ conn->st_rootcert = (void *) CFArrayCreate(NULL,
+ (const void **) root_ref,
+ 1, &kCFTypeArrayCallBacks);
+ }
+
+ if (!conn->st_rootcert)
+ {
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+ }
+
+ /*
+ * If the sslcert config is prefixed with a keychain identifier, the cert
+ * must be located in either the default or the specified keychain.
+ */
+ if (conn->sslcert &&
+ pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_identity_keychain(fnbuf, &identity, keychains);
+
+ if (identity)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ /*
+ * No prefix on the sslcert config, the certificate must thus reside in a
+ * file on disk.
+ */
+ else
+ {
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, cert_array);
+ if (status != noErr)
+ {
+ if (status == -1)
+ return noErr;
+
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ cert_from_file = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in
+ * order to create the identity.
+ */
+
+ /*
+ * The sslkey config is prefixed with keychain: indicating that the
+ * key should be loaded from Keychain instead of the filesystem.
+ * Search for the private key matching the cert_ref in the opened
+ * Keychains. If found, we get the identity returned.
+ */
+ if (conn->sslkey &&
+ pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref,
+ &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the key to be
+ * loaded from that file.
+ */
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+ }
+ }
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ if (key_ref)
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ }
+
+ if (!identity)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create identity from certificate/key\n"));
+ return errSecInvalidValue;
+ }
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (cert_from_file)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate(conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher(conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion(conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState(conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index b3492b033a..08c3005d14 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -80,6 +80,14 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_SECURETRANSPORT
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#undef Size
+#undef uint64
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -470,8 +478,18 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+
+ SSLContextRef ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 4e9095529a..d62c6ba247 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -123,6 +123,20 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### Keychains
+
+ifeq ($(with_securetransport),yes)
+
+KEYCHAINS := ssl/client.keychain
+
+# This target generates all Keychains
+keychains: $(KEYCHAINS)
+
+PWD=$(shell pwd)
+ssl/client.keychain: ssl/client.crt ssl/client.key
+ certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p=
+endif
+
.PHONY: sslfiles-clean
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
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index b4d5746e20..a419d29c94 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -159,7 +159,14 @@ sub switch_server_cert
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";
+ if (check_pg_config("#define USE_SECURETRANSPORT 1"))
+ {
+ print $sslconf "ssl_crl_file=''\n";
+ }
+ else
+ {
+ print $sslconf "ssl_crl_file='root+client.crl'\n";
+ }
close $sslconf;
$node->reload;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 28837a1391..a858344329 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -2,7 +2,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 40;
+use Test::More;
use ServerSetup;
use File::Copy;
@@ -21,6 +21,27 @@ my $common_connstr;
copy("ssl/client.key", "ssl/client_tmp.key");
chmod 0600, "ssl/client_tmp.key";
+# macOS Secure Transport does not support loading CRL files, so skip CRL
+# tests when built with Secure Transport support.
+my $supports_crl_files = ! check_pg_config("#define USE_SECURETRANSPORT 1");
+
+# Test for support of Secure Transport Keychain secure archives
+# This check is currently the same as $supports_crl_files, but clarity when
+# reading code is more important than avoiding the use of an extra variable
+my $supports_keychains = check_pg_config("#define USE_SECURETRANSPORT 1");
+
+#### Setup
+
+if ($supports_keychains)
+{
+ plan tests => 43;
+}
+else
+{
+ plan tests => 40;
+}
+
+
#### Part 0. Set up the server.
note "setting up data directory";
@@ -91,18 +112,37 @@ test_connect_ok($common_connstr,
note "testing sslcrl option with a non-revoked cert";
-# 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");
-
-# 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");
-
-# 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"
-);
+if ($supports_crl_files)
+{
+ # 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");
+
+ # 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"
+ );
+
+ # 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");
+}
+else
+{
+ # Invalid CRL filename is the same as no CRL, succeeds
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid");
+
+ # With the correct CRL, succeeds (this cert is not revoked)
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+
+ # A CRL belonging to a different CA is not accepted, this should fail iff
+ # CRL files are supported
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl");
+}
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
@@ -176,9 +216,18 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok($common_connstr,
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca");
-test_connect_fails($common_connstr,
-"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
-);
+if ($supports_crl_files)
+{
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+}
+else
+{
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl"
+ );
+}
### Part 2. Server-side tests.
###
@@ -199,10 +248,20 @@ test_connect_ok($common_connstr,
test_connect_fails($common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key");
-# revoked client cert
-test_connect_fails($common_connstr,
-"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
-);
+if ($supports_crl_files)
+{
+ # revoked client cert
+ test_connect_fails($common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
+ );
+}
+else
+{
+ # revoked client cert
+ test_connect_ok($common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key"
+ );
+}
# 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');
@@ -213,5 +272,15 @@ test_connect_ok($common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt");
test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt");
+if ($supports_keychains)
+{
+ # empty keychain
+ test_connect_fails("user=ssltestuser keychain=invalid");
+
+ # correct client cert in keychain with and without proper label
+ test_connect_fails("user=ssltestuser keychain=ssl/client.keychain");
+ test_connect_ok("user=ssltestuser sslcert=ssltestuser keychain=ssl/client.keychain");
+}
+
# clean up
unlink "ssl/client_tmp.key";
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 67c1409a6e..03a5533e29 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -15,6 +15,9 @@ my $SERVERHOSTADDR = '127.0.0.1';
my $supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+my $supports_tls_unique =
+ ! check_pg_config("#define USE_SECURETRANSPORT 1");
+
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -38,14 +41,27 @@ $ENV{PGPASSWORD} = "pass";
$common_connstr =
"user=ssltestuser dbname=trustdb sslmode=require hostaddr=$SERVERHOSTADDR";
-# Default settings
-test_connect_ok($common_connstr, '',
- "SCRAM authentication with default channel binding");
-
# Channel binding settings
-test_connect_ok($common_connstr,
- "scram_channel_binding=tls-unique",
- "SCRAM authentication with tls-unique as channel binding");
+if ($supports_tls_unique)
+{
+ # Default settings
+ test_connect_ok($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ test_connect_ok($common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
+else
+{
+ # Default settings
+ test_connect_fails($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ test_connect_fails($common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
test_connect_ok($common_connstr,
"scram_channel_binding=''",
"SCRAM authentication without channel binding");
--
2.14.1.145.gb3622a4ee
Hi,
On 2018-01-26 23:30:08 +0100, Daniel Gustafsson wrote:
And another rebase and update after the refactoring in c1869542b3a4da4b12ca.
Also fixed some typos in comments. The other patches originally posted in this
patchset are either committed or made redundant.
Could you provide a quick summary about where you think this patch
stands currently? The cover letter you had for this thread was great...
- Andres
On 02 Mar 2018, at 10:10, Andres Freund <andres@anarazel.de> wrote:
On 2018-01-26 23:30:08 +0100, Daniel Gustafsson wrote:
And another rebase and update after the refactoring in c1869542b3a4da4b12ca.
Also fixed some typos in comments. The other patches originally posted in this
patchset are either committed or made redundant.Could you provide a quick summary about where you think this patch
stands currently? The cover letter you had for this thread was great…
I’m actually working on that right now, combined with a rebase. I’m travelling
today and tomorrow with limited time and internet available but I will provide
this on Sunday at the latest.
cheers ./daniel
On 02 Mar 2018, at 03:10, Andres Freund <andres@anarazel.de> wrote:
On 2018-01-26 23:30:08 +0100, Daniel Gustafsson wrote:And another rebase and update after the refactoring in c1869542b3a4da4b12ca.
Also fixed some typos in comments. The other patches originally posted in this
patchset are either committed or made redundant.Could you provide a quick summary about where you think this patch
stands currently? The cover letter you had for this thread was great…
Below is a summary of the current state of the patch.
Current State
=============
I’ve tried to keep this patch current with the SSL work that Peter E has been
doing, and I believe it is using the new generic infrastructure provided
wherever possible. The new SCRAM channel binding support is supported in the
sense that it isn’t supported (errors out “gracefully”) - due to Secure
Transport lacking the needed APIs.
Keychain support
================
In an earlier version this patch was not supporting Keychains for the backend
part, but I have since implemented that such that both frontend and backend can
load identities from Keychains. The common way to use Keychains in macOS apps
is to always include the user default Keychain in addition to any specific
ones. My opinion, which is what I’ve implemented, is that we (at least) in the
backend don’t want to include the user default Keychain by default, but instead
have a config setting which turns that on should the user want to. My
reasoning is that a) server applications want more fine grained control and; b)
including a Keychain we don’t control when running tests seems a like a
terrible idea. Allowing the default Keychain to be turned on with a config
knob seems a better option, so that’s what I’ve done. (this config knob was
added last week on the plane and was thus not in the last revision published, I
will post a new revision shortly but I want to read this code again first to
ensure I’m not wasting reviewer time with bugs/things I should’ve caught.)
Certificate Revocation
======================
There is no API for CRL files in Secure Transport, they are expected to be
loaded into the Keychain. The ssl_crl_file setting is erroring out with a hint
to avoid silently dropping important configuration. This is not a change from
any previously published version, but a background reminder for the next
section..
Testing
=======
Due to lack of better ideas, I’d added an ugly kludge in the tests to bypass
CRL tests on Secure Transport. I think however that I figured out how to
properly programmatically create Keychains that contain the identity and CRL
info to match the current SSL tests. I’m coding that right now, and hopefully
this means that there will be no (or few) diffs required against the tests,
only the infrastructure.
Commitfest Status
=================
Do I think this patch is realistic to target for v11? Well. Given where we
are in the cycle, I don’t think any new TLS implementation going in is
realistic at this point since none of the proposed ones have had enough tyre
kicking done. That might change should there be lots of interest and work
started soon, but as has been discussed elsewhere recently the project has
limited resources. I have time to work on this, and support reviewers of it,
but that’s only piece of the puzzle.
If no new TLS library is supported in v11, we still got cleaner SSL support out
of it due to the work performed to further remove our dependency on OpenSSL, so
we still come out on top IMO. Thanks Peter et.al!
The current revision of the patch applies cleanly on HEAD except for the tests,
but I hope to post a new version with reworked tests properly using Keychains
shortly (as in, early this week)
So, TL;DR: both frontend and backend API are implemented except for SCRAM
channel binding and CRL file support, it needs tests and documentation, it does
not implement pgcrypto, it will be fixed to support whichever GUC strategy the
project decides on for multiple TLS library support.
cheers ./daniel
On Sun, Mar 04, 2018 at 11:15:58PM +0100, Daniel Gustafsson wrote:
Commitfest Status
=================
Do I think this patch is realistic to target for v11? Well. Given where we
are in the cycle, I don’t think any new TLS implementation going in is
realistic at this point since none of the proposed ones have had enough tyre
kicking done. That might change should there be lots of interest and work
started soon, but as has been discussed elsewhere recently the project has
limited resources. I have time to work on this, and support reviewers of it,
but that’s only piece of the puzzle.
This patch has been around for some time now, and a couple of people
have expressed interest in the feature, so it seems to me that there is
still some room to get new features in the tree. I am not personally
planning to spend much time on reviewing this specific implementation
though, however...
If no new TLS library is supported in v11, we still got cleaner SSL support out
of it due to the work performed to further remove our dependency on OpenSSL, so
we still come out on top IMO. Thanks Peter et.al!
I am definitely interested in plugging in more generic APIs for this
release, so as people can also fork Postgres and implement their own SSL
implementation more easily. One patch that still applies in this area
is I think this one to allow SSL implementations let the backend know
more easily is channel binding needs to be implemented or not:
https://commitfest.postgresql.org/17/1491/
So, TL;DR: both frontend and backend API are implemented except for SCRAM
channel binding and CRL file support, it needs tests and documentation, it does
not implement pgcrypto, it will be fixed to support whichever GUC strategy the
project decides on for multiple TLS library support.
One bit of conclusion in this area is that at Peter E has argued in
favor of having separate configure switches for each each SSL
implementation instead of reworking things into a single, generic
switch. The GUC portion is also pointing in the direction of having one
set of parameters per implementation so as assigning default values is a
no-brainer. Seeing the GUC portion changed. Even if this is not
changed, there is always the point of assuming that the existing
SSL parameters map to OpenSSL, and add on top of it the new sets. But
that would be confusing for the user than renaming simply the existing
parameters.
--
Michael
On 05 Mar 2018, at 02:41, Michael Paquier <michael@paquier.xyz> wrote:
On Sun, Mar 04, 2018 at 11:15:58PM +0100, Daniel Gustafsson wrote:
If no new TLS library is supported in v11, we still got cleaner SSL support out
of it due to the work performed to further remove our dependency on OpenSSL, so
we still come out on top IMO. Thanks Peter et.al!I am definitely interested in plugging in more generic APIs for this
release, so as people can also fork Postgres and implement their own SSL
implementation more easily. One patch that still applies in this area
is I think this one to allow SSL implementations let the backend know
more easily is channel binding needs to be implemented or not:
https://commitfest.postgresql.org/17/1491/
Right. Regardless of the state of this patch I hope that can make it into 11
to further make 11 a good base for hacking on SSL code.
So, TL;DR: both frontend and backend API are implemented except for SCRAM
channel binding and CRL file support, it needs tests and documentation, it does
not implement pgcrypto, it will be fixed to support whichever GUC strategy the
project decides on for multiple TLS library support.One bit of conclusion in this area is that at Peter E has argued in
favor of having separate configure switches for each each SSL
implementation instead of reworking things into a single, generic
switch. The GUC portion is also pointing in the direction of having one
set of parameters per implementation so as assigning default values is a
no-brainer.
FWIW I completely agree with this approach. With a single set of GUCs for all
implementations I fear that we risk ending up with configurations that require
shoehorning, and that will no-doubt open the risk for vulnerable servers due to
the configuration being hard to get right. Either approach does introduce a
problem for moving a hand-edited postgresql.conf files between clusters running
different libraries, but thats hard to avoid I think.
cheers ./daniel
On 2018-03-05 10:41:01 +0900, Michael Paquier wrote:
If no new TLS library is supported in v11, we still got cleaner SSL support out
of it due to the work performed to further remove our dependency on OpenSSL, so
we still come out on top IMO. Thanks Peter et.al!I am definitely interested in plugging in more generic APIs for this
release, so as people can also fork Postgres and implement their own SSL
implementation more easily.
I don't think that should be a goal. A positive side-effect, yes, but we
imo shouldn't let that as a goal guide us.
Greetings,
Andres Freund
On 3/4/18 17:15, Daniel Gustafsson wrote:
Do I think this patch is realistic to target for v11? Well. Given where we
are in the cycle, I don’t think any new TLS implementation going in is
realistic at this point since none of the proposed ones have had enough tyre
kicking done. That might change should there be lots of interest and work
started soon, but as has been discussed elsewhere recently the project has
limited resources. I have time to work on this, and support reviewers of it,
but that’s only piece of the puzzle.
I think it would be best if both this patch and the GnuTLS patch are
moved to the next CF and are attacked early in the PG12 cycle.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 3/4/18 17:15, Daniel Gustafsson wrote:
Do I think this patch is realistic to target for v11? Well. Given where we
are in the cycle, I don’t think any new TLS implementation going in is
realistic at this point since none of the proposed ones have had enough tyre
kicking done. That might change should there be lots of interest and work
started soon, but as has been discussed elsewhere recently the project has
limited resources. I have time to work on this, and support reviewers of it,
but that’s only piece of the puzzle.
I think it would be best if both this patch and the GnuTLS patch are
moved to the next CF and are attacked early in the PG12 cycle.
+1. I think it's fairly important that those two get reviewed/committed
in the same cycle, in case we need to adjust the relevant APIs. It
seems unlikely that we can muster the effort to get both done for v11.
regards, tom lane
On 06 Mar 2018, at 22:08, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 3/4/18 17:15, Daniel Gustafsson wrote:
Do I think this patch is realistic to target for v11? Well. Given where we
are in the cycle, I don’t think any new TLS implementation going in is
realistic at this point since none of the proposed ones have had enough tyre
kicking done. That might change should there be lots of interest and work
started soon, but as has been discussed elsewhere recently the project has
limited resources. I have time to work on this, and support reviewers of it,
but that’s only piece of the puzzle.I think it would be best if both this patch and the GnuTLS patch are
moved to the next CF and are attacked early in the PG12 cycle.+1. I think it's fairly important that those two get reviewed/committed
in the same cycle, in case we need to adjust the relevant APIs.
I completely agree with all of the above.
cheers ./daniel
On 6 Mar 2018, at 22:08, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 3/4/18 17:15, Daniel Gustafsson wrote:
Do I think this patch is realistic to target for v11? Well. Given where we
are in the cycle, I don’t think any new TLS implementation going in is
realistic at this point since none of the proposed ones have had enough tyre
kicking done. That might change should there be lots of interest and work
started soon, but as has been discussed elsewhere recently the project has
limited resources. I have time to work on this, and support reviewers of it,
but that’s only piece of the puzzle.I think it would be best if both this patch and the GnuTLS patch are
moved to the next CF and are attacked early in the PG12 cycle.+1. I think it's fairly important that those two get reviewed/committed
in the same cycle, in case we need to adjust the relevant APIs. It
seems unlikely that we can muster the effort to get both done for v11.
Attached is an updated patch for supporting the native macOS Secure Transport
library, rebased on top of current master. This patch still fails a couple of
the SSL tests, and doesn’t support any channel binding for SCRAM, but is IMO a
good enough WIP/snifftest to see if the current implementation approach is at
all any good and/or of interest.
Apart from being rebased on current master, this version contains mostly
general cleanups as well as removing support for anything older than High
Sierra (I no longer have access to systems on older versions so I’m unable to
test). On top of that, the few notable new things are:
* adds support for disallowing usage of the default user Keychain
* adds support for ssl_passphrase_command which was added in 8a3d9425290ff5f64
* extends the SSL tests to pass in separate expected output per backend. How
to refactor the test code to cope with multiple backends hasn’t really been
discussed, with the GnuTLS patch taking another approach, but this was handy
for me while testing to keep the capabilities and functionality separate.
(Reordering the parameters to test_connect_fails() was initially by mistake but
I like how it matches the order in test_connect_ok() so kept it). This should
probably be discussed on a separate thread though.
If there are parts which are insufficiently commented, let me know and I’ll do
my best to extend.
cheers ./daniel
Attachments:
0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v7.patchapplication/octet-stream; name=0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v7.patch; x-unix-mode=0644Download
From 1b321b2d352a72876e5989765e1f403b07db9dee Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 27 Jun 2018 12:04:48 +0200
Subject: [PATCH] WIP: Add support for Apple Secure Transport SSL library
This adds frontend and backend support for the Secure Transport SSL
library on macOS, but currently lacks SCRAM support. Thus, the patch
is not in a final state but is provided for hacking and testing.
---
configure | 57 +
configure.in | 14 +
src/Makefile.global.in | 1 +
src/backend/Makefile | 6 +-
src/backend/libpq/Makefile | 5 +
src/backend/libpq/be-secure-securetransport.c | 1470 +++++++++++++++++++++
src/backend/libpq/be-secure.c | 4 +
src/backend/utils/misc/guc.c | 36 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/common/securetransport.h | 514 ++++++++
src/include/common/sha2.h | 4 +-
src/include/libpq/libpq-be.h | 17 +-
src/include/libpq/libpq.h | 4 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 12 +-
src/interfaces/libpq/Makefile | 6 +-
src/interfaces/libpq/fe-connect.c | 13 +
src/interfaces/libpq/fe-secure-securetransport.c | 1494 ++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 2 +
src/interfaces/libpq/libpq-int.h | 23 +-
src/test/ssl/Makefile | 17 +-
src/test/ssl/ServerSetup.pm | 23 +-
src/test/ssl/t/001_ssltests.pl | 278 +++-
src/test/ssl/t/002_scram.pl | 36 +-
24 files changed, 3949 insertions(+), 91 deletions(-)
create mode 100644 src/backend/libpq/be-secure-securetransport.c
create mode 100644 src/include/common/securetransport.h
create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c
diff --git a/configure b/configure
index 7ab3edcae5..b1073faa0f 100755
--- a/configure
+++ b/configure
@@ -707,6 +707,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
with_ldap
with_krb_srvnam
@@ -853,6 +854,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1553,6 +1555,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -7695,6 +7698,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
#
# SELinux
#
@@ -12777,6 +12815,25 @@ else
fi
+fi
+
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.in b/configure.in
index d242394778..c1f2fb8ce0 100644
--- a/configure.in
+++ b/configure.in
@@ -811,6 +811,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
#
# SELinux
#
@@ -1335,6 +1344,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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 95d090e72d..2e8bf78452 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -185,6 +185,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_gssapi = @with_gssapi@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 25af514fba..c8bbf477b5 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -51,6 +51,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LDFLAGS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-catalog-headers submake-utils-headers postgres $(POSTGRES_IMP)
@@ -60,7 +64,7 @@ ifneq ($(PORTNAME), win32)
ifneq ($(PORTNAME), aix)
postgres: $(OBJS)
- $(CC) $(CFLAGS) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@
+ $(CC) $(CFLAGS) -framework CoreFoundation -framework Security $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@
endif
endif
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..3f8a8b96cb 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000000..c64bda6da2
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1470 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future).
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interested to investigate.
+ *
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "common/base64.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err. Since we
+ * may have encountered an error due to memory pressure, we don't want to rely
+ * on dynamically allocating memory for this error message.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+static bool ssl_is_server_start;
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+/*
+ * SecIdentityCreate is not an exported Secure Transport API. There is however
+ * no exported Secure Transport function that can create an identity from a
+ * SecCertificateRef and a SecKeyRef without having either of them in a
+ * Keychain. This function is commonly used in open source projects (such as
+ * ffmpeg and mono f.e), but finding an alternative is a TODO.
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static bool load_key(char *name, CFArrayRef *out);
+static OSStatus load_keychain(char *name, CFArrayRef *keychains);
+static OSStatus load_certificate_file(char *name, CFArrayRef *cert_array);
+static OSStatus load_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+
+static int load_dh_file(char *filename, char **buf);
+static void load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl);
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+static void KeychainEnsureValid(CFArrayRef *keychains);
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize global Secure Transport structures (if any)
+ *
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ *
+ * Another reason to defer this until when in the backend is that Keychains
+ * are SQLite3 backed, and sqlite does not allow access across a fork. See
+ * https://sqlite.org/faq.html#q6 for more information.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+
+ ssl_is_server_start = isServerStart;
+
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * be_tls_open_server
+ * Attempt to negotiate a secure connection
+ *
+ * Unlike the OpenSSL backend, this function is responsible for the deferred
+ * loading of keys and certificates for use in the connection. See the comment
+ * on be_tls_init for further reasoning around this.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFArrayRef keychains = NULL;
+ CFMutableArrayRef chain;
+ char *dh_buf;
+ int dh_len;
+
+ Assert(!port->ssl);
+
+ if (ssl_keychain_file[0])
+ {
+ status = load_keychain(ssl_keychain_file, &keychains);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load keychain(s): \"%s\"",
+ pg_SSLerrmessage(status))));
+ }
+
+ /*
+ * If the ssl_cert_file is prefixed with a keychain reference, we will try
+ * to load a complete identity from the Keychain.
+ */
+ if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains);
+ else
+ {
+ status = load_certificate_file(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": \"%s\"",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ if (!load_key(ssl_key_file, &keys))
+ return -1;
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ }
+
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate_file(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": \"%s\"",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API
+ */
+ if (ssl_crl_file[0])
+ ereport(FATAL,
+ (errmsg("CRL files not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ /*
+ * We use kTryAuthenticate here since we don't know which sslmode the
+ * client is using. If we were to use kAlwaysAuthenticate then sslmode
+ * require won't work as intended.
+ */
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * In case the user hasn't configured a DH parameters file, we load a pre-
+ * computed DH parameter to avoid having Secure Transport computing one for
+ * us (which is done by default unless one is set).
+ */
+ dh_buf = NULL;
+ if (ssl_dh_params_file[0])
+ dh_len = load_dh_file(ssl_dh_params_file, &dh_buf);
+
+ if (!dh_buf || dh_len == 0)
+ {
+ dh_buf = pstrdup(FILE_DH2048);
+ dh_len = sizeof(FILE_DH2048);
+ }
+
+ load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl);
+
+ /*
+ * Set Tlsv1.2 as the minimum protocol version we allow for the connection
+ */
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * We can't initiate the SSL structures in _init as per how OpenSSL does
+ * it, so we can't use serverstart for the same error handling. We still
+ * use it for a few things so maintain it for a single instantiation.
+ * TODO: it would be neat if this hack isn't required, more thinking is
+ * required.
+ */
+ ssl_is_server_start = false;
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ if (status == errSSLWouldBlock)
+ continue;
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert;
+ CFStringRef usercert_cn;
+ const char *peer_cn;
+
+ usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+
+ /* Guard against empty/missing CNs */
+ peer_cn = CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8);
+ if (!peer_cn)
+ port->peer_cn = pstrdup("");
+ else
+ port->peer_cn = pstrdup(peer_cn);
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ */
+static bool
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ SecItemImportExportKeyParameters params;
+
+ if (!check_ssl_key_file_permissions(name, ssl_is_server_start))
+ return false;
+
+ /*
+ * check_ssl_key_file_permissions() has already checked the file for
+ * existence and correct permissions, but we still need to stat it to
+ * get the filesize.
+ */
+ if (stat(name, &stat_buf) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ /*
+ * There is no way to set a callback for acquiring the passphrase like how
+ * OpenSSL does it, so we need to re-run the import if it failed with a
+ * passphrase missing status. If no ssl_passphrase_command has been set we
+ * currently don't retry, which is something that will need to be revisited.
+ * TODO: figure out what would be the least confusing to the user here;
+ * perhaps supplying our own fallback passphrase_command? (which should be
+ * a TLS backend common function since it wouldn't be Secure Transport
+ * specific?)
+ */
+ if (status == errSecPassphraseRequired)
+ {
+ if ((ssl_is_server_start && ssl_passphrase_command[0]) ||
+ (!ssl_is_server_start && ssl_passphrase_command[0] && ssl_passphrase_command_supports_reload))
+ {
+ const char *prompt = "Enter PEM pass phrase: ";
+ char buf[256];
+ CFStringRef passphrase;
+
+ run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+
+ passphrase = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
+ params.passphrase = passphrase;
+ memset(&buf, '\0', sizeof(buf));
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(passphrase);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" cannot be loaded because it requires a passphrase",
+ name)));
+ }
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": \"%s\"",
+ name, pg_SSLerrmessage(status))));
+
+ return true;
+}
+
+/*
+ * load_keychain
+ * Open the specified, and default, Keychain
+ *
+ * Operations on keychains other than the default take the keychain references
+ * in an array. We need to copy a reference to the default keychain into the
+ * array to include it in the search.
+ *
+ * For server applications, we don't want modal dialog boxes opened for
+ * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE)
+ * will turn off all GUI interaction with the user, which may seem like what we
+ * want server side. This however has the side effect to turn off all GUI
+ * elements for all applications until some application calls
+ * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus
+ * remove wanted GUI interaction from another app, or another app might
+ * introduce it for us.
+ */
+static OSStatus
+load_keychain(char *name, CFArrayRef *keychains)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ SecKeychainRef kc[2];
+ int array_len = 1;
+
+ /*
+ * If we are passing in a non-empty CFArrayRef, and fail to load the user
+ * Keychain then we would risk injecting Keychains since we will trust this
+ * array from hereon. Unconditionally error out hard immediately to avoid.
+ */
+ if (*keychains != NULL)
+ ereport(FATAL,
+ (errmsg("Requesting to load Keychains into already allocated memory")));
+
+ if (stat(name, &stat_buf) != 0)
+ return errSecInvalidValue;
+
+ status = SecKeychainOpen(name, &kc[0]);
+ if (status == noErr)
+ {
+ SecKeychainUnlock(kc[0], 0, "", TRUE);
+
+ /*
+ * If we are allowed to use the default Keychain, add it to the array
+ * to include it in Keychain searches. If we are only using the default
+ * Keychain and no user defined Keychain we don't create an array at
+ * all since the recommended procedure is to pass NULL instead of an
+ * array containing only a reference to the default Keychain.
+ */
+ if (ssl_keychain_use_default)
+ SecKeychainCopyDefault(&kc[array_len++]);
+
+ *keychains = CFArrayCreate(NULL, (const void **) kc, array_len,
+ &kCFTypeArrayCallBacks);
+
+ if (!*keychains)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to allocate memory");
+ return errSecInternalError;
+ }
+ }
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+load_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /* Ensure we have a correctly set Keychains array */
+ KeychainEnsureValid(&keychains);
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+/*
+ * load_certificate_file
+ * Extracts a certificate from a PEM file on the filesystem
+ */
+static OSStatus
+load_certificate_file(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to find file");
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to open file for reading");
+ return errSecInternalError;
+ }
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to read file");
+ return errSecInternalError;
+ }
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL,
+ cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ snprintf(internal_err, ERR_MSG_LEN, "unable to open file for reading");
+ return errSecInternalError;
+}
+
+/*
+ * load_dh_file
+ * Slurp the contents of the specified file into buf
+ *
+ * Open the supplied filename and load its contents. This function is only
+ * reading the data without assessing its structure, actually parsing it is
+ * performed by load_dh_params(). The reason for splitting up the process is
+ * that we also support loading hardcoded DH params.
+ */
+static int
+load_dh_file(char *filename, char **buf)
+{
+ FILE *dh;
+ struct stat stat_buf;
+ int ret;
+
+ /*
+ * Open the DH file and slurp the contents. If the file doesn't exist it's
+ * not an error, if it can't be opened it is however an error.
+ */
+ ret = stat(filename, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ return 0;
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((dh = AllocateFile(filename, "r")) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+
+ *buf = palloc(stat_buf.st_size);
+ ret = fread(*buf, 1, stat_buf.st_size, dh);
+ FreeFile(dh);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read DH parameters file \"%s\": %m",
+ filename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("DH parameters file \"%s\" is not a regular file",
+ filename)));
+
+ return ret;
+}
+
+/*
+ * load_dh_params
+ * Load the specified DH params for the connection
+ *
+ * Secure Transport requires the DH params to be in DER format, but to be
+ * compatible with the OpenSSL code we also support PEM and convert to DER
+ * before loading. Conversion does rudimentary PEM parsing, if we miss the
+ * data being correct, the Secure Transport API will give an error anyways so
+ * we're just checking basic integrity.
+ *
+ * This function may scribble on the dh parameter so if that's required so stay
+ * intact in the caller, a copy should be sent.
+ */
+#define DH_HEADER "-----BEGIN DH PARAMETERS-----"
+#define DH_FOOTER "-----END DH PARAMETERS-----"
+static void
+load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl)
+{
+ OSStatus status;
+ char *der;
+ int der_len;
+
+ Assert(dh);
+
+ /* Convert PEM to DER */
+ if (is_pem)
+ {
+ char *head;
+ char *tail;
+ int pem_len = 0;
+
+ if (strstr(dh, DH_HEADER) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, header missing")));
+
+ dh += strlen(DH_HEADER);
+ tail = strstr(dh, DH_FOOTER);
+ if (!tail)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, footer missing")));
+ *tail = '\0';
+
+ /* In order to PEM convert it we need to remove all newlines */
+ head = dh;
+ tail = dh;
+ while (*head != '\0')
+ {
+ if (*head != '\n')
+ {
+ *tail++ = *head++;
+ pem_len++;
+ }
+ else
+ head++;
+ }
+ *tail = '\0';
+
+ der = palloc(pg_b64_dec_len(strlen(dh)) + 1);
+ der_len = pg_b64_decode(dh, strlen(dh), der);
+ der[der_len] = '\0';
+ }
+ else
+ {
+ der = dh;
+ der_len = len;
+ }
+
+ status = SSLSetDiffieHellmanParams(ssl, der, der_len);
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unable to load DH parameters: %s",
+ pg_SSLerrmessage(status))));
+}
+
+/*
+ * This function should return the expected TLS Finished message information
+ * from the client, but there is no API for this in Secure Transport. As a
+ * result, channel binding is not supported by Secure Transport.
+ */
+char *
+be_tls_get_peer_finished(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+/*
+ * be_tls_close
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect TLS version for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ if (protocol == kTLSProtocol11)
+ return "TLSv1.1";
+ else if (protocol == kTLSProtocol12)
+ return "TLSv1.2";
+
+ return "unknown";
+}
+
+/*
+ * be_tls_read
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * be_tls_write
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr && trust != NULL)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+ const char *cipher_name;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect cipher for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ cipher_name = pg_SSLciphername(cipher);
+ if (cipher_name == NULL)
+ elog(ERROR, "Unknown cipher detected");
+
+ return cipher_name;
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ /*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there still is no translation for it in 10.11 El Capitan, so we
+ * maintain our own.
+ */
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * The Secure Transport supplied error string for invalid passphrase only
+ * reads "invalid attribute" without any reference to a passphrase, which
+ * can be confusing. Override with our own.
+ */
+ if (status == errSecInvalidAttributePassphrase)
+ return pstrdup(_("Incorrect passphrase."));
+
+ if (status == errSSLRecordOverflow)
+ return pstrdup(_("SSL error"));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Misc */
+/* ------------------------------------------------------------ */
+
+/*
+ * KeychainEnsureValid
+ * Ensure the validity of using the passed Keychain array
+ *
+ * Any consumers of the Keychain array should always call this to ensure that
+ * it is set up in a manner that reflect the configuration. If it not, then
+ * this function will fatally error out as all codepaths should assume that
+ * setup has been done correctly and that there is no recovery in case it
+ * hasn't.
+ */
+static void
+KeychainEnsureValid(CFArrayRef *keychains)
+{
+ int keychain_count;
+
+ /*
+ * If the keychain array is unallocated, we must be allowed to use the
+ * default user Keychain, as that will be the effect of passing NULL to the
+ * Keychain search API. If not, error out.
+ */
+ if (*keychains == NULL)
+ {
+ if (!ssl_keychain_use_default)
+ goto error;
+
+ return;
+ }
+
+ keychain_count = CFArrayGetCount(*keychains);
+
+ /*
+ * If we have one Keychain loaded then we must have a Keychain file
+ * configured, and not be allowed to use the default Keychain. If we have
+ * two then we must have a Keychain file configured *and* be allowed to use
+ * the default user Keychain. If we have any other number of Keychains in
+ * the array then we definitely have an incorrect situation.
+ */
+ if (keychain_count == 1)
+ {
+ if (ssl_keychain_file[0] == '\0')
+ goto error;
+
+ return;
+ }
+ else if (keychain_count == 2)
+ {
+ if (ssl_keychain_file[0] == '\0' || !ssl_keychain_use_default)
+ goto error;
+
+ return;
+ }
+ else
+ /* Fallthrough to erroring out */
+
+error:
+ ereport(FATAL,
+ (errmsg("Incorrect loading of Keychains detected")));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index edfe2c0751..459f440727 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -45,6 +45,10 @@ char *ssl_crl_file;
char *ssl_dh_params_file;
char *ssl_passphrase_command;
bool ssl_passphrase_command_supports_reload;
+#ifdef USE_SECURETRANSPORT
+char *ssl_keychain_file;
+bool ssl_keychain_use_default = false;
+#endif
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index b05fb209bb..af761fafb5 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -503,6 +503,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -1822,6 +1823,17 @@ static struct config_bool ConfigureNamesBool[] =
true,
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_use_default", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Allow using the default Keychain of the current user."),
+ NULL
+ },
+ &ssl_keychain_use_default,
+ false,
+ NULL, NULL, NULL
+ },
+#endif
/* End-of-list marker */
{
@@ -3508,6 +3520,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
{
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
@@ -3755,6 +3779,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the Keychain file."),
+ NULL
+ },
+ &ssl_keychain_file,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 9e39baf466..d4715a1671 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -106,6 +106,7 @@
#ssl_dh_params_file = ''
#ssl_passphrase_command = ''
#ssl_passphrase_command_supports_reload = off
+#ssl_keychain_file = ''
#------------------------------------------------------------------------------
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000000..d3a38e2a5c
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,514 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport_common.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+#ifndef FRONTEND
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement. This
+ * function is currently only used in the libpq backend.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0d28..6d35f47864 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7698cd1f88..9ba5b6f46c 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,12 +191,15 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+ void *ssl;
+ int ssl_buffered;
#endif
} Port;
@@ -214,7 +225,7 @@ CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
/*
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 7bf06c65e9..907e22720c 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -82,6 +82,10 @@ extern char *ssl_crl_file;
extern char *ssl_dh_params_file;
extern char *ssl_passphrase_command;
extern bool ssl_passphrase_command_supports_reload;
+#ifdef USE_SECURETRANSPORT
+extern char *ssl_keychain_file;
+extern bool ssl_keychain_use_default;
+#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 89b8804251..2db5405900 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -907,6 +907,9 @@
/* Define to use OpenSSL for random number generation */
#undef USE_OPENSSL_RANDOM
+/* Define to build with Apple Secure Transport support. (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index b309395f11..5469473d72 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -162,11 +162,17 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
+#else
+#define SSL_LIBRARY "None"
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index abe0a50e98..9bc7989bab 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,11 @@ else
OBJS += sha2.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -112,7 +117,6 @@ ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
-
distprep: libpq-dist.rc
libpq.rc libpq-dist.rc: libpq.rc.in
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a7e969d7c1..e363a54dec 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -330,6 +330,16 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain_use_default", NULL, NULL, NULL,
+ "UseDefaultKeychain", "", 1,
+ offsetof(struct pg_conn, keychain_use_default)},
+
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -3360,6 +3370,9 @@ makeEmptyPGconn(void)
conn->allow_ssl_try = true;
conn->wait_ssl_try = false;
conn->ssl_in_use = false;
+#ifdef USE_SECURETRANSPORT
+ conn->keychain_use_default = true;
+#endif
#endif
/*
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000000..db323c5ce1
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1494 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readiblitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *common_name,
+ SecCertificateRef *certificate,
+ CFArrayRef keychains,
+ char *hostname);
+static OSStatus import_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+static OSStatus import_pem(const char *path, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * Exported function to allow application to tell us it's already initialized
+ * Secure Transport and/or libcrypto. Since the current implementation only
+ * allow do_crypto be set for the OpenSSL backend, we should always get the
+ * same value passed in both variables.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ Assert(do_ssl ? do_crypto : !do_crypto);
+ return;
+}
+
+/*
+ * pgtls_open_client
+ * Begin, or continue, negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin(conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection(conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs(conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we dont have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ SSLSetPeerDomainName(conn->ssl, conn->pghost, strlen(conn->pghost));
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption(conn->ssl, kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake(conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust(conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ if (!trusted)
+ return errSecNoAccessForItem;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * pgtls_read_pending
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize(conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead(conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * pgtls_write
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite(conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite(conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+char *
+pgtls_get_finished(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"%s\" is not supported by this build\n"),
+ conn->scram_channel_binding);
+ return NULL;
+}
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"%s\" is not supported by this build\n"),
+ conn->scram_channel_binding);
+ return NULL;
+}
+
+/*
+ * pgtls_init
+ * Initialize SSL system.
+ *
+ * There is little state or context to initialize for Secure Transport, the
+ * heavy lifting is performed by pgtls_open_client.
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose(conn->ssl);
+ CFRelease(conn->ssl);
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * pg_SSLSocketRead
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+import_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ SecIdentityRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * We don't need to set a kSecMatchLimit key since the default is to only
+ * return a single reference. Older versions of macOS had issues with
+ * certificate matching on labels, but we don't support older versions so
+ * no need to extract all and match ourselves.
+ */
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+
+ if (status == noErr)
+ *identity = (SecIdentityRef) CFRetain(temp);
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_certificate_keychain(const char *common_name, SecCertificateRef *certificate,
+ CFArrayRef keychains, char *hostname)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFStringRef host = NULL;
+ CFArrayRef temp;
+ SecPolicyRef ssl_policy;
+ int i;
+
+ /*
+ * Make sure the user didn't just specify the keychain prefix as the
+ * certificate config. The passed certificate will have the keychain
+ * prefix stripped so in that case the string is expected to be empty.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ /*
+ * If we didn't get a set of Keychains passed, skip adding it to the
+ * dictionary thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * Specifying a hostname requires it to match the hostname in the leaf
+ * certificate.
+ */
+ if (hostname)
+ host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8);
+ ssl_policy = SecPolicyCreateSSL(true, host);
+ CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label has been reported to not work (revisions of 10.12), we request
+ * all and find the one we want. Copy all the results to a temp array and
+ * scan it for the certificate we are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i);
+
+ if (search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ *certificate = (SecCertificateRef) CFRetain(search_cert);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(ssl_policy);
+ CFRelease(query);
+ CFRelease(cert);
+ if (host)
+ CFRelease(host);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, char *passphrase, CFArrayRef *certificate)
+{
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+ struct stat buf;
+
+ if (!path || strlen(path) == 0)
+ return errSecInvalidValue;
+
+ if (stat(path, &buf) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return -1;
+
+ return errSecInvalidValue;
+ }
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInvalidValue;
+
+ certdata = malloc(buf.st_size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, buf.st_size, fp) != buf.st_size)
+ {
+ fclose(fp);
+ free(certdata);
+ return errSSLBadCert;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, buf.st_size);
+ free(certdata);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecAllocate;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ return SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ bool have_homedir;
+ bool cert_from_file = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecCertificateRef root_ref[1];
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+ SecKeychainRef kcref[2];
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching. If no additional keychain is specified we don't need to open
+ * the default keychain and pass to searches since Secure Transport will
+ * use the default when passing NULL instead of an array of Keychain refs.
+ */
+ if (conn->keychain)
+ {
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr && kcref[0] != NULL)
+ {
+ SecKeychainStatus kcstatus;
+ status = SecKeychainGetStatus(kcref[0], &kcstatus);
+ if (status == noErr)
+ {
+ switch (kcstatus)
+ {
+ /*
+ * If the Keychain is already unlocked, readable or
+ * writeable, we don't need to do more. If not, try to
+ * unlock it.
+ */
+ case kSecUnlockStateStatus:
+ case kSecReadPermStatus:
+ case kSecWritePermStatus:
+ break;
+ default:
+ /*
+ * TODO: we need to get the passphrase from the
+ * user, but we currently don't have a good
+ * mechanism for that. For now, we assume a blank
+ * passphrase but we need to figure out a good way
+ * to have the user enter the passphrase.
+ */
+ SecKeychainUnlock(kcref[0], 0, "", TRUE);
+ break;
+ }
+ }
+
+ /*
+ * We only need to open, and add, the default Keychain in case
+ * have a user keychain opened, else we will pass NULL to any
+ * keychain search which will use the default keychain by..
+ * default.
+ */
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the files
+ * could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Try to load the root cert from either a user defined keychain or the
+ * default Keychain in case none is specified
+ */
+ if (conn->sslrootcert &&
+ pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ root_ref[0] = NULL;
+ strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL);
+
+ if (root_ref[0])
+ conn->st_rootcert = (void *) CFArrayCreate(NULL,
+ (const void **) root_ref,
+ 1, &kCFTypeArrayCallBacks);
+ }
+
+ if (!conn->st_rootcert)
+ {
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+ }
+
+ /*
+ * If the sslcert config is prefixed with a keychain identifier, the cert
+ * must be located in either the default or the specified keychain.
+ */
+ if (conn->sslcert &&
+ pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ status = import_identity_keychain(fnbuf, &identity, keychains);
+
+ if (identity && status == noErr)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ else
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate \"%s\" from keychain: %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+
+ return status;
+ }
+ }
+ /*
+ * No prefix on the sslcert config, the certificate must thus reside in a
+ * file on disk.
+ */
+ else
+ {
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, cert_array);
+ if (status != noErr)
+ {
+ if (status == -1)
+ return noErr;
+
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ cert_from_file = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in
+ * order to create the identity.
+ */
+
+ /*
+ * The sslkey config is prefixed with keychain: indicating that the
+ * key should be loaded from Keychain instead of the filesystem.
+ * Search for the private key matching the cert_ref in the opened
+ * Keychains. If found, we get the identity returned.
+ */
+ if (conn->sslkey &&
+ pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref,
+ &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the
+ * key to be loaded from that file.
+ */
+ if (fnbuf[0] != '\0')
+ {
+
+ if (stat(fnbuf, &buf) != 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+ fnbuf);
+ return errSecInvalidKeyRef;
+ }
+#ifndef WIN32
+ if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
+ fnbuf);
+ return errSecInvalidKeyRef;
+ }
+#endif
+ status = import_pem(fnbuf, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+ }
+ }
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ if (key_ref)
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ }
+
+ if (!identity)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create identity from certificate/key\n"));
+ return errSecInvalidValue;
+ }
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (cert_from_file)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate(conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher(conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion(conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState(conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249bf0..5fa6734643 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -157,8 +157,10 @@ void
PQinitOpenSSL(int do_ssl, int do_crypto)
{
#ifdef USE_SSL
+#ifdef USE_OPENSSL
pgtls_init_library(do_ssl, do_crypto);
#endif
+#endif
}
/*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9a586ff25a..06d2825880 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -80,6 +80,14 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_SECURETRANSPORT
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#undef Size
+#undef uint64
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -470,8 +478,19 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+ bool keychain_use_default;
+
+ SSLContextRef ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index df477f1d40..0c9b4cdddd 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_securetransport
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -32,7 +33,7 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
# This target generates all the key and certificate files.
sslfiles: $(SSLFILES)
-# Openssl requires a directory to put all generated certificates in. We don't
+# 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:
mkdir ssl/new_certs_dir
@@ -130,6 +131,20 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### Keychains
+
+ifeq ($(with_securetransport),yes)
+
+KEYCHAINS := ssl/client.keychain
+
+# This target generates all Keychains
+keychains: $(KEYCHAINS)
+
+PWD=$(shell pwd)
+ssl/client.keychain: ssl/client.crt ssl/client.key
+ certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p=
+endif
+
.PHONY: sslfiles-clean
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
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index 1cd3badaa1..b068671348 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -26,12 +26,22 @@ use Test::More;
use Exporter 'import';
our @EXPORT = qw(
+ set_backend
configure_test_server_for_ssl
switch_server_cert
test_connect_fails
test_connect_ok
);
+our $current_backend;
+
+sub set_backend
+{
+ my ($backend) = @_;
+
+ $current_backend = $backend;
+}
+
# Define a couple of helper functions to test connecting to the server.
# The first argument is a base connection string to use for connection.
@@ -52,7 +62,7 @@ sub test_connect_ok
sub test_connect_fails
{
- my ($common_connstr, $connstr, $expected_stderr, $test_name) = @_;
+ my ($common_connstr, $connstr, $test_name, %expected_stderr) = @_;
my $cmd = [
'psql', '-X', '-A', '-t', '-c',
@@ -60,7 +70,7 @@ sub test_connect_fails
'-d', "$common_connstr $connstr"
];
- command_fails_like($cmd, $expected_stderr, $test_name);
+ command_fails_like($cmd, $expected_stderr{$current_backend}, $test_name);
return;
}
@@ -151,7 +161,14 @@ sub switch_server_cert
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";
+ if (check_pg_config("#define USE_SECURETRANSPORT 1"))
+ {
+ print $sslconf "ssl_crl_file=''\n";
+ }
+ else
+ {
+ print $sslconf "ssl_crl_file='root+client.crl'\n";
+ }
close $sslconf;
$node->restart;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index e550207454..8ccc0e5688 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -6,9 +6,32 @@ use Test::More;
use ServerSetup;
use File::Copy;
+# Some tests are backend specific, so store the currently used backend and
+# some particular capabilities which are interesting for the test
+my %tls_backend;
+
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 64;
+ $tls_backend{library} = 'openssl';
+ $tls_backend{tests} = 64;
+ $tls_backend{crl_support} = 1;
+ $tls_backend{keychain_support} = 0;
+}
+elsif ($ENV{with_securetransport} eq 'yes')
+{
+ $tls_backend{library} = 'securetransport';
+ $tls_backend{tests} = 67;
+ $tls_backend{crl_support} = 0;
+ $tls_backend{keychain_support} = 1;
+}
+else
+{
+ $tls_backend{library} = undef;
+}
+
+if (defined $tls_backend{library})
+{
+ plan tests => $tls_backend{tests};
}
else
{
@@ -25,6 +48,8 @@ my $SERVERHOSTADDR = '127.0.0.1';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
+set_backend($tls_backend{library});
+
# The client's private key must not be world-readable, so take a copy
# of the key stored in the code tree and update its permissions.
copy("ssl/client.key", "ssl/client_tmp.key");
@@ -60,10 +85,28 @@ 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);
+if ($tls_backend{library} eq 'securetransport')
+{
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=invalid sslmode=require",
+ "connect without server root cert sslmode=require",
+ ( securetransport => qr/record overflow/ ));
+}
+else
+{
+ 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";
@@ -93,8 +136,8 @@ $common_connstr =
# The server should not accept non-SSL connections.
test_connect_fails(
$common_connstr, "sslmode=disable",
- qr/\Qno pg_hba.conf entry\E/,
- "server doesn't accept non-SSL connections");
+ "server doesn't accept non-SSL connections",
+ ( openssl => qr/\Qno pg_hba.conf entry\E/ ,securetransport => qr/\Qno pg_hba.conf entry\E/ ));
# Try without a root cert. In sslmode=require, this should work. In verify-ca
# or verify-full mode it should fail.
@@ -105,31 +148,50 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
- "connect without server root cert sslmode=verify-ca");
+ "connect without server root cert sslmode=verify-ca",
+ (
+ openssl => qr/root certificate file "invalid" does not exist/,
+ securetransport => qr/root certificate file "invalid" does not exist/
+ ));
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
- "connect without server root cert sslmode=verify-full");
+ "connect without server root cert sslmode=verify-full",
+ (
+ openssl => qr/root certificate file "invalid" does not exist/,
+ securetransport => qr/root certificate file "invalid" does not exist/
+ ));
# 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");
+ "connect with wrong server root cert sslmode=require",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
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");
+ "connect with wrong server root cert sslmode=verify-ca",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
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");
+ "connect with wrong server root cert sslmode=verify-full",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
# 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");
+ "connect with server CA cert, without root CA",
+ ( openssl => qr/SSL error/, securetransport => qr/SSL error/));
# And finally, with the correct root cert.
test_connect_ok(
@@ -158,24 +220,36 @@ test_connect_ok(
# 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",
- "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");
-
-# 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",
- "CRL with a non-revoked cert");
+if ($tls_backend{crl_support})
+{
+ # 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",
+ "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",
+ "CRL belonging to a different CA",
+ ( openssl => qr/SSL error/ ));
+
+ # 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",
+ "CRL with a non-revoked cert");
+}
+else
+{
+ # Test that the presence of a CRL configuration throws an error
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "unsupported sslcrl option",
+ ( securetransport => qr/CRL files are not supported/ ));
+}
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
@@ -193,8 +267,11 @@ 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/,
- "mismatch between host name and server certificate sslmode=verify-full");
+ "mismatch between host name and server certificate sslmode=verify-full",
+ (
+ openssl => qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
@@ -218,13 +295,19 @@ 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/,
- "host name not matching with X.509 Subject Alternative Names");
+ "host name not matching with X.509 Subject Alternative Names",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
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/,
- "host name not matching with X.509 Subject Alternative Names wildcard");
+ "host name not matching with X.509 Subject Alternative Names wildcard",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
@@ -241,14 +324,19 @@ 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/,
- "host name not matching with a single X.509 Subject Alternative Name");
+ "host name not matching with a single X.509 Subject Alternative Name",
+ (
+ openssl => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
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/,
- "host name not matching with a single X.509 Subject Alternative Name wildcard"
-);
+ "host name not matching with a single X.509 Subject Alternative Name wildcard",
+ (
+ openssl => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
@@ -268,8 +356,11 @@ 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/,
- "certificate with both a CN and SANs ignores CN");
+ "certificate with both a CN and SANs ignores CN",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
# 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.
@@ -284,8 +375,11 @@ 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/,
- "server certificate without CN or SANs sslmode=verify-full");
+ "server certificate without CN or SANs sslmode=verify-full",
+ (
+ openssl => qr/could not get server's host name from server certificate/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test that the CRL works
switch_server_cert($node, 'server-revoked');
@@ -298,11 +392,46 @@ test_connect_ok(
$common_connstr,
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
"connects without client-side CRL");
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
- qr/SSL error/,
- "does not connect with client-side CRL");
+if ($tls_backend{crl_support})
+{
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL",
+ ( openssl => qr/SSL error/ ));
+}
+else
+{
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL",
+ ( securetransport => qr/CRL files are not supported with Secure Transport/ ));
+}
+
+# test Secure Transport keychain support
+if ($tls_backend{keychain_support})
+{
+ $common_connstr =
+ "user=ssltestuser dbname=certdb hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ # empty keychain
+ test_connect_fails($common_connstr,
+ "keychain=invalid",
+ "invalid Keychain file reference",
+ ( securetransport => qr/The specified item has no access control/ ));
+
+ # correct client cert in keychain with and without proper label
+ test_connect_fails(
+ $common_connstr,
+ "keychain=ssl/client.keychain",
+ "client cert in keychain but without label",
+ ( securetransport => qr/The specified item has no access control/ ));
+ test_connect_ok(
+ $common_connstr,
+ "sslcert=keychain:ssltestuser keychain=ssl/client.keychain",
+ "client cert in keychain");
+}
+
### Server-side tests.
###
@@ -317,8 +446,11 @@ $common_connstr =
test_connect_fails(
$common_connstr,
"user=ssltestuser sslcert=invalid",
- qr/connection requires a valid client certificate/,
- "certificate authorization fails without client cert");
+ "certificate authorization fails without client cert",
+ (
+ openssl => qr/connection requires a valid client certificate/,
+ securetransport => qr/connection requires a valid client certificate/
+ ));
# correct client cert
test_connect_ok(
@@ -330,23 +462,30 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
- qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
- "certificate authorization fails because of file permissions");
+ "certificate authorization fails because of file permissions",
+ (
+ openssl => qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
+ securetransport => qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!
+ ));
# client cert belonging to another user
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
- "certificate authorization fails with client cert belonging to another user"
-);
+ "certificate authorization fails with client cert belonging to another user",
+ (
+ openssl => qr/certificate authentication failed for user "anotheruser"/,
+ securetransport => qr/certificate authentication failed for user "anotheruser"/
+ ));
-# revoked client cert
-test_connect_fails(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
- qr/SSL error/,
- "certificate authorization fails with revoked client cert");
+if ($tls_backend{crl_support})
+{
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "certificate authorization fails with revoked client cert",
+ ( openssl => qr/SSL error/, securetransport => qr/SSL error/ ));
+}
# 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');
@@ -358,8 +497,13 @@ test_connect_ok(
"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");
+ "intermediate client certificate is missing",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/certificate authentication failed/
+ ));
# clean up
unlink("ssl/client_tmp.key", "ssl/client_wrongperms_tmp.key",
"ssl/client-revoked_tmp.key");
+unlink("ssl/client.keychain") if ($tls_backend{keychain_support});
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 52a8f458cb..f18d1fdb26 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -22,6 +22,9 @@ my $SERVERHOSTADDR = '127.0.0.1';
my $supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+my $supports_tls_unique =
+ ! check_pg_config("#define USE_SECURETRANSPORT 1");
+
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -45,17 +48,34 @@ $ENV{PGPASSWORD} = "pass";
$common_connstr =
"user=ssltestuser dbname=trustdb sslmode=require hostaddr=$SERVERHOSTADDR";
-# Default settings
-test_connect_ok($common_connstr, '',
- "SCRAM authentication with default channel binding");
-
# Channel binding settings
-test_connect_ok(
- $common_connstr,
- "scram_channel_binding=tls-unique",
- "SCRAM authentication with tls-unique as channel binding");
+if ($supports_tls_unique)
+{
+ # Default settings
+ test_connect_ok($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ # Channel binding settings
+ test_connect_ok(
+ $common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
+else
+{
+ # Default settings
+ test_connect_fails($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ # Channel binding settings
+ test_connect_fails(
+ $common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
test_connect_ok($common_connstr, "scram_channel_binding=''",
"SCRAM authentication without channel binding");
+
if ($supports_tls_server_end_point)
{
test_connect_ok(
--
2.14.1.145.gb3622a4ee
On 27 Jun 2018, at 14:32, Daniel Gustafsson <daniel@yesql.se> wrote:
Attached is an updated patch for supporting the native macOS Secure Transport
library, rebased on top of current master.
Courtesy of the ever-present Murphy I managed to forget some testcode in
src/backend/Makefile which broke compilation for builds without secure
transport, attached v8 patch fixes that.
cheers ./daniel
Attachments:
0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v8.patchapplication/octet-stream; name=0001-WIP-Add-support-for-Apple-Secure-Transport-SSL-li-v8.patch; x-unix-mode=0644Download
From b0577ffed78b8d117a6ab037e7aa2a5826107630 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 27 Jun 2018 12:04:48 +0200
Subject: [PATCH] WIP: Add support for Apple Secure Transport SSL library
This adds frontend and backend support for the Secure Transport SSL
library on macOS, but currently lacks SCRAM support. Thus, the patch
is not in a final state but is provided for hacking and testing.
---
configure | 57 +
configure.in | 14 +
src/Makefile.global.in | 1 +
src/backend/Makefile | 4 +
src/backend/libpq/Makefile | 5 +
src/backend/libpq/be-secure-securetransport.c | 1470 +++++++++++++++++++++
src/backend/libpq/be-secure.c | 4 +
src/backend/utils/misc/guc.c | 36 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/common/securetransport.h | 514 ++++++++
src/include/common/sha2.h | 4 +-
src/include/libpq/libpq-be.h | 17 +-
src/include/libpq/libpq.h | 4 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 12 +-
src/interfaces/libpq/Makefile | 6 +-
src/interfaces/libpq/fe-connect.c | 13 +
src/interfaces/libpq/fe-secure-securetransport.c | 1494 ++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 2 +
src/interfaces/libpq/libpq-int.h | 23 +-
src/test/ssl/Makefile | 17 +-
src/test/ssl/ServerSetup.pm | 23 +-
src/test/ssl/t/001_ssltests.pl | 278 +++-
src/test/ssl/t/002_scram.pl | 36 +-
24 files changed, 3948 insertions(+), 90 deletions(-)
create mode 100644 src/backend/libpq/be-secure-securetransport.c
create mode 100644 src/include/common/securetransport.h
create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c
diff --git a/configure b/configure
index 7ab3edcae5..b1073faa0f 100755
--- a/configure
+++ b/configure
@@ -707,6 +707,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
with_ldap
with_krb_srvnam
@@ -853,6 +854,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1553,6 +1555,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -7695,6 +7698,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
#
# SELinux
#
@@ -12777,6 +12815,25 @@ else
fi
+fi
+
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.in b/configure.in
index d242394778..c1f2fb8ce0 100644
--- a/configure.in
+++ b/configure.in
@@ -811,6 +811,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
#
# SELinux
#
@@ -1335,6 +1344,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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 95d090e72d..2e8bf78452 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -185,6 +185,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_gssapi = @with_gssapi@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 25af514fba..24ac3587d9 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -51,6 +51,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LIBS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-catalog-headers submake-utils-headers postgres $(POSTGRES_IMP)
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..3f8a8b96cb 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000000..c64bda6da2
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1470 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future).
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interested to investigate.
+ *
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "common/base64.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err. Since we
+ * may have encountered an error due to memory pressure, we don't want to rely
+ * on dynamically allocating memory for this error message.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+static bool ssl_is_server_start;
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+/*
+ * SecIdentityCreate is not an exported Secure Transport API. There is however
+ * no exported Secure Transport function that can create an identity from a
+ * SecCertificateRef and a SecKeyRef without having either of them in a
+ * Keychain. This function is commonly used in open source projects (such as
+ * ffmpeg and mono f.e), but finding an alternative is a TODO.
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static bool load_key(char *name, CFArrayRef *out);
+static OSStatus load_keychain(char *name, CFArrayRef *keychains);
+static OSStatus load_certificate_file(char *name, CFArrayRef *cert_array);
+static OSStatus load_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+
+static int load_dh_file(char *filename, char **buf);
+static void load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl);
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+static void KeychainEnsureValid(CFArrayRef *keychains);
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize global Secure Transport structures (if any)
+ *
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ *
+ * Another reason to defer this until when in the backend is that Keychains
+ * are SQLite3 backed, and sqlite does not allow access across a fork. See
+ * https://sqlite.org/faq.html#q6 for more information.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+
+ ssl_is_server_start = isServerStart;
+
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * be_tls_open_server
+ * Attempt to negotiate a secure connection
+ *
+ * Unlike the OpenSSL backend, this function is responsible for the deferred
+ * loading of keys and certificates for use in the connection. See the comment
+ * on be_tls_init for further reasoning around this.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFArrayRef keychains = NULL;
+ CFMutableArrayRef chain;
+ char *dh_buf;
+ int dh_len;
+
+ Assert(!port->ssl);
+
+ if (ssl_keychain_file[0])
+ {
+ status = load_keychain(ssl_keychain_file, &keychains);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load keychain(s): \"%s\"",
+ pg_SSLerrmessage(status))));
+ }
+
+ /*
+ * If the ssl_cert_file is prefixed with a keychain reference, we will try
+ * to load a complete identity from the Keychain.
+ */
+ if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains);
+ else
+ {
+ status = load_certificate_file(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": \"%s\"",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ if (!load_key(ssl_key_file, &keys))
+ return -1;
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ }
+
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate_file(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": \"%s\"",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API
+ */
+ if (ssl_crl_file[0])
+ ereport(FATAL,
+ (errmsg("CRL files not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ /*
+ * We use kTryAuthenticate here since we don't know which sslmode the
+ * client is using. If we were to use kAlwaysAuthenticate then sslmode
+ * require won't work as intended.
+ */
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * In case the user hasn't configured a DH parameters file, we load a pre-
+ * computed DH parameter to avoid having Secure Transport computing one for
+ * us (which is done by default unless one is set).
+ */
+ dh_buf = NULL;
+ if (ssl_dh_params_file[0])
+ dh_len = load_dh_file(ssl_dh_params_file, &dh_buf);
+
+ if (!dh_buf || dh_len == 0)
+ {
+ dh_buf = pstrdup(FILE_DH2048);
+ dh_len = sizeof(FILE_DH2048);
+ }
+
+ load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl);
+
+ /*
+ * Set Tlsv1.2 as the minimum protocol version we allow for the connection
+ */
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * We can't initiate the SSL structures in _init as per how OpenSSL does
+ * it, so we can't use serverstart for the same error handling. We still
+ * use it for a few things so maintain it for a single instantiation.
+ * TODO: it would be neat if this hack isn't required, more thinking is
+ * required.
+ */
+ ssl_is_server_start = false;
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ if (status == errSSLWouldBlock)
+ continue;
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert;
+ CFStringRef usercert_cn;
+ const char *peer_cn;
+
+ usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+
+ /* Guard against empty/missing CNs */
+ peer_cn = CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8);
+ if (!peer_cn)
+ port->peer_cn = pstrdup("");
+ else
+ port->peer_cn = pstrdup(peer_cn);
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ */
+static bool
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ SecItemImportExportKeyParameters params;
+
+ if (!check_ssl_key_file_permissions(name, ssl_is_server_start))
+ return false;
+
+ /*
+ * check_ssl_key_file_permissions() has already checked the file for
+ * existence and correct permissions, but we still need to stat it to
+ * get the filesize.
+ */
+ if (stat(name, &stat_buf) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ /*
+ * There is no way to set a callback for acquiring the passphrase like how
+ * OpenSSL does it, so we need to re-run the import if it failed with a
+ * passphrase missing status. If no ssl_passphrase_command has been set we
+ * currently don't retry, which is something that will need to be revisited.
+ * TODO: figure out what would be the least confusing to the user here;
+ * perhaps supplying our own fallback passphrase_command? (which should be
+ * a TLS backend common function since it wouldn't be Secure Transport
+ * specific?)
+ */
+ if (status == errSecPassphraseRequired)
+ {
+ if ((ssl_is_server_start && ssl_passphrase_command[0]) ||
+ (!ssl_is_server_start && ssl_passphrase_command[0] && ssl_passphrase_command_supports_reload))
+ {
+ const char *prompt = "Enter PEM pass phrase: ";
+ char buf[256];
+ CFStringRef passphrase;
+
+ run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+
+ passphrase = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
+ params.passphrase = passphrase;
+ memset(&buf, '\0', sizeof(buf));
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(passphrase);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" cannot be loaded because it requires a passphrase",
+ name)));
+ }
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": \"%s\"",
+ name, pg_SSLerrmessage(status))));
+
+ return true;
+}
+
+/*
+ * load_keychain
+ * Open the specified, and default, Keychain
+ *
+ * Operations on keychains other than the default take the keychain references
+ * in an array. We need to copy a reference to the default keychain into the
+ * array to include it in the search.
+ *
+ * For server applications, we don't want modal dialog boxes opened for
+ * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE)
+ * will turn off all GUI interaction with the user, which may seem like what we
+ * want server side. This however has the side effect to turn off all GUI
+ * elements for all applications until some application calls
+ * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus
+ * remove wanted GUI interaction from another app, or another app might
+ * introduce it for us.
+ */
+static OSStatus
+load_keychain(char *name, CFArrayRef *keychains)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ SecKeychainRef kc[2];
+ int array_len = 1;
+
+ /*
+ * If we are passing in a non-empty CFArrayRef, and fail to load the user
+ * Keychain then we would risk injecting Keychains since we will trust this
+ * array from hereon. Unconditionally error out hard immediately to avoid.
+ */
+ if (*keychains != NULL)
+ ereport(FATAL,
+ (errmsg("Requesting to load Keychains into already allocated memory")));
+
+ if (stat(name, &stat_buf) != 0)
+ return errSecInvalidValue;
+
+ status = SecKeychainOpen(name, &kc[0]);
+ if (status == noErr)
+ {
+ SecKeychainUnlock(kc[0], 0, "", TRUE);
+
+ /*
+ * If we are allowed to use the default Keychain, add it to the array
+ * to include it in Keychain searches. If we are only using the default
+ * Keychain and no user defined Keychain we don't create an array at
+ * all since the recommended procedure is to pass NULL instead of an
+ * array containing only a reference to the default Keychain.
+ */
+ if (ssl_keychain_use_default)
+ SecKeychainCopyDefault(&kc[array_len++]);
+
+ *keychains = CFArrayCreate(NULL, (const void **) kc, array_len,
+ &kCFTypeArrayCallBacks);
+
+ if (!*keychains)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to allocate memory");
+ return errSecInternalError;
+ }
+ }
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+load_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /* Ensure we have a correctly set Keychains array */
+ KeychainEnsureValid(&keychains);
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+/*
+ * load_certificate_file
+ * Extracts a certificate from a PEM file on the filesystem
+ */
+static OSStatus
+load_certificate_file(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to find file");
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to open file for reading");
+ return errSecInternalError;
+ }
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to read file");
+ return errSecInternalError;
+ }
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL,
+ cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ snprintf(internal_err, ERR_MSG_LEN, "unable to open file for reading");
+ return errSecInternalError;
+}
+
+/*
+ * load_dh_file
+ * Slurp the contents of the specified file into buf
+ *
+ * Open the supplied filename and load its contents. This function is only
+ * reading the data without assessing its structure, actually parsing it is
+ * performed by load_dh_params(). The reason for splitting up the process is
+ * that we also support loading hardcoded DH params.
+ */
+static int
+load_dh_file(char *filename, char **buf)
+{
+ FILE *dh;
+ struct stat stat_buf;
+ int ret;
+
+ /*
+ * Open the DH file and slurp the contents. If the file doesn't exist it's
+ * not an error, if it can't be opened it is however an error.
+ */
+ ret = stat(filename, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ return 0;
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((dh = AllocateFile(filename, "r")) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+
+ *buf = palloc(stat_buf.st_size);
+ ret = fread(*buf, 1, stat_buf.st_size, dh);
+ FreeFile(dh);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read DH parameters file \"%s\": %m",
+ filename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("DH parameters file \"%s\" is not a regular file",
+ filename)));
+
+ return ret;
+}
+
+/*
+ * load_dh_params
+ * Load the specified DH params for the connection
+ *
+ * Secure Transport requires the DH params to be in DER format, but to be
+ * compatible with the OpenSSL code we also support PEM and convert to DER
+ * before loading. Conversion does rudimentary PEM parsing, if we miss the
+ * data being correct, the Secure Transport API will give an error anyways so
+ * we're just checking basic integrity.
+ *
+ * This function may scribble on the dh parameter so if that's required so stay
+ * intact in the caller, a copy should be sent.
+ */
+#define DH_HEADER "-----BEGIN DH PARAMETERS-----"
+#define DH_FOOTER "-----END DH PARAMETERS-----"
+static void
+load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl)
+{
+ OSStatus status;
+ char *der;
+ int der_len;
+
+ Assert(dh);
+
+ /* Convert PEM to DER */
+ if (is_pem)
+ {
+ char *head;
+ char *tail;
+ int pem_len = 0;
+
+ if (strstr(dh, DH_HEADER) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, header missing")));
+
+ dh += strlen(DH_HEADER);
+ tail = strstr(dh, DH_FOOTER);
+ if (!tail)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, footer missing")));
+ *tail = '\0';
+
+ /* In order to PEM convert it we need to remove all newlines */
+ head = dh;
+ tail = dh;
+ while (*head != '\0')
+ {
+ if (*head != '\n')
+ {
+ *tail++ = *head++;
+ pem_len++;
+ }
+ else
+ head++;
+ }
+ *tail = '\0';
+
+ der = palloc(pg_b64_dec_len(strlen(dh)) + 1);
+ der_len = pg_b64_decode(dh, strlen(dh), der);
+ der[der_len] = '\0';
+ }
+ else
+ {
+ der = dh;
+ der_len = len;
+ }
+
+ status = SSLSetDiffieHellmanParams(ssl, der, der_len);
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unable to load DH parameters: %s",
+ pg_SSLerrmessage(status))));
+}
+
+/*
+ * This function should return the expected TLS Finished message information
+ * from the client, but there is no API for this in Secure Transport. As a
+ * result, channel binding is not supported by Secure Transport.
+ */
+char *
+be_tls_get_peer_finished(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+char *
+be_tls_get_certificate_hash(Port *port, size_t *len)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("channel binding is not supported by this build")));
+ return NULL;
+}
+
+/*
+ * be_tls_close
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect TLS version for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ if (protocol == kTLSProtocol11)
+ return "TLSv1.1";
+ else if (protocol == kTLSProtocol12)
+ return "TLSv1.2";
+
+ return "unknown";
+}
+
+/*
+ * be_tls_read
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * be_tls_write
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr && trust != NULL)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+ const char *cipher_name;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect cipher for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ cipher_name = pg_SSLciphername(cipher);
+ if (cipher_name == NULL)
+ elog(ERROR, "Unknown cipher detected");
+
+ return cipher_name;
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ /*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there still is no translation for it in 10.11 El Capitan, so we
+ * maintain our own.
+ */
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * The Secure Transport supplied error string for invalid passphrase only
+ * reads "invalid attribute" without any reference to a passphrase, which
+ * can be confusing. Override with our own.
+ */
+ if (status == errSecInvalidAttributePassphrase)
+ return pstrdup(_("Incorrect passphrase."));
+
+ if (status == errSSLRecordOverflow)
+ return pstrdup(_("SSL error"));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Misc */
+/* ------------------------------------------------------------ */
+
+/*
+ * KeychainEnsureValid
+ * Ensure the validity of using the passed Keychain array
+ *
+ * Any consumers of the Keychain array should always call this to ensure that
+ * it is set up in a manner that reflect the configuration. If it not, then
+ * this function will fatally error out as all codepaths should assume that
+ * setup has been done correctly and that there is no recovery in case it
+ * hasn't.
+ */
+static void
+KeychainEnsureValid(CFArrayRef *keychains)
+{
+ int keychain_count;
+
+ /*
+ * If the keychain array is unallocated, we must be allowed to use the
+ * default user Keychain, as that will be the effect of passing NULL to the
+ * Keychain search API. If not, error out.
+ */
+ if (*keychains == NULL)
+ {
+ if (!ssl_keychain_use_default)
+ goto error;
+
+ return;
+ }
+
+ keychain_count = CFArrayGetCount(*keychains);
+
+ /*
+ * If we have one Keychain loaded then we must have a Keychain file
+ * configured, and not be allowed to use the default Keychain. If we have
+ * two then we must have a Keychain file configured *and* be allowed to use
+ * the default user Keychain. If we have any other number of Keychains in
+ * the array then we definitely have an incorrect situation.
+ */
+ if (keychain_count == 1)
+ {
+ if (ssl_keychain_file[0] == '\0')
+ goto error;
+
+ return;
+ }
+ else if (keychain_count == 2)
+ {
+ if (ssl_keychain_file[0] == '\0' || !ssl_keychain_use_default)
+ goto error;
+
+ return;
+ }
+ else
+ /* Fallthrough to erroring out */
+
+error:
+ ereport(FATAL,
+ (errmsg("Incorrect loading of Keychains detected")));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index edfe2c0751..459f440727 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -45,6 +45,10 @@ char *ssl_crl_file;
char *ssl_dh_params_file;
char *ssl_passphrase_command;
bool ssl_passphrase_command_supports_reload;
+#ifdef USE_SECURETRANSPORT
+char *ssl_keychain_file;
+bool ssl_keychain_use_default = false;
+#endif
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index b05fb209bb..af761fafb5 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -503,6 +503,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -1822,6 +1823,17 @@ static struct config_bool ConfigureNamesBool[] =
true,
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_use_default", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Allow using the default Keychain of the current user."),
+ NULL
+ },
+ &ssl_keychain_use_default,
+ false,
+ NULL, NULL, NULL
+ },
+#endif
/* End-of-list marker */
{
@@ -3508,6 +3520,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
{
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
@@ -3755,6 +3779,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the Keychain file."),
+ NULL
+ },
+ &ssl_keychain_file,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 9e39baf466..d4715a1671 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -106,6 +106,7 @@
#ssl_dh_params_file = ''
#ssl_passphrase_command = ''
#ssl_passphrase_command_supports_reload = off
+#ssl_keychain_file = ''
#------------------------------------------------------------------------------
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000000..d3a38e2a5c
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,514 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport_common.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+#ifndef FRONTEND
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement. This
+ * function is currently only used in the libpq backend.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0d28..6d35f47864 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7698cd1f88..9ba5b6f46c 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,12 +191,15 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+ void *ssl;
+ int ssl_buffered;
#endif
} Port;
@@ -214,7 +225,7 @@ CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
/*
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 7bf06c65e9..907e22720c 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -82,6 +82,10 @@ extern char *ssl_crl_file;
extern char *ssl_dh_params_file;
extern char *ssl_passphrase_command;
extern bool ssl_passphrase_command_supports_reload;
+#ifdef USE_SECURETRANSPORT
+extern char *ssl_keychain_file;
+extern bool ssl_keychain_use_default;
+#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 89b8804251..2db5405900 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -907,6 +907,9 @@
/* Define to use OpenSSL for random number generation */
#undef USE_OPENSSL_RANDOM
+/* Define to build with Apple Secure Transport support. (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index b309395f11..5469473d72 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -162,11 +162,17 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
+#else
+#define SSL_LIBRARY "None"
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index abe0a50e98..9bc7989bab 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,11 @@ else
OBJS += sha2.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
@@ -112,7 +117,6 @@ ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm
encnames.c wchar.c: % : $(backend_src)/utils/mb/%
rm -f $@ && $(LN_S) $< .
-
distprep: libpq-dist.rc
libpq.rc libpq-dist.rc: libpq.rc.in
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a7e969d7c1..e363a54dec 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -330,6 +330,16 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain_use_default", NULL, NULL, NULL,
+ "UseDefaultKeychain", "", 1,
+ offsetof(struct pg_conn, keychain_use_default)},
+
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -3360,6 +3370,9 @@ makeEmptyPGconn(void)
conn->allow_ssl_try = true;
conn->wait_ssl_try = false;
conn->ssl_in_use = false;
+#ifdef USE_SECURETRANSPORT
+ conn->keychain_use_default = true;
+#endif
#endif
/*
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000000..db323c5ce1
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1494 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readiblitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *common_name,
+ SecCertificateRef *certificate,
+ CFArrayRef keychains,
+ char *hostname);
+static OSStatus import_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+static OSStatus import_pem(const char *path, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * Exported function to allow application to tell us it's already initialized
+ * Secure Transport and/or libcrypto. Since the current implementation only
+ * allow do_crypto be set for the OpenSSL backend, we should always get the
+ * same value passed in both variables.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ Assert(do_ssl ? do_crypto : !do_crypto);
+ return;
+}
+
+/*
+ * pgtls_open_client
+ * Begin, or continue, negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin(conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection(conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs(conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we dont have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ SSLSetPeerDomainName(conn->ssl, conn->pghost, strlen(conn->pghost));
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption(conn->ssl, kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake(conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust(conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ if (!trusted)
+ return errSecNoAccessForItem;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * pgtls_read_pending
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize(conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead(conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * pgtls_write
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite(conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite(conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+char *
+pgtls_get_finished(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"%s\" is not supported by this build\n"),
+ conn->scram_channel_binding);
+ return NULL;
+}
+char *
+pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
+{
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("channel binding type \"%s\" is not supported by this build\n"),
+ conn->scram_channel_binding);
+ return NULL;
+}
+
+/*
+ * pgtls_init
+ * Initialize SSL system.
+ *
+ * There is little state or context to initialize for Secure Transport, the
+ * heavy lifting is performed by pgtls_open_client.
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose(conn->ssl);
+ CFRelease(conn->ssl);
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * pg_SSLSocketRead
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+import_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ SecIdentityRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * We don't need to set a kSecMatchLimit key since the default is to only
+ * return a single reference. Older versions of macOS had issues with
+ * certificate matching on labels, but we don't support older versions so
+ * no need to extract all and match ourselves.
+ */
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+
+ if (status == noErr)
+ *identity = (SecIdentityRef) CFRetain(temp);
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_certificate_keychain(const char *common_name, SecCertificateRef *certificate,
+ CFArrayRef keychains, char *hostname)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFStringRef host = NULL;
+ CFArrayRef temp;
+ SecPolicyRef ssl_policy;
+ int i;
+
+ /*
+ * Make sure the user didn't just specify the keychain prefix as the
+ * certificate config. The passed certificate will have the keychain
+ * prefix stripped so in that case the string is expected to be empty.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ /*
+ * If we didn't get a set of Keychains passed, skip adding it to the
+ * dictionary thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * Specifying a hostname requires it to match the hostname in the leaf
+ * certificate.
+ */
+ if (hostname)
+ host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8);
+ ssl_policy = SecPolicyCreateSSL(true, host);
+ CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label has been reported to not work (revisions of 10.12), we request
+ * all and find the one we want. Copy all the results to a temp array and
+ * scan it for the certificate we are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i);
+
+ if (search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ *certificate = (SecCertificateRef) CFRetain(search_cert);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(ssl_policy);
+ CFRelease(query);
+ CFRelease(cert);
+ if (host)
+ CFRelease(host);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, char *passphrase, CFArrayRef *certificate)
+{
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+ struct stat buf;
+
+ if (!path || strlen(path) == 0)
+ return errSecInvalidValue;
+
+ if (stat(path, &buf) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return -1;
+
+ return errSecInvalidValue;
+ }
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInvalidValue;
+
+ certdata = malloc(buf.st_size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, buf.st_size, fp) != buf.st_size)
+ {
+ fclose(fp);
+ free(certdata);
+ return errSSLBadCert;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, buf.st_size);
+ free(certdata);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecAllocate;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ return SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ bool have_homedir;
+ bool cert_from_file = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecCertificateRef root_ref[1];
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+ SecKeychainRef kcref[2];
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching. If no additional keychain is specified we don't need to open
+ * the default keychain and pass to searches since Secure Transport will
+ * use the default when passing NULL instead of an array of Keychain refs.
+ */
+ if (conn->keychain)
+ {
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr && kcref[0] != NULL)
+ {
+ SecKeychainStatus kcstatus;
+ status = SecKeychainGetStatus(kcref[0], &kcstatus);
+ if (status == noErr)
+ {
+ switch (kcstatus)
+ {
+ /*
+ * If the Keychain is already unlocked, readable or
+ * writeable, we don't need to do more. If not, try to
+ * unlock it.
+ */
+ case kSecUnlockStateStatus:
+ case kSecReadPermStatus:
+ case kSecWritePermStatus:
+ break;
+ default:
+ /*
+ * TODO: we need to get the passphrase from the
+ * user, but we currently don't have a good
+ * mechanism for that. For now, we assume a blank
+ * passphrase but we need to figure out a good way
+ * to have the user enter the passphrase.
+ */
+ SecKeychainUnlock(kcref[0], 0, "", TRUE);
+ break;
+ }
+ }
+
+ /*
+ * We only need to open, and add, the default Keychain in case
+ * have a user keychain opened, else we will pass NULL to any
+ * keychain search which will use the default keychain by..
+ * default.
+ */
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the files
+ * could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Try to load the root cert from either a user defined keychain or the
+ * default Keychain in case none is specified
+ */
+ if (conn->sslrootcert &&
+ pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ root_ref[0] = NULL;
+ strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL);
+
+ if (root_ref[0])
+ conn->st_rootcert = (void *) CFArrayCreate(NULL,
+ (const void **) root_ref,
+ 1, &kCFTypeArrayCallBacks);
+ }
+
+ if (!conn->st_rootcert)
+ {
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+ }
+
+ /*
+ * If the sslcert config is prefixed with a keychain identifier, the cert
+ * must be located in either the default or the specified keychain.
+ */
+ if (conn->sslcert &&
+ pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ status = import_identity_keychain(fnbuf, &identity, keychains);
+
+ if (identity && status == noErr)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ else
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate \"%s\" from keychain: %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+
+ return status;
+ }
+ }
+ /*
+ * No prefix on the sslcert config, the certificate must thus reside in a
+ * file on disk.
+ */
+ else
+ {
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, cert_array);
+ if (status != noErr)
+ {
+ if (status == -1)
+ return noErr;
+
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ cert_from_file = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in
+ * order to create the identity.
+ */
+
+ /*
+ * The sslkey config is prefixed with keychain: indicating that the
+ * key should be loaded from Keychain instead of the filesystem.
+ * Search for the private key matching the cert_ref in the opened
+ * Keychains. If found, we get the identity returned.
+ */
+ if (conn->sslkey &&
+ pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref,
+ &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the
+ * key to be loaded from that file.
+ */
+ if (fnbuf[0] != '\0')
+ {
+
+ if (stat(fnbuf, &buf) != 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+ fnbuf);
+ return errSecInvalidKeyRef;
+ }
+#ifndef WIN32
+ if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
+ fnbuf);
+ return errSecInvalidKeyRef;
+ }
+#endif
+ status = import_pem(fnbuf, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+ }
+ }
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ if (key_ref)
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ }
+
+ if (!identity)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create identity from certificate/key\n"));
+ return errSecInvalidValue;
+ }
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (cert_from_file)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate(conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher(conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion(conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState(conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249bf0..5fa6734643 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -157,8 +157,10 @@ void
PQinitOpenSSL(int do_ssl, int do_crypto)
{
#ifdef USE_SSL
+#ifdef USE_OPENSSL
pgtls_init_library(do_ssl, do_crypto);
#endif
+#endif
}
/*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9a586ff25a..06d2825880 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -80,6 +80,14 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_SECURETRANSPORT
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#undef Size
+#undef uint64
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -470,8 +478,19 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+ bool keychain_use_default;
+
+ SSLContextRef ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index df477f1d40..0c9b4cdddd 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_securetransport
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -32,7 +33,7 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
# This target generates all the key and certificate files.
sslfiles: $(SSLFILES)
-# Openssl requires a directory to put all generated certificates in. We don't
+# 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:
mkdir ssl/new_certs_dir
@@ -130,6 +131,20 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### Keychains
+
+ifeq ($(with_securetransport),yes)
+
+KEYCHAINS := ssl/client.keychain
+
+# This target generates all Keychains
+keychains: $(KEYCHAINS)
+
+PWD=$(shell pwd)
+ssl/client.keychain: ssl/client.crt ssl/client.key
+ certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p=
+endif
+
.PHONY: sslfiles-clean
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
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index 1cd3badaa1..b068671348 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -26,12 +26,22 @@ use Test::More;
use Exporter 'import';
our @EXPORT = qw(
+ set_backend
configure_test_server_for_ssl
switch_server_cert
test_connect_fails
test_connect_ok
);
+our $current_backend;
+
+sub set_backend
+{
+ my ($backend) = @_;
+
+ $current_backend = $backend;
+}
+
# Define a couple of helper functions to test connecting to the server.
# The first argument is a base connection string to use for connection.
@@ -52,7 +62,7 @@ sub test_connect_ok
sub test_connect_fails
{
- my ($common_connstr, $connstr, $expected_stderr, $test_name) = @_;
+ my ($common_connstr, $connstr, $test_name, %expected_stderr) = @_;
my $cmd = [
'psql', '-X', '-A', '-t', '-c',
@@ -60,7 +70,7 @@ sub test_connect_fails
'-d', "$common_connstr $connstr"
];
- command_fails_like($cmd, $expected_stderr, $test_name);
+ command_fails_like($cmd, $expected_stderr{$current_backend}, $test_name);
return;
}
@@ -151,7 +161,14 @@ sub switch_server_cert
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";
+ if (check_pg_config("#define USE_SECURETRANSPORT 1"))
+ {
+ print $sslconf "ssl_crl_file=''\n";
+ }
+ else
+ {
+ print $sslconf "ssl_crl_file='root+client.crl'\n";
+ }
close $sslconf;
$node->restart;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index e550207454..8ccc0e5688 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -6,9 +6,32 @@ use Test::More;
use ServerSetup;
use File::Copy;
+# Some tests are backend specific, so store the currently used backend and
+# some particular capabilities which are interesting for the test
+my %tls_backend;
+
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 64;
+ $tls_backend{library} = 'openssl';
+ $tls_backend{tests} = 64;
+ $tls_backend{crl_support} = 1;
+ $tls_backend{keychain_support} = 0;
+}
+elsif ($ENV{with_securetransport} eq 'yes')
+{
+ $tls_backend{library} = 'securetransport';
+ $tls_backend{tests} = 67;
+ $tls_backend{crl_support} = 0;
+ $tls_backend{keychain_support} = 1;
+}
+else
+{
+ $tls_backend{library} = undef;
+}
+
+if (defined $tls_backend{library})
+{
+ plan tests => $tls_backend{tests};
}
else
{
@@ -25,6 +48,8 @@ my $SERVERHOSTADDR = '127.0.0.1';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
+set_backend($tls_backend{library});
+
# The client's private key must not be world-readable, so take a copy
# of the key stored in the code tree and update its permissions.
copy("ssl/client.key", "ssl/client_tmp.key");
@@ -60,10 +85,28 @@ 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);
+if ($tls_backend{library} eq 'securetransport')
+{
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=invalid sslmode=require",
+ "connect without server root cert sslmode=require",
+ ( securetransport => qr/record overflow/ ));
+}
+else
+{
+ 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";
@@ -93,8 +136,8 @@ $common_connstr =
# The server should not accept non-SSL connections.
test_connect_fails(
$common_connstr, "sslmode=disable",
- qr/\Qno pg_hba.conf entry\E/,
- "server doesn't accept non-SSL connections");
+ "server doesn't accept non-SSL connections",
+ ( openssl => qr/\Qno pg_hba.conf entry\E/ ,securetransport => qr/\Qno pg_hba.conf entry\E/ ));
# Try without a root cert. In sslmode=require, this should work. In verify-ca
# or verify-full mode it should fail.
@@ -105,31 +148,50 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
- "connect without server root cert sslmode=verify-ca");
+ "connect without server root cert sslmode=verify-ca",
+ (
+ openssl => qr/root certificate file "invalid" does not exist/,
+ securetransport => qr/root certificate file "invalid" does not exist/
+ ));
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
- "connect without server root cert sslmode=verify-full");
+ "connect without server root cert sslmode=verify-full",
+ (
+ openssl => qr/root certificate file "invalid" does not exist/,
+ securetransport => qr/root certificate file "invalid" does not exist/
+ ));
# 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");
+ "connect with wrong server root cert sslmode=require",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
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");
+ "connect with wrong server root cert sslmode=verify-ca",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
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");
+ "connect with wrong server root cert sslmode=verify-full",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
# 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");
+ "connect with server CA cert, without root CA",
+ ( openssl => qr/SSL error/, securetransport => qr/SSL error/));
# And finally, with the correct root cert.
test_connect_ok(
@@ -158,24 +220,36 @@ test_connect_ok(
# 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",
- "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");
-
-# 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",
- "CRL with a non-revoked cert");
+if ($tls_backend{crl_support})
+{
+ # 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",
+ "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",
+ "CRL belonging to a different CA",
+ ( openssl => qr/SSL error/ ));
+
+ # 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",
+ "CRL with a non-revoked cert");
+}
+else
+{
+ # Test that the presence of a CRL configuration throws an error
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "unsupported sslcrl option",
+ ( securetransport => qr/CRL files are not supported/ ));
+}
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
@@ -193,8 +267,11 @@ 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/,
- "mismatch between host name and server certificate sslmode=verify-full");
+ "mismatch between host name and server certificate sslmode=verify-full",
+ (
+ openssl => qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
@@ -218,13 +295,19 @@ 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/,
- "host name not matching with X.509 Subject Alternative Names");
+ "host name not matching with X.509 Subject Alternative Names",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
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/,
- "host name not matching with X.509 Subject Alternative Names wildcard");
+ "host name not matching with X.509 Subject Alternative Names wildcard",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
@@ -241,14 +324,19 @@ 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/,
- "host name not matching with a single X.509 Subject Alternative Name");
+ "host name not matching with a single X.509 Subject Alternative Name",
+ (
+ openssl => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
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/,
- "host name not matching with a single X.509 Subject Alternative Name wildcard"
-);
+ "host name not matching with a single X.509 Subject Alternative Name wildcard",
+ (
+ openssl => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
@@ -268,8 +356,11 @@ 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/,
- "certificate with both a CN and SANs ignores CN");
+ "certificate with both a CN and SANs ignores CN",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
# 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.
@@ -284,8 +375,11 @@ 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/,
- "server certificate without CN or SANs sslmode=verify-full");
+ "server certificate without CN or SANs sslmode=verify-full",
+ (
+ openssl => qr/could not get server's host name from server certificate/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test that the CRL works
switch_server_cert($node, 'server-revoked');
@@ -298,11 +392,46 @@ test_connect_ok(
$common_connstr,
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
"connects without client-side CRL");
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
- qr/SSL error/,
- "does not connect with client-side CRL");
+if ($tls_backend{crl_support})
+{
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL",
+ ( openssl => qr/SSL error/ ));
+}
+else
+{
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL",
+ ( securetransport => qr/CRL files are not supported with Secure Transport/ ));
+}
+
+# test Secure Transport keychain support
+if ($tls_backend{keychain_support})
+{
+ $common_connstr =
+ "user=ssltestuser dbname=certdb hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ # empty keychain
+ test_connect_fails($common_connstr,
+ "keychain=invalid",
+ "invalid Keychain file reference",
+ ( securetransport => qr/The specified item has no access control/ ));
+
+ # correct client cert in keychain with and without proper label
+ test_connect_fails(
+ $common_connstr,
+ "keychain=ssl/client.keychain",
+ "client cert in keychain but without label",
+ ( securetransport => qr/The specified item has no access control/ ));
+ test_connect_ok(
+ $common_connstr,
+ "sslcert=keychain:ssltestuser keychain=ssl/client.keychain",
+ "client cert in keychain");
+}
+
### Server-side tests.
###
@@ -317,8 +446,11 @@ $common_connstr =
test_connect_fails(
$common_connstr,
"user=ssltestuser sslcert=invalid",
- qr/connection requires a valid client certificate/,
- "certificate authorization fails without client cert");
+ "certificate authorization fails without client cert",
+ (
+ openssl => qr/connection requires a valid client certificate/,
+ securetransport => qr/connection requires a valid client certificate/
+ ));
# correct client cert
test_connect_ok(
@@ -330,23 +462,30 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
- qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
- "certificate authorization fails because of file permissions");
+ "certificate authorization fails because of file permissions",
+ (
+ openssl => qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
+ securetransport => qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!
+ ));
# client cert belonging to another user
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
- "certificate authorization fails with client cert belonging to another user"
-);
+ "certificate authorization fails with client cert belonging to another user",
+ (
+ openssl => qr/certificate authentication failed for user "anotheruser"/,
+ securetransport => qr/certificate authentication failed for user "anotheruser"/
+ ));
-# revoked client cert
-test_connect_fails(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
- qr/SSL error/,
- "certificate authorization fails with revoked client cert");
+if ($tls_backend{crl_support})
+{
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "certificate authorization fails with revoked client cert",
+ ( openssl => qr/SSL error/, securetransport => qr/SSL error/ ));
+}
# 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');
@@ -358,8 +497,13 @@ test_connect_ok(
"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");
+ "intermediate client certificate is missing",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/certificate authentication failed/
+ ));
# clean up
unlink("ssl/client_tmp.key", "ssl/client_wrongperms_tmp.key",
"ssl/client-revoked_tmp.key");
+unlink("ssl/client.keychain") if ($tls_backend{keychain_support});
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 52a8f458cb..f18d1fdb26 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -22,6 +22,9 @@ my $SERVERHOSTADDR = '127.0.0.1';
my $supports_tls_server_end_point =
check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1");
+my $supports_tls_unique =
+ ! check_pg_config("#define USE_SECURETRANSPORT 1");
+
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
@@ -45,17 +48,34 @@ $ENV{PGPASSWORD} = "pass";
$common_connstr =
"user=ssltestuser dbname=trustdb sslmode=require hostaddr=$SERVERHOSTADDR";
-# Default settings
-test_connect_ok($common_connstr, '',
- "SCRAM authentication with default channel binding");
-
# Channel binding settings
-test_connect_ok(
- $common_connstr,
- "scram_channel_binding=tls-unique",
- "SCRAM authentication with tls-unique as channel binding");
+if ($supports_tls_unique)
+{
+ # Default settings
+ test_connect_ok($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ # Channel binding settings
+ test_connect_ok(
+ $common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
+else
+{
+ # Default settings
+ test_connect_fails($common_connstr, '',
+ "SCRAM authentication with default channel binding");
+
+ # Channel binding settings
+ test_connect_fails(
+ $common_connstr,
+ "scram_channel_binding=tls-unique",
+ "SCRAM authentication with tls-unique as channel binding");
+}
test_connect_ok($common_connstr, "scram_channel_binding=''",
"SCRAM authentication without channel binding");
+
if ($supports_tls_server_end_point)
{
test_connect_ok(
--
2.14.1.145.gb3622a4ee
On 27/06/18 21:57, Daniel Gustafsson wrote:
On 27 Jun 2018, at 14:32, Daniel Gustafsson <daniel@yesql.se> wrote:
Attached is an updated patch for supporting the native macOS Secure Transport
library, rebased on top of current master.Courtesy of the ever-present Murphy I managed to forget some testcode in
src/backend/Makefile which broke compilation for builds without secure
transport, attached v8 patch fixes that.
I've read through this patch now in more detail. Looks pretty good, but
I have a laundry list of little things below. The big missing item is
documentation.
--- laundry list begins ---
What exactly does 'ssl_is_server_start' mean? I don't understand that
mechanism. ISTM it's only checked in load_key(), via
be_tls_open_server(), which should only be called after be_tls_init(),
in which case it's always 'true' when it's checked. Should there be an
Assertion on it or something?
The "-framework" option, being added to CFLAGS, is clang specific. I
think we need some more autoconf magic, to make this work with gcc.
In be_tls_open_server(), I believe there are several cases of using
variables uninitialized. For example, load_identity_keychain() doesn't
always set the 'identity' output parameter, but there's an "if (identity
== NULL)" check after the call to it. And 'certificates' is left
uninitialized, if load_identity_keychain() is used. Also, 'dh_len' is
used uninitialized here in the "if (!dh_buf || dh_len == 0)" line, if
the 'ssl_dh_params_file' option is not set. Did clang not give warnings
about these?
+ /* + * Certificate Revocation List are not supported in the Secure Transport + * API + */
The corresponding client code has a longer comment on that:
+ /* + * There is no API to load CRL lists in Secure Transport, they can however + * be imported into a Keychain with the commandline application "certtool". + * For libpq to use them, the certificate/key and root certificate needs to + * be using an identity in a Keychain into which the CRL have been + * imported. That needs to be documented. + */
Is it possible to programmatically create a temporary keychain, in
memory, and load the CRL to it? (I googled a bit, and couldn't find any
suitable API for it, so I guess not..)
+ if (ssl_crl_file[0]) + ereport(FATAL, + (errmsg("CRL files not supported with Secure Transport")));
I think this should be COMMERROR, like other errors around this. We
don't want to pass that error message to the client. Although I guess it
won't reach the client as we haven't negotiated TLS yet.
+ /* + * We use kTryAuthenticate here since we don't know which sslmode the + * client is using. If we were to use kAlwaysAuthenticate then sslmode + * require won't work as intended. + */ + if (ssl_loaded_verify_locations) + SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
That comment sounds wrong. This is in backend code, and
SSLSetClientSideAuthenticate() is all about checking the client's
certificate in the server, while libpq 'sslmode' is about checking the
server's certificate in the client.
+ for (;;) + { + status = SSLHandshake((SSLContextRef) port->ssl); + if (status == noErr) + break; + + if (status == errSSLWouldBlock) + continue; + ... + }
Does this busy-wait, if there's not enough data from the client? That
seems bad. In the corresponding client-side code, there's a comment on
that too, but it's still bad.
In be_tls_get_version and PQsslAttribute, can we add support for
kTLSProtocol13? Perhaps with an autoconf test, if the constant is not
available on all macOS versions.
In be_tls_get_peerdn_name, wouldn't SecCertificateCopyCommonName() be
more appropriate than SecCertificateCopyLongDescription()?
In be_tls_get_cipher, returning "unknown" would seem better than
erroring out, if the cipher isn't recognized.
Check error message style. eg.:
+ ereport(COMMERROR, + (errmsg("could not load server certificate \"%s\": \"%s\"", + ssl_cert_file, pg_SSLerrmessage(status))));
Why is the result of pg_SSLerrmessage() in quotes? Maybe errdetail?
+ * Any consumers of the Keychain array should always call this to ensure that + * it is set up in a manner that reflect the configuration. If it not, then
s/reflect/reflects/
+ else if (keychain_count == 2) + { + if (ssl_keychain_file[0] == '\0' || !ssl_keychain_use_default) + goto error; + + return; + } + else + /* Fallthrough to erroring out */ + +error: + ereport(FATAL, + (errmsg("Incorrect loading of Keychains detected"))); +}
That fallthrough looks dangerous.
--- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -106,6 +106,7 @@ #ssl_dh_params_file = '' #ssl_passphrase_command = '' #ssl_passphrase_command_supports_reload = off +#ssl_keychain_file = ''
Do we want to have this Secure Transport-specific option in the sample
config file? Comment at least
+#elif USE_SECURETRANSPORT + void *ssl; + int ssl_buffered;
Add comments. Mention the actual type of 'ssl' (SSLContextRef), and what
'ssl_buffered' means.
libpq-be.h and libpq-int.h use a different strategy to avoid clashes
between PostgreSQL's and Secure Transport's versions of Size and uint64.
Let's be consistent. (I like the libpq-fe.h version more, i.e. using
"void *" and avoiding the #include)
- * SSL implementation (e.g. be-secure-openssl.c) + * SSL implementation (e.g. be-secure-<implementation>.c)
Since this is just an example, I think leaving it as be-secure-openssl.c
is fine.
Also update pg_config.h.win32 with the new USE_SECURETRANSPOT flag being
added to pg_config.h.in.
In the call to SSLSetPeerDomainName(), should check return code.
+ /* + * In sslmode "require" we accept some certificate verification + * failures when we don't have a rootcert since MITM protection + * isn't enforced. Check the reported failure and trust in case + * the cert is missing, self signed or expired/future. + */ + if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert) + {
Not just "require", but "allow" as well, right?
freePGconn should free conn->keychain. Would be good to write a little
test that opens & closes millions of connectins, to check for memory leaks.
+ * Queries the specified Keychain, or the default unless not defined, for a
"unless not defined" is a double-negative. I don't understand that
sentence. (there are two copies of the same comment in the patch, in FE
and BE. And the FE function is called "load_identity_keychain", but its
comment says "import_identity_keychain" )
PQinitOpenSSL and PQinitSSL are inconsistent in whether to call
pgtls_init_library(), when compiled with Secure Transport.
pgtls_init_library() is a no-op, so it doesn't matter, but let's be
consistent. Perhaps do "#define pgtls_init_library() ((void)true)" in
the header?
s/readiblitity/readability/
s/dont/don't/
s/securetransport_common.h/securetransport.h/
s/f.e/for example/ (at least I'm not familiar with that abbreviation)
--- end of laundry list ---
- Heikki
Heikki Linnakangas <hlinnaka@iki.fi> writes:
On 27/06/18 21:57, Daniel Gustafsson wrote:
Courtesy of the ever-present Murphy I managed to forget some testcode in
src/backend/Makefile which broke compilation for builds without secure
transport, attached v8 patch fixes that.
I've read through this patch now in more detail. Looks pretty good, but
I have a laundry list of little things below. The big missing item is
documentation.
I did some simple testing on this today. The patch has bit-rotted,
mostly as a consequence of 77291139c which removed tls-unique channel
binding. That's probably a good thing for the Secure Transport code,
since it wasn't supporting that anyway, but it needs to be updated.
I ripped out the failing-to-compile code (maybe that was too simplistic?)
but could not figure out whether there was anything still useful about
the diffs in ssl/t/002_scram.pl, so I left those out. Anyway, the
attached update applies cleanly to today's HEAD, and the openssl part
still compiles cleanly and passes regression tests. The securetransport
part compiles cleanly, but it fails 8 of the 68 tests in 001_ssltests.pl.
I'm not sure how many of those might be new and how many were there as
of the previous submission.
The "-framework" option, being added to CFLAGS, is clang specific. I
think we need some more autoconf magic, to make this work with gcc.
AFAIK, Apple's "gcc" *is* clang; it certainly has no problem with
these switches (and I rather doubt there's any hope of linking to
Secure Transport without 'em). However, I agree that the technique
of teaching random makefiles about this explicitly is mighty ugly,
and we ought to put the logic into configure instead, if possible.
Possibly it could be modeled on LDAP_LIBS or ICU_LIBS, ie configure
sets up a macro that pulls in the openssl libraries, or the
secure transport libraries, or $other-implementation, or nothing.
The CFLAGS hacks need similar treatment (btw, should they be
CPPFLAGS hacks instead? I think they're morally equivalent to
-I switches). And avoid using "override" if at all possible.
Some other comments:
* I notice that contrib/sslinfo hasn't been touched. That probably
ought to be on the to-do list for this, though I don't insist that
it needs to be in the first commit.
* I'm not sure what the "keychain" additions to test/ssl/Makefile
are for, but they don't seem to be wired up to anything. Running
"make keychains" has no impact on the test failures, either.
* I do not like making the libpq connection parameters for this be
#ifdef'd out when the option isn't selected. I believe project policy is
that we accept all parameters always, and either ignore unsupported ones,
or throw errors if they're set to nondefault values (cf. the comment above
the sslmode parameter in fe-connect.c). I realize that some earlier
patches like GSSAPI did not get the word on this, but that's not a reason
to emulate their mistake. I'm not sure about the equivalent decision
w.r.t. backend GUCs, but we need to figure that out.
* In place of changes like
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
I'd be inclined to just do "#ifdef USE_OPENSSL", ie assume that macro
can't be set without USE_SSL.
regards, tom lane
Attachments:
secure-transport-v9.patchtext/x-diff; charset=us-ascii; name=secure-transport-v9.patchDownload
diff --git a/configure b/configure
index 23ebfa8..6bdc96a 100755
--- a/configure
+++ b/configure
@@ -708,6 +708,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
with_ldap
with_krb_srvnam
@@ -854,6 +855,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1554,6 +1556,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -7968,6 +7971,41 @@ $as_echo "$with_openssl" >&6; }
#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
+#
# SELinux
#
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with SELinux support" >&5
@@ -12998,6 +13036,25 @@ fi
fi
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+fi
+
if test "$with_pam" = yes ; then
for ac_header in security/pam_appl.h
do :
diff --git a/configure.in b/configure.in
index 530f275..58c9883 100644
--- a/configure.in
+++ b/configure.in
@@ -841,6 +841,15 @@ AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
+#
# SELinux
#
AC_MSG_CHECKING([whether to build with SELinux support])
@@ -1367,6 +1376,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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9cf0c35..48ee631 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -185,6 +185,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_gssapi = @with_gssapi@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 3a58bf6..a93b9a7 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -51,6 +51,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LIBS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-catalog-headers submake-utils-headers postgres $(POSTGRES_IMP)
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23..3f8a8b9 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000..6c3f36c
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1447 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future).
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interested to investigate.
+ *
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "common/base64.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err. Since we
+ * may have encountered an error due to memory pressure, we don't want to rely
+ * on dynamically allocating memory for this error message.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+static bool ssl_is_server_start;
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+/*
+ * SecIdentityCreate is not an exported Secure Transport API. There is however
+ * no exported Secure Transport function that can create an identity from a
+ * SecCertificateRef and a SecKeyRef without having either of them in a
+ * Keychain. This function is commonly used in open source projects (such as
+ * ffmpeg and mono f.e), but finding an alternative is a TODO.
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static bool load_key(char *name, CFArrayRef *out);
+static OSStatus load_keychain(char *name, CFArrayRef *keychains);
+static OSStatus load_certificate_file(char *name, CFArrayRef *cert_array);
+static OSStatus load_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+
+static int load_dh_file(char *filename, char **buf);
+static void load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl);
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+static void KeychainEnsureValid(CFArrayRef *keychains);
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize global Secure Transport structures (if any)
+ *
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ *
+ * Another reason to defer this until when in the backend is that Keychains
+ * are SQLite3 backed, and sqlite does not allow access across a fork. See
+ * https://sqlite.org/faq.html#q6 for more information.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+
+ ssl_is_server_start = isServerStart;
+
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * be_tls_open_server
+ * Attempt to negotiate a secure connection
+ *
+ * Unlike the OpenSSL backend, this function is responsible for the deferred
+ * loading of keys and certificates for use in the connection. See the comment
+ * on be_tls_init for further reasoning around this.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFArrayRef keychains = NULL;
+ CFMutableArrayRef chain;
+ char *dh_buf;
+ int dh_len;
+
+ Assert(!port->ssl);
+
+ if (ssl_keychain_file[0])
+ {
+ status = load_keychain(ssl_keychain_file, &keychains);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load keychain(s): \"%s\"",
+ pg_SSLerrmessage(status))));
+ }
+
+ /*
+ * If the ssl_cert_file is prefixed with a keychain reference, we will try
+ * to load a complete identity from the Keychain.
+ */
+ if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains);
+ else
+ {
+ status = load_certificate_file(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": \"%s\"",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ if (!load_key(ssl_key_file, &keys))
+ return -1;
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ }
+
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate_file(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": \"%s\"",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API
+ */
+ if (ssl_crl_file[0])
+ ereport(FATAL,
+ (errmsg("CRL files not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ /*
+ * We use kTryAuthenticate here since we don't know which sslmode the
+ * client is using. If we were to use kAlwaysAuthenticate then sslmode
+ * require won't work as intended.
+ */
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * In case the user hasn't configured a DH parameters file, we load a pre-
+ * computed DH parameter to avoid having Secure Transport computing one for
+ * us (which is done by default unless one is set).
+ */
+ dh_buf = NULL;
+ if (ssl_dh_params_file[0])
+ dh_len = load_dh_file(ssl_dh_params_file, &dh_buf);
+
+ if (!dh_buf || dh_len == 0)
+ {
+ dh_buf = pstrdup(FILE_DH2048);
+ dh_len = sizeof(FILE_DH2048);
+ }
+
+ load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl);
+
+ /*
+ * Set Tlsv1.2 as the minimum protocol version we allow for the connection
+ */
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * We can't initiate the SSL structures in _init as per how OpenSSL does
+ * it, so we can't use serverstart for the same error handling. We still
+ * use it for a few things so maintain it for a single instantiation.
+ * TODO: it would be neat if this hack isn't required, more thinking is
+ * required.
+ */
+ ssl_is_server_start = false;
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ if (status == errSSLWouldBlock)
+ continue;
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: \"%s\"",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert;
+ CFStringRef usercert_cn;
+ const char *peer_cn;
+
+ usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+
+ /* Guard against empty/missing CNs */
+ peer_cn = CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8);
+ if (!peer_cn)
+ port->peer_cn = pstrdup("");
+ else
+ port->peer_cn = pstrdup(peer_cn);
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ */
+static bool
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ SecItemImportExportKeyParameters params;
+
+ if (!check_ssl_key_file_permissions(name, ssl_is_server_start))
+ return false;
+
+ /*
+ * check_ssl_key_file_permissions() has already checked the file for
+ * existence and correct permissions, but we still need to stat it to
+ * get the filesize.
+ */
+ if (stat(name, &stat_buf) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ /*
+ * There is no way to set a callback for acquiring the passphrase like how
+ * OpenSSL does it, so we need to re-run the import if it failed with a
+ * passphrase missing status. If no ssl_passphrase_command has been set we
+ * currently don't retry, which is something that will need to be revisited.
+ * TODO: figure out what would be the least confusing to the user here;
+ * perhaps supplying our own fallback passphrase_command? (which should be
+ * a TLS backend common function since it wouldn't be Secure Transport
+ * specific?)
+ */
+ if (status == errSecPassphraseRequired)
+ {
+ if ((ssl_is_server_start && ssl_passphrase_command[0]) ||
+ (!ssl_is_server_start && ssl_passphrase_command[0] && ssl_passphrase_command_supports_reload))
+ {
+ const char *prompt = "Enter PEM pass phrase: ";
+ char buf[256];
+ CFStringRef passphrase;
+
+ run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, sizeof(buf));
+
+ passphrase = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
+ params.passphrase = passphrase;
+ memset(&buf, '\0', sizeof(buf));
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(passphrase);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" cannot be loaded because it requires a passphrase",
+ name)));
+ }
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": \"%s\"",
+ name, pg_SSLerrmessage(status))));
+
+ return true;
+}
+
+/*
+ * load_keychain
+ * Open the specified, and default, Keychain
+ *
+ * Operations on keychains other than the default take the keychain references
+ * in an array. We need to copy a reference to the default keychain into the
+ * array to include it in the search.
+ *
+ * For server applications, we don't want modal dialog boxes opened for
+ * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE)
+ * will turn off all GUI interaction with the user, which may seem like what we
+ * want server side. This however has the side effect to turn off all GUI
+ * elements for all applications until some application calls
+ * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus
+ * remove wanted GUI interaction from another app, or another app might
+ * introduce it for us.
+ */
+static OSStatus
+load_keychain(char *name, CFArrayRef *keychains)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ SecKeychainRef kc[2];
+ int array_len = 1;
+
+ /*
+ * If we are passing in a non-empty CFArrayRef, and fail to load the user
+ * Keychain then we would risk injecting Keychains since we will trust this
+ * array from hereon. Unconditionally error out hard immediately to avoid.
+ */
+ if (*keychains != NULL)
+ ereport(FATAL,
+ (errmsg("Requesting to load Keychains into already allocated memory")));
+
+ if (stat(name, &stat_buf) != 0)
+ return errSecInvalidValue;
+
+ status = SecKeychainOpen(name, &kc[0]);
+ if (status == noErr)
+ {
+ SecKeychainUnlock(kc[0], 0, "", TRUE);
+
+ /*
+ * If we are allowed to use the default Keychain, add it to the array
+ * to include it in Keychain searches. If we are only using the default
+ * Keychain and no user defined Keychain we don't create an array at
+ * all since the recommended procedure is to pass NULL instead of an
+ * array containing only a reference to the default Keychain.
+ */
+ if (ssl_keychain_use_default)
+ SecKeychainCopyDefault(&kc[array_len++]);
+
+ *keychains = CFArrayCreate(NULL, (const void **) kc, array_len,
+ &kCFTypeArrayCallBacks);
+
+ if (!*keychains)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to allocate memory");
+ return errSecInternalError;
+ }
+ }
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+load_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /* Ensure we have a correctly set Keychains array */
+ KeychainEnsureValid(&keychains);
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+/*
+ * load_certificate_file
+ * Extracts a certificate from a PEM file on the filesystem
+ */
+static OSStatus
+load_certificate_file(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to find file");
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to open file for reading");
+ return errSecInternalError;
+ }
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to read file");
+ return errSecInternalError;
+ }
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL,
+ cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ snprintf(internal_err, ERR_MSG_LEN, "unable to open file for reading");
+ return errSecInternalError;
+}
+
+/*
+ * load_dh_file
+ * Slurp the contents of the specified file into buf
+ *
+ * Open the supplied filename and load its contents. This function is only
+ * reading the data without assessing its structure, actually parsing it is
+ * performed by load_dh_params(). The reason for splitting up the process is
+ * that we also support loading hardcoded DH params.
+ */
+static int
+load_dh_file(char *filename, char **buf)
+{
+ FILE *dh;
+ struct stat stat_buf;
+ int ret;
+
+ /*
+ * Open the DH file and slurp the contents. If the file doesn't exist it's
+ * not an error, if it can't be opened it is however an error.
+ */
+ ret = stat(filename, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ return 0;
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((dh = AllocateFile(filename, "r")) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+
+ *buf = palloc(stat_buf.st_size);
+ ret = fread(*buf, 1, stat_buf.st_size, dh);
+ FreeFile(dh);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read DH parameters file \"%s\": %m",
+ filename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("DH parameters file \"%s\" is not a regular file",
+ filename)));
+
+ return ret;
+}
+
+/*
+ * load_dh_params
+ * Load the specified DH params for the connection
+ *
+ * Secure Transport requires the DH params to be in DER format, but to be
+ * compatible with the OpenSSL code we also support PEM and convert to DER
+ * before loading. Conversion does rudimentary PEM parsing, if we miss the
+ * data being correct, the Secure Transport API will give an error anyways so
+ * we're just checking basic integrity.
+ *
+ * This function may scribble on the dh parameter so if that's required so stay
+ * intact in the caller, a copy should be sent.
+ */
+#define DH_HEADER "-----BEGIN DH PARAMETERS-----"
+#define DH_FOOTER "-----END DH PARAMETERS-----"
+static void
+load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl)
+{
+ OSStatus status;
+ char *der;
+ int der_len;
+
+ Assert(dh);
+
+ /* Convert PEM to DER */
+ if (is_pem)
+ {
+ char *head;
+ char *tail;
+ int pem_len = 0;
+
+ if (strstr(dh, DH_HEADER) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, header missing")));
+
+ dh += strlen(DH_HEADER);
+ tail = strstr(dh, DH_FOOTER);
+ if (!tail)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("Invalid PEM format for DH parameter, footer missing")));
+ *tail = '\0';
+
+ /* In order to PEM convert it we need to remove all newlines */
+ head = dh;
+ tail = dh;
+ while (*head != '\0')
+ {
+ if (*head != '\n')
+ {
+ *tail++ = *head++;
+ pem_len++;
+ }
+ else
+ head++;
+ }
+ *tail = '\0';
+
+ der = palloc(pg_b64_dec_len(strlen(dh)) + 1);
+ der_len = pg_b64_decode(dh, strlen(dh), der);
+ der[der_len] = '\0';
+ }
+ else
+ {
+ der = dh;
+ der_len = len;
+ }
+
+ status = SSLSetDiffieHellmanParams(ssl, der, der_len);
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unable to load DH parameters: %s",
+ pg_SSLerrmessage(status))));
+}
+
+/*
+ * be_tls_close
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect TLS version for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ if (protocol == kTLSProtocol11)
+ return "TLSv1.1";
+ else if (protocol == kTLSProtocol12)
+ return "TLSv1.2";
+
+ return "unknown";
+}
+
+/*
+ * be_tls_read
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * be_tls_write
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr && trust != NULL)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+ const char *cipher_name;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect cipher for connection: \"%s\"",
+ pg_SSLerrmessage(status))));
+
+ cipher_name = pg_SSLciphername(cipher);
+ if (cipher_name == NULL)
+ elog(ERROR, "Unknown cipher detected");
+
+ return cipher_name;
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ /*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there still is no translation for it in 10.11 El Capitan, so we
+ * maintain our own.
+ */
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * The Secure Transport supplied error string for invalid passphrase only
+ * reads "invalid attribute" without any reference to a passphrase, which
+ * can be confusing. Override with our own.
+ */
+ if (status == errSecInvalidAttributePassphrase)
+ return pstrdup(_("Incorrect passphrase."));
+
+ if (status == errSSLRecordOverflow)
+ return pstrdup(_("SSL error"));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Misc */
+/* ------------------------------------------------------------ */
+
+/*
+ * KeychainEnsureValid
+ * Ensure the validity of using the passed Keychain array
+ *
+ * Any consumers of the Keychain array should always call this to ensure that
+ * it is set up in a manner that reflect the configuration. If it not, then
+ * this function will fatally error out as all codepaths should assume that
+ * setup has been done correctly and that there is no recovery in case it
+ * hasn't.
+ */
+static void
+KeychainEnsureValid(CFArrayRef *keychains)
+{
+ int keychain_count;
+
+ /*
+ * If the keychain array is unallocated, we must be allowed to use the
+ * default user Keychain, as that will be the effect of passing NULL to the
+ * Keychain search API. If not, error out.
+ */
+ if (*keychains == NULL)
+ {
+ if (!ssl_keychain_use_default)
+ goto error;
+
+ return;
+ }
+
+ keychain_count = CFArrayGetCount(*keychains);
+
+ /*
+ * If we have one Keychain loaded then we must have a Keychain file
+ * configured, and not be allowed to use the default Keychain. If we have
+ * two then we must have a Keychain file configured *and* be allowed to use
+ * the default user Keychain. If we have any other number of Keychains in
+ * the array then we definitely have an incorrect situation.
+ */
+ if (keychain_count == 1)
+ {
+ if (ssl_keychain_file[0] == '\0')
+ goto error;
+
+ return;
+ }
+ else if (keychain_count == 2)
+ {
+ if (ssl_keychain_file[0] == '\0' || !ssl_keychain_use_default)
+ goto error;
+
+ return;
+ }
+ else
+ /* Fallthrough to erroring out */
+
+error:
+ ereport(FATAL,
+ (errmsg("Incorrect loading of Keychains detected")));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index d349d7c..21d5eb4 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -46,6 +46,10 @@ char *ssl_crl_file;
char *ssl_dh_params_file;
char *ssl_passphrase_command;
bool ssl_passphrase_command_supports_reload;
+#ifdef USE_SECURETRANSPORT
+char *ssl_keychain_file;
+bool ssl_keychain_use_default = false;
+#endif
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e9f542c..c4a3925 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -511,6 +511,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -1830,6 +1831,17 @@ static struct config_bool ConfigureNamesBool[] =
true,
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_use_default", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Allow using the default Keychain of the current user."),
+ NULL
+ },
+ &ssl_keychain_use_default,
+ false,
+ NULL, NULL, NULL
+ },
+#endif
/* End-of-list marker */
{
@@ -3517,6 +3529,18 @@ static struct config_string ConfigureNamesString[] =
},
{
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
+ {
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
gettext_noop("Sets the current role."),
@@ -3778,6 +3802,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the Keychain file."),
+ NULL
+ },
+ &ssl_keychain_file,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4e61bc6..eb19aa0 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -106,6 +106,7 @@
#ssl_dh_params_file = ''
#ssl_passphrase_command = ''
#ssl_passphrase_command_supports_reload = off
+#ssl_keychain_file = ''
#------------------------------------------------------------------------------
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000..d3a38e2
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,514 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport_common.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+#ifndef FRONTEND
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement. This
+ * function is currently only used in the libpq backend.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0..6d35f47 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_SSL) && defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index ef5528c..69aa93d 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,12 +191,15 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+ void *ssl;
+ int ssl_buffered;
#endif
} Port;
@@ -214,7 +225,7 @@ CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
/*
@@ -269,6 +280,7 @@ extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
*
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
+ * It's also not supported with Apple Secure Transport.
*/
#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 36baf6b..284e1cc 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -83,6 +83,10 @@ extern char *ssl_crl_file;
extern char *ssl_dh_params_file;
extern char *ssl_passphrase_command;
extern bool ssl_passphrase_command_supports_reload;
+#ifdef USE_SECURETRANSPORT
+extern char *ssl_keychain_file;
+extern bool ssl_keychain_use_default;
+#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 5d40796..4fad10d 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -932,6 +932,10 @@
/* Use replacement snprintf() functions. */
#undef USE_REPL_SNPRINTF
+/* Define to build with Apple Secure Transport support.
+ (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
/* 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 b036525..2731f82 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -162,11 +162,17 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
+#else
+#define SSL_LIBRARY "None"
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index ec0122a..a021364 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,11 @@ else
OBJS += sha2.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+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 45bc067..a8ac764 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -325,6 +325,16 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain_use_default", NULL, NULL, NULL,
+ "UseDefaultKeychain", "", 1,
+ offsetof(struct pg_conn, keychain_use_default)},
+
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -3536,6 +3546,9 @@ makeEmptyPGconn(void)
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
+#ifdef USE_SECURETRANSPORT
+ conn->keychain_use_default = true;
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000..f1a0455
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1477 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readiblitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *common_name,
+ SecCertificateRef *certificate,
+ CFArrayRef keychains,
+ char *hostname);
+static OSStatus import_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+static OSStatus import_pem(const char *path, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+/*
+ * pgtls_init_library
+ *
+ * Exported function to allow application to tell us it's already initialized
+ * Secure Transport and/or libcrypto. Since the current implementation only
+ * allow do_crypto be set for the OpenSSL backend, we should always get the
+ * same value passed in both variables.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ Assert(do_ssl ? do_crypto : !do_crypto);
+ return;
+}
+
+/*
+ * pgtls_open_client
+ * Begin, or continue, negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin(conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection(conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs(conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we dont have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ SSLSetPeerDomainName(conn->ssl, conn->pghost, strlen(conn->pghost));
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption(conn->ssl, kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake(conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust(conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ if (!trusted)
+ return errSecNoAccessForItem;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * pgtls_read_pending
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize(conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead(conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * pgtls_write
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite(conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite(conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+/*
+ * pgtls_init
+ * Initialize SSL system.
+ *
+ * There is little state or context to initialize for Secure Transport, the
+ * heavy lifting is performed by pgtls_open_client.
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose(conn->ssl);
+ CFRelease(conn->ssl);
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * pg_SSLSocketRead
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain, or the default unless not defined, for a
+ * identity with a certificate matching the passed certificate reference.
+ * Keychains are searched by creating a dictionary of key/value pairs with the
+ * search criteria and then asking for a copy of the matching entry/entries to
+ * the search criteria.
+ */
+static OSStatus
+import_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ SecIdentityRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * We don't need to set a kSecMatchLimit key since the default is to only
+ * return a single reference. Older versions of macOS had issues with
+ * certificate matching on labels, but we don't support older versions so
+ * no need to extract all and match ourselves.
+ */
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+
+ if (status == noErr)
+ *identity = (SecIdentityRef) CFRetain(temp);
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_certificate_keychain(const char *common_name, SecCertificateRef *certificate,
+ CFArrayRef keychains, char *hostname)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFStringRef host = NULL;
+ CFArrayRef temp;
+ SecPolicyRef ssl_policy;
+ int i;
+
+ /*
+ * Make sure the user didn't just specify the keychain prefix as the
+ * certificate config. The passed certificate will have the keychain
+ * prefix stripped so in that case the string is expected to be empty.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ /*
+ * If we didn't get a set of Keychains passed, skip adding it to the
+ * dictionary thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * Specifying a hostname requires it to match the hostname in the leaf
+ * certificate.
+ */
+ if (hostname)
+ host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8);
+ ssl_policy = SecPolicyCreateSSL(true, host);
+ CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label has been reported to not work (revisions of 10.12), we request
+ * all and find the one we want. Copy all the results to a temp array and
+ * scan it for the certificate we are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i);
+
+ if (search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ *certificate = (SecCertificateRef) CFRetain(search_cert);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(ssl_policy);
+ CFRelease(query);
+ CFRelease(cert);
+ if (host)
+ CFRelease(host);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, char *passphrase, CFArrayRef *certificate)
+{
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+ struct stat buf;
+
+ if (!path || strlen(path) == 0)
+ return errSecInvalidValue;
+
+ if (stat(path, &buf) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return -1;
+
+ return errSecInvalidValue;
+ }
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInvalidValue;
+
+ certdata = malloc(buf.st_size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, buf.st_size, fp) != buf.st_size)
+ {
+ fclose(fp);
+ free(certdata);
+ return errSSLBadCert;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, buf.st_size);
+ free(certdata);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecAllocate;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ return SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ bool have_homedir;
+ bool cert_from_file = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecCertificateRef root_ref[1];
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+ SecKeychainRef kcref[2];
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching. If no additional keychain is specified we don't need to open
+ * the default keychain and pass to searches since Secure Transport will
+ * use the default when passing NULL instead of an array of Keychain refs.
+ */
+ if (conn->keychain)
+ {
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr && kcref[0] != NULL)
+ {
+ SecKeychainStatus kcstatus;
+ status = SecKeychainGetStatus(kcref[0], &kcstatus);
+ if (status == noErr)
+ {
+ switch (kcstatus)
+ {
+ /*
+ * If the Keychain is already unlocked, readable or
+ * writeable, we don't need to do more. If not, try to
+ * unlock it.
+ */
+ case kSecUnlockStateStatus:
+ case kSecReadPermStatus:
+ case kSecWritePermStatus:
+ break;
+ default:
+ /*
+ * TODO: we need to get the passphrase from the
+ * user, but we currently don't have a good
+ * mechanism for that. For now, we assume a blank
+ * passphrase but we need to figure out a good way
+ * to have the user enter the passphrase.
+ */
+ SecKeychainUnlock(kcref[0], 0, "", TRUE);
+ break;
+ }
+ }
+
+ /*
+ * We only need to open, and add, the default Keychain in case
+ * have a user keychain opened, else we will pass NULL to any
+ * keychain search which will use the default keychain by..
+ * default.
+ */
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the files
+ * could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Try to load the root cert from either a user defined keychain or the
+ * default Keychain in case none is specified
+ */
+ if (conn->sslrootcert &&
+ pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ root_ref[0] = NULL;
+ strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL);
+
+ if (root_ref[0])
+ conn->st_rootcert = (void *) CFArrayCreate(NULL,
+ (const void **) root_ref,
+ 1, &kCFTypeArrayCallBacks);
+ }
+
+ if (!conn->st_rootcert)
+ {
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+ }
+
+ /*
+ * If the sslcert config is prefixed with a keychain identifier, the cert
+ * must be located in either the default or the specified keychain.
+ */
+ if (conn->sslcert &&
+ pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ status = import_identity_keychain(fnbuf, &identity, keychains);
+
+ if (identity && status == noErr)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ else
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate \"%s\" from keychain: %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+
+ return status;
+ }
+ }
+ /*
+ * No prefix on the sslcert config, the certificate must thus reside in a
+ * file on disk.
+ */
+ else
+ {
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, cert_array);
+ if (status != noErr)
+ {
+ if (status == -1)
+ return noErr;
+
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ cert_from_file = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in
+ * order to create the identity.
+ */
+
+ /*
+ * The sslkey config is prefixed with keychain: indicating that the
+ * key should be loaded from Keychain instead of the filesystem.
+ * Search for the private key matching the cert_ref in the opened
+ * Keychains. If found, we get the identity returned.
+ */
+ if (conn->sslkey &&
+ pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref,
+ &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the
+ * key to be loaded from that file.
+ */
+ if (fnbuf[0] != '\0')
+ {
+
+ if (stat(fnbuf, &buf) != 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+ fnbuf);
+ return errSecInvalidKeyRef;
+ }
+#ifndef WIN32
+ if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
+ fnbuf);
+ return errSecInvalidKeyRef;
+ }
+#endif
+ status = import_pem(fnbuf, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+ }
+ }
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ if (key_ref)
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ }
+
+ if (!identity)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create identity from certificate/key\n"));
+ return errSecInvalidValue;
+ }
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (cert_from_file)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate(conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher(conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion(conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState(conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249..5fa6734 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -157,8 +157,10 @@ void
PQinitOpenSSL(int do_ssl, int do_crypto)
{
#ifdef USE_SSL
+#ifdef USE_OPENSSL
pgtls_init_library(do_ssl, do_crypto);
#endif
+#endif
}
/*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index bdd8f9d..94b56aa 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -80,6 +80,14 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_SECURETRANSPORT
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#undef Size
+#undef uint64
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -474,8 +482,19 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+ bool keychain_use_default;
+
+ SSLContextRef ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
@@ -728,6 +747,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
*
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
+ * It's also not supported with Apple Secure Transport.
*/
#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 97389c9..0c9b4cd 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_securetransport
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -130,6 +131,20 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### Keychains
+
+ifeq ($(with_securetransport),yes)
+
+KEYCHAINS := ssl/client.keychain
+
+# This target generates all Keychains
+keychains: $(KEYCHAINS)
+
+PWD=$(shell pwd)
+ssl/client.keychain: ssl/client.crt ssl/client.key
+ certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p=
+endif
+
.PHONY: sslfiles-clean
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
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index 3b451a3..2331939 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -26,12 +26,22 @@ use Test::More;
use Exporter 'import';
our @EXPORT = qw(
+ set_backend
configure_test_server_for_ssl
switch_server_cert
test_connect_fails
test_connect_ok
);
+our $current_backend;
+
+sub set_backend
+{
+ my ($backend) = @_;
+
+ $current_backend = $backend;
+}
+
# Define a couple of helper functions to test connecting to the server.
# The first argument is a base connection string to use for connection.
@@ -56,7 +66,7 @@ sub test_connect_fails
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($common_connstr, $connstr, $expected_stderr, $test_name) = @_;
+ my ($common_connstr, $connstr, $test_name, %expected_stderr) = @_;
my $cmd = [
'psql', '-X', '-A', '-t', '-c',
@@ -64,7 +74,7 @@ sub test_connect_fails
'-d', "$common_connstr $connstr"
];
- command_fails_like($cmd, $expected_stderr, $test_name);
+ command_fails_like($cmd, $expected_stderr{$current_backend}, $test_name);
return;
}
@@ -155,7 +165,14 @@ sub switch_server_cert
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";
+ if (check_pg_config("#define USE_SECURETRANSPORT 1"))
+ {
+ print $sslconf "ssl_crl_file=''\n";
+ }
+ else
+ {
+ print $sslconf "ssl_crl_file='root+client.crl'\n";
+ }
close $sslconf;
$node->restart;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2b875a3..6352bb0 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -6,9 +6,34 @@ use Test::More;
use ServerSetup;
use File::Copy;
+# Some tests are backend specific, so store the currently used backend and
+# some particular capabilities which are interesting for the test
+my %tls_backend;
+
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 65;
+ $tls_backend{library} = 'openssl';
+ $tls_backend{library_name} = 'OpenSSL';
+ $tls_backend{tests} = 65;
+ $tls_backend{crl_support} = 1;
+ $tls_backend{keychain_support} = 0;
+}
+elsif ($ENV{with_securetransport} eq 'yes')
+{
+ $tls_backend{library} = 'securetransport';
+ $tls_backend{library_name} = 'Secure Transport';
+ $tls_backend{tests} = 68;
+ $tls_backend{crl_support} = 0;
+ $tls_backend{keychain_support} = 1;
+}
+else
+{
+ $tls_backend{library} = undef;
+}
+
+if (defined $tls_backend{library})
+{
+ plan tests => $tls_backend{tests};
}
else
{
@@ -25,6 +50,8 @@ my $SERVERHOSTADDR = '127.0.0.1';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
+set_backend($tls_backend{library});
+
# The client's private key must not be world-readable, so take a copy
# of the key stored in the code tree and update its permissions.
copy("ssl/client.key", "ssl/client_tmp.key");
@@ -52,7 +79,7 @@ $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, $tls_backend{library_name}, 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, 'trust');
@@ -65,10 +92,28 @@ 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);
+if ($tls_backend{library} eq 'securetransport')
+{
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=invalid sslmode=require",
+ "connect without server root cert sslmode=require",
+ ( securetransport => qr/record overflow/ ));
+}
+else
+{
+ 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";
@@ -98,8 +143,8 @@ $common_connstr =
# The server should not accept non-SSL connections.
test_connect_fails(
$common_connstr, "sslmode=disable",
- qr/\Qno pg_hba.conf entry\E/,
- "server doesn't accept non-SSL connections");
+ "server doesn't accept non-SSL connections",
+ ( openssl => qr/\Qno pg_hba.conf entry\E/ ,securetransport => qr/\Qno pg_hba.conf entry\E/ ));
# Try without a root cert. In sslmode=require, this should work. In verify-ca
# or verify-full mode it should fail.
@@ -110,31 +155,50 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
- "connect without server root cert sslmode=verify-ca");
+ "connect without server root cert sslmode=verify-ca",
+ (
+ openssl => qr/root certificate file "invalid" does not exist/,
+ securetransport => qr/root certificate file "invalid" does not exist/
+ ));
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
- "connect without server root cert sslmode=verify-full");
+ "connect without server root cert sslmode=verify-full",
+ (
+ openssl => qr/root certificate file "invalid" does not exist/,
+ securetransport => qr/root certificate file "invalid" does not exist/
+ ));
# 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");
+ "connect with wrong server root cert sslmode=require",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
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");
+ "connect with wrong server root cert sslmode=verify-ca",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
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");
+ "connect with wrong server root cert sslmode=verify-full",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
# 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");
+ "connect with server CA cert, without root CA",
+ ( openssl => qr/SSL error/, securetransport => qr/SSL error/));
# And finally, with the correct root cert.
test_connect_ok(
@@ -163,24 +227,36 @@ test_connect_ok(
# 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",
- "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");
-
-# 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",
- "CRL with a non-revoked cert");
+if ($tls_backend{crl_support})
+{
+ # 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",
+ "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",
+ "CRL belonging to a different CA",
+ ( openssl => qr/SSL error/ ));
+
+ # 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",
+ "CRL with a non-revoked cert");
+}
+else
+{
+ # Test that the presence of a CRL configuration throws an error
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "unsupported sslcrl option",
+ ( securetransport => qr/CRL files are not supported/ ));
+}
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
@@ -198,8 +274,11 @@ 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/,
- "mismatch between host name and server certificate sslmode=verify-full");
+ "mismatch between host name and server certificate sslmode=verify-full",
+ (
+ openssl => qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
@@ -223,13 +302,19 @@ 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/,
- "host name not matching with X.509 Subject Alternative Names");
+ "host name not matching with X.509 Subject Alternative Names",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
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/,
- "host name not matching with X.509 Subject Alternative Names wildcard");
+ "host name not matching with X.509 Subject Alternative Names wildcard",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
@@ -246,14 +331,19 @@ 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/,
- "host name not matching with a single X.509 Subject Alternative Name");
+ "host name not matching with a single X.509 Subject Alternative Name",
+ (
+ openssl => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
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/,
- "host name not matching with a single X.509 Subject Alternative Name wildcard"
-);
+ "host name not matching with a single X.509 Subject Alternative Name wildcard",
+ (
+ openssl => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
@@ -273,8 +363,11 @@ 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/,
- "certificate with both a CN and SANs ignores CN");
+ "certificate with both a CN and SANs ignores CN",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
# 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.
@@ -289,8 +382,11 @@ 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/,
- "server certificate without CN or SANs sslmode=verify-full");
+ "server certificate without CN or SANs sslmode=verify-full",
+ (
+ openssl => qr/could not get server's host name from server certificate/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test that the CRL works
switch_server_cert($node, 'server-revoked');
@@ -303,11 +399,46 @@ test_connect_ok(
$common_connstr,
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
"connects without client-side CRL");
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
- qr/SSL error/,
- "does not connect with client-side CRL");
+if ($tls_backend{crl_support})
+{
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL",
+ ( openssl => qr/SSL error/ ));
+}
+else
+{
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL",
+ ( securetransport => qr/CRL files are not supported with Secure Transport/ ));
+}
+
+# test Secure Transport keychain support
+if ($tls_backend{keychain_support})
+{
+ $common_connstr =
+ "user=ssltestuser dbname=certdb hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ # empty keychain
+ test_connect_fails($common_connstr,
+ "keychain=invalid",
+ "invalid Keychain file reference",
+ ( securetransport => qr/The specified item has no access control/ ));
+
+ # correct client cert in keychain with and without proper label
+ test_connect_fails(
+ $common_connstr,
+ "keychain=ssl/client.keychain",
+ "client cert in keychain but without label",
+ ( securetransport => qr/The specified item has no access control/ ));
+ test_connect_ok(
+ $common_connstr,
+ "sslcert=keychain:ssltestuser keychain=ssl/client.keychain",
+ "client cert in keychain");
+}
+
### Server-side tests.
###
@@ -322,8 +453,11 @@ $common_connstr =
test_connect_fails(
$common_connstr,
"user=ssltestuser sslcert=invalid",
- qr/connection requires a valid client certificate/,
- "certificate authorization fails without client cert");
+ "certificate authorization fails without client cert",
+ (
+ openssl => qr/connection requires a valid client certificate/,
+ securetransport => qr/connection requires a valid client certificate/
+ ));
# correct client cert
test_connect_ok(
@@ -335,23 +469,30 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
- qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
- "certificate authorization fails because of file permissions");
+ "certificate authorization fails because of file permissions",
+ (
+ openssl => qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
+ securetransport => qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!
+ ));
# client cert belonging to another user
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
- "certificate authorization fails with client cert belonging to another user"
-);
+ "certificate authorization fails with client cert belonging to another user",
+ (
+ openssl => qr/certificate authentication failed for user "anotheruser"/,
+ securetransport => qr/certificate authentication failed for user "anotheruser"/
+ ));
-# revoked client cert
-test_connect_fails(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
- qr/SSL error/,
- "certificate authorization fails with revoked client cert");
+if ($tls_backend{crl_support})
+{
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "certificate authorization fails with revoked client cert",
+ ( openssl => qr/SSL error/, securetransport => qr/SSL error/ ));
+}
# 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');
@@ -363,8 +504,13 @@ test_connect_ok(
"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");
+ "intermediate client certificate is missing",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/certificate authentication failed/
+ ));
# clean up
unlink("ssl/client_tmp.key", "ssl/client_wrongperms_tmp.key",
"ssl/client-revoked_tmp.key");
+unlink("ssl/client.keychain") if ($tls_backend{keychain_support});
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index b460a7f..5c36bdd 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -8,7 +8,8 @@ use Test::More;
use ServerSetup;
use File::Copy;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_openssl} ne 'yes' &&
+ $ENV{with_securetransport} ne 'yes')
{
plan skip_all => 'SSL not supported by this build';
}
On 26 Sep 2018, at 01:08, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Heikki Linnakangas <hlinnaka@iki.fi> writes:
On 27/06/18 21:57, Daniel Gustafsson wrote:
Courtesy of the ever-present Murphy I managed to forget some testcode in
src/backend/Makefile which broke compilation for builds without secure
transport, attached v8 patch fixes that.I've read through this patch now in more detail. Looks pretty good, but
I have a laundry list of little things below. The big missing item is
documentation.I did some simple testing on this today. The patch has bit-rotted,
mostly as a consequence of 77291139c which removed tls-unique channel
binding. That's probably a good thing for the Secure Transport code,
since it wasn't supporting that anyway, but it needs to be updated.
Thanks for taking a look!
I ripped out the failing-to-compile code (maybe that was too simplistic?)
but could not figure out whether there was anything still useful about
the diffs in ssl/t/002_scram.pl, so I left those out. Anyway, the
attached update applies cleanly to today's HEAD, and the openssl part
still compiles cleanly and passes regression tests. The securetransport
part compiles cleanly, but it fails 8 of the 68 tests in 001_ssltests.pl.
I'm not sure how many of those might be new and how many were there as
of the previous submission.
I had some local diffs that I hacked up based on Heikkis review this summer,
but I never got around to sending them out. I’ve rebased these changes on top
of your v9 patch as the attached v10. With this patch I get 5 failing tests on
High Sierra.
The "-framework" option, being added to CFLAGS, is clang specific. I
think we need some more autoconf magic, to make this work with gcc.AFAIK, Apple's "gcc" *is* clang; it certainly has no problem with
these switches (and I rather doubt there's any hope of linking to
Secure Transport without 'em).
That is correct, there is however q legitimate question as to how to support
those who install an actual gcc via for example homebrew. -framework is a
linker option which according to the GCC docs isn’t being passed to the linker
by GCC. AFAICT from reading, -framework is however merely a convenient way of
using -l, so it should be doable to just use normal -l for both clang and gcc,
though I haven’t researched that. Another option is to require a macOS
clang(gcc) for secure transport support, at least initially.
It’s not clear to me just how common it is to use GCC via homebrew on macOS.
However, I agree that the technique
of teaching random makefiles about this explicitly is mighty ugly,
and we ought to put the logic into configure instead, if possible.
Possibly it could be modeled on LDAP_LIBS or ICU_LIBS, ie configure
sets up a macro that pulls in the openssl libraries, or the
secure transport libraries, or $other-implementation, or nothing.
The CFLAGS hacks need similar treatment (btw, should they be
CPPFLAGS hacks instead? I think they're morally equivalent to
-I switches). And avoid using "override" if at all possible.
Agreed, thats a better idea.
Some other comments:
* I notice that contrib/sslinfo hasn't been touched. That probably
ought to be on the to-do list for this, though I don't insist that
it needs to be in the first commit.
Right, that one is on the todo.
* I'm not sure what the "keychain" additions to test/ssl/Makefile
are for, but they don't seem to be wired up to anything. Running
"make keychains" has no impact on the test failures, either.
Since keychain files are binary blobs the make target is intended to create the
required keychain files out of the existing certificate/keys for use with the
SSL tests. Generating keychains needs to be wired to running the tests on
secure transport enabled systems (iff we want to have tests for keychains of
course).
* I do not like making the libpq connection parameters for this be
#ifdef'd out when the option isn't selected. I believe project policy is
that we accept all parameters always, and either ignore unsupported ones,
or throw errors if they're set to nondefault values (cf. the comment above
the sslmode parameter in fe-connect.c). I realize that some earlier
patches like GSSAPI did not get the word on this, but that's not a reason
to emulate their mistake. I'm not sure about the equivalent decision
w.r.t. backend GUCs, but we need to figure that out.
Makes sense. I added these just after the #if defined(ENABLE_GSS) lines and
just took a page from that playbook. Haven’t changed in the attached but agree
it should be done.
* In place of changes like -#ifdef USE_SSL +#if defined(USE_SSL) && defined(USE_OPENSSL) I'd be inclined to just do "#ifdef USE_OPENSSL", ie assume that macro can't be set without USE_SSL.
Good point, fixed in the attached.
Also, below are responses to the laundrylist raised by Heikki upthread:
--- laundry list begins ---What exactly does 'ssl_is_server_start' mean? I don't understand that mechanism. ISTM it's only checked in load_key(), via be_tls_open_server(), which should only be called after be_tls_init(), in which case it's always 'true' when it's checked. Should there be an Assertion on it or something?
Correct, this was a broken attempt at handling the server reload that didn’t
work due to brainfade. Removed and tidied up.
In be_tls_open_server(), I believe there are several cases of using variables uninitialized. For example, load_identity_keychain() doesn't always set the 'identity' output parameter, but there's an "if (identity == NULL)" check after the call to it. And 'certificates' is left uninitialized, if load_identity_keychain() is used. Also, 'dh_len' is used uninitialized here in the "if (!dh_buf || dh_len == 0)" line, if the 'ssl_dh_params_file' option is not set. Did clang not give warnings about these?’
Nope, no warnings were issueed. I do agree with your findings and have fixed
these and looked for others.
+ /* + * Certificate Revocation List are not supported in the Secure Transport + * API + */The corresponding client code has a longer comment on that:
+ /* + * There is no API to load CRL lists in Secure Transport, they can however + * be imported into a Keychain with the commandline application "certtool". + * For libpq to use them, the certificate/key and root certificate needs to + * be using an identity in a Keychain into which the CRL have been + * imported. That needs to be documented. + */Is it possible to programmatically create a temporary keychain, in memory, and load the CRL to it? (I googled a bit, and couldn't find any suitable API for it, so I guess not..)
I attempted this first using the lowlevel CSSM/CDSA APIs, but deleted it again
as it turned into well over 1500 lines of invoking undocumented CSSM API calls.
And it wasn’t event a complete support at that point. No official APIs exist
for dealing with CRL files. Having the user load the CRL into the Keychain is
about the only thing we can do.
+ if (ssl_crl_file[0]) + ereport(FATAL, + (errmsg("CRL files not supported with Secure Transport")));I think this should be COMMERROR, like other errors around this. We don't want to pass that error message to the client. Although I guess it won't reach the client as we haven't negotiated TLS yet.
Fixed.
+ /* + * We use kTryAuthenticate here since we don't know which sslmode the + * client is using. If we were to use kAlwaysAuthenticate then sslmode + * require won't work as intended. + */ + if (ssl_loaded_verify_locations) + SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);That comment sounds wrong. This is in backend code, and SSLSetClientSideAuthenticate() is all about checking the client's certificate in the server, while libpq 'sslmode' is about checking the server's certificate in the client.
Fixed.
+ for (;;) + { + status = SSLHandshake((SSLContextRef) port->ssl); + if (status == noErr) + break; + + if (status == errSSLWouldBlock) + continue; + ... + }Does this busy-wait, if there's not enough data from the client? That seems bad. In the corresponding client-side code, there's a comment on that too, but it's still bad.
It does busy-wait, and was doing so because I had misunderstood the Secure
Transport documentation. I’ve fixed it now similarly to how the OpenSSL code
handles it. Fixing it client-side remains a TODO.
In be_tls_get_version and PQsslAttribute, can we add support for kTLSProtocol13? Perhaps with an autoconf test, if the constant is not available on all macOS versions.
Good point, I’ve added an autoconf test for it with an ifdef around using it in
the code.
In be_tls_get_peerdn_name, wouldn't SecCertificateCopyCommonName() be more appropriate than SecCertificateCopyLongDescription()?
The OpenSSL support use X509_get_subject_name() which afaict returns the full
subject and not just the CN, so SecCertificateCopyLongDescription() should be
the equivalent.
In be_tls_get_cipher, returning "unknown" would seem better than erroring out, if the cipher isn't recognized.
Fixed.
Check error message style. eg.:
+ ereport(COMMERROR, + (errmsg("could not load server certificate \"%s\": \"%s\"", + ssl_cert_file, pg_SSLerrmessage(status))));Why is the result of pg_SSLerrmessage() in quotes? Maybe errdetail?
I really don’t know why I had put it in quotes, but it was clearly not a good
idea. Fixed all occurrences to not use quotes, but kept them in errmsg() for
now.
+ * Any consumers of the Keychain array should always call this to ensure that + * it is set up in a manner that reflect the configuration. If it not, thens/reflect/reflects/
Fixed.
+ else if (keychain_count == 2) + { + if (ssl_keychain_file[0] == '\0' || !ssl_keychain_use_default) + goto error; + + return; + } + else + /* Fallthrough to erroring out */ + +error: + ereport(FATAL, + (errmsg("Incorrect loading of Keychains detected"))); +}That fallthrough looks dangerous.
Fixed and refactored the function to be clearer while at it. The default for
the ssl_keychain_use_default GUC is set to false, which seems a safe bet.
During hacking I’ve temporarily set it to ‘on’ in the testsuite though, the
keychain setup for the tests needs to be figured out (as in how and what do we
want to test).
--- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -106,6 +106,7 @@ #ssl_dh_params_file = '' #ssl_passphrase_command = '' #ssl_passphrase_command_supports_reload = off +#ssl_keychain_file = ''Do we want to have this Secure Transport-specific option in the sample config file? Comment at least
On second thought I don’t think we do, so removed this.
+#elif USE_SECURETRANSPORT + void *ssl; + int ssl_buffered;Add comments. Mention the actual type of 'ssl' (SSLContextRef), and what 'ssl_buffered' means.
Fixed.
libpq-be.h and libpq-int.h use a different strategy to avoid clashes between PostgreSQL's and Secure Transport's versions of Size and uint64. Let's be consistent. (I like the libpq-fe.h version more, i.e. using "void *" and avoiding the #include)
Yeah, I’ve been meaning to fix that but clearly forgot. Fixed now.
- * SSL implementation (e.g. be-secure-openssl.c) + * SSL implementation (e.g. be-secure-<implementation>.c)Since this is just an example, I think leaving it as be-secure-openssl.c is fine.
Fixed.
Also update pg_config.h.win32 with the new USE_SECURETRANSPOT flag being added to pg_config.h.in.
Fixed.
In the call to SSLSetPeerDomainName(), should check return code.
Fixed.
+ /* + * In sslmode "require" we accept some certificate verification + * failures when we don't have a rootcert since MITM protection + * isn't enforced. Check the reported failure and trust in case + * the cert is missing, self signed or expired/future. + */ + if (strcmp(conn->sslmode, "require") == 0 && !conn->st_rootcert) + {Not just "require", but "allow" as well, right?
freePGconn should free conn->keychain. Would be good to write a little test that opens & closes millions of connectins, to check for memory leaks.
Fixed (the free part, not the util but I agree it would be interesting).
+ * Queries the specified Keychain, or the default unless not defined, for a
"unless not defined" is a double-negative. I don't understand that sentence. (there are two copies of the same comment in the patch, in FE and BE. And the FE function is called "load_identity_keychain", but its comment says "import_identity_keychain” )
Fixed.
PQinitOpenSSL and PQinitSSL are inconsistent in whether to call pgtls_init_library(), when compiled with Secure Transport. pgtls_init_library() is a no-op, so it doesn't matter, but let's be consistent. Perhaps do "#define pgtls_init_library() ((void)true)" in the header?
Fixed.
s/readiblitity/readability/
s/dont/don't/
s/securetransport_common.h/securetransport.h/
s/f.e/for example/ (at least I'm not familiar with that abbreviation)
Fixed.
cheers ./daniel
Attachments:
secure-transport-v10.patchapplication/octet-stream; name=secure-transport-v10.patch; x-unix-mode=0644Download
From 8b3e0f6d2195283fe94d7e38ea7ec3442f79b754 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 26 Sep 2018 22:52:17 +0200
Subject: [PATCH] WIP: Add support for Apple Secure Transport SSL library
This adds frontend and backend support for the Secure Transport SSL
library on macOS, but currently lacks SCRAM support. Thus, the patch
is not in a final state but is provided for hacking and testing.
---
configure | 73 ++
configure.in | 21 +
src/Makefile.global.in | 1 +
src/backend/Makefile | 4 +
src/backend/libpq/Makefile | 5 +
src/backend/libpq/be-secure-securetransport.c | 1458 +++++++++++++++++++++
src/backend/libpq/be-secure.c | 4 +
src/backend/utils/misc/guc.c | 36 +
src/include/common/securetransport.h | 514 ++++++++
src/include/common/sha2.h | 4 +-
src/include/libpq/libpq-be.h | 31 +-
src/include/libpq/libpq.h | 4 +
src/include/pg_config.h.in | 8 +
src/include/pg_config.h.win32 | 7 +
src/include/pg_config_manual.h | 12 +-
src/interfaces/libpq/Makefile | 5 +
src/interfaces/libpq/fe-connect.c | 17 +
src/interfaces/libpq/fe-secure-securetransport.c | 1496 ++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 2 +
src/interfaces/libpq/libpq-int.h | 16 +-
src/test/ssl/Makefile | 15 +
src/test/ssl/ServerSetup.pm | 24 +-
src/test/ssl/t/001_ssltests.pl | 282 +++-
src/test/ssl/t/002_scram.pl | 3 +-
24 files changed, 3960 insertions(+), 82 deletions(-)
create mode 100644 src/backend/libpq/be-secure-securetransport.c
create mode 100644 src/include/common/securetransport.h
create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c
diff --git a/configure b/configure
index 23ebfa8f3d..e99993049b 100755
--- a/configure
+++ b/configure
@@ -708,6 +708,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
with_ldap
with_krb_srvnam
@@ -854,6 +855,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1554,6 +1556,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -7967,6 +7970,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
#
# SELinux
#
@@ -12998,6 +13036,41 @@ fi
fi
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <Security/Security.h>
+int
+main ()
+{
+int p = kTLSProtocol13;
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ $as_echo "#define HAVE_KTLSPROTOCOL13 1" >>confdefs.h
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
if test "$with_pam" = yes ; then
for ac_header in security/pam_appl.h
do :
diff --git a/configure.in b/configure.in
index 530f275993..315cb6a875 100644
--- a/configure.in
+++ b/configure.in
@@ -840,6 +840,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
#
# SELinux
#
@@ -1367,6 +1376,18 @@ 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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+ dnl Ideally we should be able to use AC_CHECK_DECLS here but it fails to
+ dnl pick up the Corefoundation style enums, so run a compilation to be more
+ dnl fail-safe
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+ [#include <Security/Security.h>],
+ [int p = kTLSProtocol13;])],
+ [AC_DEFINE(HAVE_KTLSPROTOCOL13, 1, [])], [])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9cf0c35f8f..48ee631083 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -185,6 +185,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_gssapi = @with_gssapi@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 3a58bf6685..a93b9a72bd 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -51,6 +51,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LIBS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-catalog-headers submake-utils-headers postgres $(POSTGRES_IMP)
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..3f8a8b96cb 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000000..fb8f467434
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1458 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future). This is a
+ * larger question than for this patch though.
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interesting to investigate.
+ * - Replace usage of internal error buffer with straight calls to
+ * ereport() where possible
+ * - Doublecheck that the used OSStatus return values make sense for
+ * error reporting
+ * - Figure out if any API calls need version gating like kTLSProtocol13
+ * - Replace -framework linker flags with -l to be GCC compatible
+ * - Fix busy-wait loop in frontend like how it's fixed in backend
+ * - contrib/sslinfo
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "common/base64.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+/*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there was for the longest time no translation and it was missing from
+ * the OSStatus enum. As it's easy enough, let's maintain our own.
+ */
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err. Since we
+ * may have encountered an error due to memory pressure, we don't want to rely
+ * on dynamically allocating memory for this error message.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+/*
+ * SecIdentityCreate is not an exported Secure Transport API. There is however
+ * no exported Secure Transport function that can create an identity from a
+ * SecCertificateRef and a SecKeyRef without having either of them in a
+ * Keychain. This function is commonly used in open source projects (such as
+ * ffmpeg and mono for example), but finding an alternative is a TODO.
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static bool load_key(char *name, CFArrayRef *out);
+static OSStatus load_keychain(char *name, CFArrayRef *keychains);
+static OSStatus load_certificate_file(char *name, CFArrayRef *cert_array);
+static OSStatus load_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+
+static int load_dh_file(char *filename, char **buf);
+static void load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl);
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+static void KeychainEnsureValid(CFArrayRef keychains);
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize global Secure Transport structures (if any)
+ *
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ *
+ * Another reason to defer this until when in the backend is that Keychains
+ * are SQLite3 backed, and sqlite does not allow access across a fork. See
+ * https://sqlite.org/faq.html#q6 for more information.
+ *
+ * This further means that the loglevel cannot be controlled using the
+ * isServerStart parameter like to OpenSSL is using it. For Secure Transport
+ * we always fall back to isServerStart being false.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * be_tls_open_server
+ * Attempt to negotiate a secure connection
+ *
+ * Unlike the OpenSSL backend, this function is responsible for the deferred
+ * loading of keys and certificates for use in the connection. See the comment
+ * on be_tls_init for further reasoning around this.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFArrayRef keychains;
+ CFMutableArrayRef chain;
+ char *dh_buf;
+ int dh_len;
+
+ Assert(!port->ssl);
+
+ /*
+ * If ssl_keychain_file has been configured we try to load a Keychain from
+ * the specified filename as well as the Default user keychain in case the
+ * configuration allows that. Else, set keychains to NULL which indicates
+ * that only the default Keychain is available.
+ */
+ if (ssl_keychain_file[0])
+ {
+ status = load_keychain(ssl_keychain_file, &keychains);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load keychain(s): %s",
+ pg_SSLerrmessage(status))));
+ }
+ else
+ keychains = NULL;
+
+ /*
+ * Ensure that we have loaded the Keychains according to the configuration
+ * before proceeding.
+ */
+ KeychainEnsureValid(keychains);
+
+ /*
+ * If the ssl_cert_file is prefixed with a keychain reference, we will try
+ * to load a complete identity from the Keychain, else treat it as a
+ * reference to a plain file on the filesystem.
+ */
+ identity = NULL;
+ certificates = NULL;
+ if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains);
+ else
+ {
+ status = load_certificate_file(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ if (!load_key(ssl_key_file, &keys))
+ return -1;
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ }
+
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: %s",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ if (certificates != NULL)
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate_file(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": %s",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API, they need to be added to a Keychain using macOS userspace tools.
+ */
+ if (ssl_crl_file[0])
+ ereport(COMMERROR,
+ (errmsg("CRL files are not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * In case the user hasn't configured a DH parameters file, we load a pre-
+ * computed DH parameter to avoid having Secure Transport computing one for
+ * us (which is done by default unless one is set).
+ */
+ dh_buf = NULL;
+ dh_len = 0;
+ if (ssl_dh_params_file[0])
+ dh_len = load_dh_file(ssl_dh_params_file, &dh_buf);
+
+ if (!dh_buf || dh_len == 0)
+ {
+ dh_buf = pstrdup(FILE_DH2048);
+ dh_len = sizeof(FILE_DH2048);
+ }
+
+ load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl);
+
+ /*
+ * Set Tlsv1.2 as the minimum protocol version we allow for the connection
+ */
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: %s",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: %s",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: %s",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: %s",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: %s",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ /*
+ * If SSLHandshake returns errSSLWouldBlock we need to call it again
+ * when we can expect the call to return a different result. Wait on
+ * the socket according to the IO direction set in port->waitfor.
+ */
+ if (status == errSSLWouldBlock)
+ {
+ WaitLatchOrSocket(MyLatch, port->waitfor, port->sock, 0,
+ WAIT_EVENT_SSL_OPEN_SERVER);
+ continue;
+ }
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: %s",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: %s",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: %s",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: %s",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert;
+ CFStringRef usercert_cn;
+ const char *peer_cn;
+
+ usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+
+ /* Guard against empty/missing CNs */
+ peer_cn = CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8);
+ if (!peer_cn)
+ port->peer_cn = pstrdup("");
+ else
+ port->peer_cn = pstrdup(peer_cn);
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ */
+static bool
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ SecItemImportExportKeyParameters params;
+
+ if (!check_ssl_key_file_permissions(name, /* isServerStart */ false))
+ return false;
+
+ /*
+ * check_ssl_key_file_permissions() has already checked the file for
+ * existence and correct permissions, but we still need to stat it to
+ * get the filesize.
+ */
+ if (stat(name, &stat_buf) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ /*
+ * There is no way to set a callback for acquiring the passphrase like how
+ * OpenSSL does it, so we need to re-run the import if it failed with a
+ * passphrase missing status. If no ssl_passphrase_command has been set we
+ * currently don't retry, which is something that will need to be revisited.
+ * TODO: figure out what would be the least confusing to the user here;
+ * perhaps supplying our own fallback passphrase_command? (which should be
+ * a TLS backend common function since it wouldn't be Secure Transport
+ * specific?)
+ */
+ if (status == errSecPassphraseRequired)
+ {
+ if (ssl_passphrase_command[0] && ssl_passphrase_command_supports_reload)
+ {
+ const char *prompt = "Enter PEM pass phrase: ";
+ char buf[256];
+ CFStringRef passphrase;
+
+ run_ssl_passphrase_command(prompt, false /* isServerStart */, buf, sizeof(buf));
+
+ passphrase = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
+ params.passphrase = passphrase;
+ memset(&buf, '\0', sizeof(buf));
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(passphrase);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" cannot be loaded because it requires a passphrase",
+ name)));
+ }
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": %s",
+ name, pg_SSLerrmessage(status))));
+
+ return true;
+}
+
+/*
+ * load_keychain
+ * Open the specified, and default, Keychain
+ *
+ * Operations on keychains other than the default take the keychain references
+ * in an array. We need to copy a reference to the default keychain into the
+ * array to include it in the search.
+ *
+ * For server applications, we don't want modal dialog boxes opened for
+ * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE)
+ * will turn off all GUI interaction with the user, which may seem like what we
+ * want server side. This however has the side effect to turn off all GUI
+ * elements for all applications until some application calls
+ * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus
+ * remove wanted GUI interaction from another app, or another app might
+ * introduce it for us.
+ */
+static OSStatus
+load_keychain(char *name, CFArrayRef *keychains)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ SecKeychainRef kc[2];
+ int array_len = 1;
+
+ /*
+ * If we are passing in a non-empty CFArrayRef, and fail to load the user
+ * Keychain then we would risk injecting Keychains since we will trust this
+ * array from hereon. Unconditionally error out hard immediately to avoid.
+ */
+ if (*keychains != NULL)
+ ereport(FATAL,
+ (errmsg("requesting to load Keychains into already allocated memory")));
+
+ if (stat(name, &stat_buf) != 0)
+ return errSecInvalidValue;
+
+ status = SecKeychainOpen(name, &kc[0]);
+ if (status == noErr)
+ {
+ SecKeychainUnlock(kc[0], 0, "", TRUE);
+
+ /*
+ * If we are allowed to use the default Keychain, add it to the array
+ * to include it in Keychain searches. If we are only using the default
+ * Keychain and no user defined Keychain we don't create an array at
+ * all since the recommended procedure is to pass NULL instead of an
+ * array containing only a reference to the default Keychain.
+ */
+ if (ssl_keychain_use_default)
+ SecKeychainCopyDefault(&kc[array_len++]);
+
+ *keychains = CFArrayCreate(NULL, (const void **) kc, array_len,
+ &kCFTypeArrayCallBacks);
+
+ if (!*keychains)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to allocate memory");
+ return errSecInternalError;
+ }
+ }
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain for a identity with a certificate matching
+ * the passed certificate reference. If the passed keychains array reference is
+ * NULL the default user Keychain will be searched. Keychains are searched by
+ * creating a dictionary of key/value pairs with the search criteria and then
+ * asking for a copy of the matching entry/entries to the search criteria.
+ */
+static OSStatus
+load_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+/*
+ * load_certificate_file
+ * Extracts a certificate from a PEM file on the filesystem
+ */
+static OSStatus
+load_certificate_file(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to find file");
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to open file for reading");
+ return errSecInternalError;
+ }
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to read file");
+ return errSecInternalError;
+ }
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL,
+ cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ snprintf(internal_err, ERR_MSG_LEN, "unable to open file for reading");
+ return errSecInternalError;
+}
+
+/*
+ * load_dh_file
+ * Slurp the contents of the specified file into buf
+ *
+ * Open the supplied filename and load its contents. This function is only
+ * reading the data without assessing its structure, actually parsing it is
+ * performed by load_dh_params(). The reason for splitting up the process is
+ * that we also support loading hardcoded DH params.
+ */
+static int
+load_dh_file(char *filename, char **buf)
+{
+ FILE *dh;
+ struct stat stat_buf;
+ int ret;
+
+ /*
+ * Open the DH file and slurp the contents. If the file doesn't exist it's
+ * not an error, if it can't be opened it is however an error.
+ */
+ ret = stat(filename, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ return 0;
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((dh = AllocateFile(filename, "r")) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+
+ *buf = palloc(stat_buf.st_size);
+ ret = fread(*buf, 1, stat_buf.st_size, dh);
+ FreeFile(dh);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read DH parameters file \"%s\": %m",
+ filename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("DH parameters file \"%s\" is not a regular file",
+ filename)));
+
+ return ret;
+}
+
+/*
+ * load_dh_params
+ * Load the specified DH params for the connection
+ *
+ * Secure Transport requires the DH params to be in DER format, but to be
+ * compatible with the OpenSSL code we also support PEM and convert to DER
+ * before loading. Conversion does rudimentary PEM parsing, if we miss the
+ * data being correct, the Secure Transport API will give an error anyways so
+ * we're just checking basic integrity.
+ *
+ * This function may scribble on the dh parameter so if that's required so stay
+ * intact in the caller, a copy should be sent.
+ */
+#define DH_HEADER "-----BEGIN DH PARAMETERS-----"
+#define DH_FOOTER "-----END DH PARAMETERS-----"
+static void
+load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl)
+{
+ OSStatus status;
+ char *der;
+ int der_len;
+
+ Assert(dh);
+
+ /* Convert PEM to DER */
+ if (is_pem)
+ {
+ char *head;
+ char *tail;
+ int pem_len = 0;
+
+ if (strstr(dh, DH_HEADER) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid PEM format for DH parameter, header missing")));
+
+ dh += strlen(DH_HEADER);
+ tail = strstr(dh, DH_FOOTER);
+ if (!tail)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid PEM format for DH parameter, footer missing")));
+ *tail = '\0';
+
+ /* In order to PEM convert it we need to remove all newlines */
+ head = dh;
+ tail = dh;
+ while (*head != '\0')
+ {
+ if (*head != '\n')
+ {
+ *tail++ = *head++;
+ pem_len++;
+ }
+ else
+ head++;
+ }
+ *tail = '\0';
+
+ der = palloc(pg_b64_dec_len(strlen(dh)) + 1);
+ der_len = pg_b64_decode(dh, strlen(dh), der);
+ der[der_len] = '\0';
+ }
+ else
+ {
+ der = dh;
+ der_len = len;
+ }
+
+ status = SSLSetDiffieHellmanParams(ssl, der, der_len);
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unable to load DH parameters: %s",
+ pg_SSLerrmessage(status))));
+}
+
+/*
+ * be_tls_close
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect TLS version for connection: %s",
+ pg_SSLerrmessage(status))));
+
+ if (protocol == kTLSProtocol11)
+ return "TLSv1.1";
+ else if (protocol == kTLSProtocol12)
+ return "TLSv1.2";
+#ifdef HAVE_KTLSPROTOCOL13
+ else if (protocol == kTLSProtocol13)
+ return "TLSv1.3";
+#endif
+
+ return "unknown";
+}
+
+/*
+ * be_tls_read
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * be_tls_write
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr && trust != NULL)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect cipher for connection: %s",
+ pg_SSLerrmessage(status))));
+
+ return pg_SSLciphername(cipher);
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * The Secure Transport supplied error string for invalid passphrase only
+ * reads "invalid attribute" without any reference to a passphrase, which
+ * can be confusing. Override with our own.
+ */
+ if (status == errSecInvalidAttributePassphrase)
+ return pstrdup(_("Incorrect passphrase."));
+
+ if (status == errSSLRecordOverflow)
+ return pstrdup(_("SSL error"));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ port->waitfor = WL_SOCKET_READABLE;
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ port->waitfor = WL_SOCKET_WRITEABLE;
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Misc */
+/* ------------------------------------------------------------ */
+
+/*
+ * KeychainEnsureValid
+ * Ensure the validity of using the passed Keychain array
+ *
+ * Any consumers of the Keychain array should always call this to ensure that
+ * it is set up in a manner that reflects the configuration. If it not, then
+ * this function will fatally error out as all codepaths should assume that
+ * setup has been done correctly and that there is no recovery in case it
+ * hasn't.
+ */
+static void
+KeychainEnsureValid(CFArrayRef keychains)
+{
+ int keychain_count;
+
+ /*
+ * If the keychain array is unallocated, we must be allowed to use the
+ * default user Keychain, as that will be the effect of passing NULL to the
+ * Keychain search API. If not, error out.
+ */
+ if (!keychains && ssl_keychain_use_default)
+ return;
+
+ if (keychains)
+ keychain_count = CFArrayGetCount(keychains);
+ else
+ keychain_count = 0;
+
+ /*
+ * If we have one Keychain loaded then we must have a Keychain file
+ * configured, and not be allowed to use the default Keychain. If we have
+ * two then we must have a Keychain file configured *and* be allowed to use
+ * the default user Keychain. If we have any other number of Keychains in
+ * the array then we definitely have an incorrect situation.
+ */
+ if (keychain_count == 1)
+ {
+ if (ssl_keychain_file[0] != '\0' && !ssl_keychain_use_default)
+ return;
+ }
+ else if (keychain_count == 2)
+ {
+ if (ssl_keychain_file[0] != '\0' && ssl_keychain_use_default)
+ return;
+ }
+
+ ereport(FATAL,
+ (errmsg("incorrect loading of Keychains detected")));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index d349d7c2c7..21d5eb414c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -46,6 +46,10 @@ char *ssl_crl_file;
char *ssl_dh_params_file;
char *ssl_passphrase_command;
bool ssl_passphrase_command_supports_reload;
+#ifdef USE_SECURETRANSPORT
+char *ssl_keychain_file;
+bool ssl_keychain_use_default = false;
+#endif
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e9f542cfed..c4a3925b6f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -511,6 +511,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -1830,6 +1831,17 @@ static struct config_bool ConfigureNamesBool[] =
true,
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_use_default", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Allow using the default Keychain of the current user."),
+ NULL
+ },
+ &ssl_keychain_use_default,
+ false,
+ NULL, NULL, NULL
+ },
+#endif
/* End-of-list marker */
{
@@ -3516,6 +3528,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
{
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
@@ -3778,6 +3802,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the Keychain file."),
+ NULL
+ },
+ &ssl_keychain_file,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000000..f66555b382
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,514 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return "unknown";
+}
+
+#ifndef FRONTEND
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement. This
+ * function is currently only used in the libpq backend.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0d28..b446d491ec 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index ef5528c897..05a2ed57cc 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,12 +191,28 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+
+ /*
+ * ssl is an SSLContextRef, but including the appropriate header file from
+ * Secure Transport causes a clash on the Size type so we need to cast it
+ * from a void pointer when used.
+ */
+ void *ssl;
+
+ /*
+ * When writing data to the socket, Secure Transport can buffer some of
+ * the data instead of writing it to the socket. ssl_buffered tracks the
+ * number of buffered bytes to ensure that we drain the buffer.
+ */
+ int ssl_buffered;
+ int waitfor;
#endif
} Port;
@@ -214,7 +238,7 @@ CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
/*
@@ -269,6 +293,7 @@ extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
*
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
+ * It's also not supported with Apple Secure Transport.
*/
#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 36baf6b919..284e1cc02f 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -83,6 +83,10 @@ extern char *ssl_crl_file;
extern char *ssl_dh_params_file;
extern char *ssl_passphrase_command;
extern bool ssl_passphrase_command_supports_reload;
+#ifdef USE_SECURETRANSPORT
+extern char *ssl_keychain_file;
+extern bool ssl_keychain_use_default;
+#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 5d4079609c..4e2cc2db80 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -932,6 +932,14 @@
/* Use replacement snprintf() functions. */
#undef USE_REPL_SNPRINTF
+/* Define to build with Apple Secure Transport support.
+ (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
+/* Define to allow using TLSv1.3 in Apple Secure Transport (introduced in
+ macOS 10.13 High Sierra) */
+#undef HAVE_KTLSPROTOCOL13
+
/* 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.h.win32 b/src/include/pg_config.h.win32
index 182698a087..6d8c18950f 100644
--- a/src/include/pg_config.h.win32
+++ b/src/include/pg_config.h.win32
@@ -717,6 +717,13 @@
/* Define to use OpenSSL for random number generation */
/* #undef USE_OPENSSL_RANDOM */
+/* Define to build with Apple Secure Transport support. (--with-securetransport) */
+/* #undef USE_SECURETRANSPORT */
+
+/* Define to allow using TLSv1.3 in Apple Secure Transport (introduced in
+ macOS 10.13 High Sierra) */
+/* #undef HAVE_KTLSPROTOCOL13 */
+
/* Define to 1 to build with PAM support. (--with-pam) */
/* #undef USE_PAM */
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index b0365254f6..2731f821cc 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -162,11 +162,17 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
+#else
+#define SSL_LIBRARY "None"
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index ec0122a901..a0213646a5 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,11 @@ else
OBJS += sha2.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+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 45bc067921..22a272bcf8 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -325,6 +325,16 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain_use_default", NULL, NULL, NULL,
+ "UseDefaultKeychain", "", 1,
+ offsetof(struct pg_conn, keychain_use_default)},
+
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -3536,6 +3546,9 @@ makeEmptyPGconn(void)
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
+#ifdef USE_SECURETRANSPORT
+ conn->keychain_use_default = true;
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@@ -3672,6 +3685,10 @@ freePGconn(PGconn *conn)
#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
if (conn->gsslib)
free(conn->gsslib);
+#endif
+#ifdef USE_SECURETRANSPORT
+ if (conn->keychain)
+ free(conn->keychain);
#endif
/* Note that conn->Pfdebug is not ours to close or free */
if (conn->last_query)
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000000..4aa3dfa5c5
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1496 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readablitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef Size
+#define pg_uint64 uint64
+#define pg_Size Size
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *common_name,
+ SecCertificateRef *certificate,
+ CFArrayRef keychains,
+ char *hostname);
+static OSStatus import_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+static OSStatus import_pem(const char *path, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+
+/*
+ * pgtls_init_library
+ *
+ * Exported function to allow application to tell us it's already initialized
+ * the SSL library and/or libcrypto. Since the current implementation only
+ * allow do_crypto be set for the OpenSSL backend, this is a no-op for Secure
+ * Transport.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ return;
+}
+
+/*
+ * pgtls_open_client
+ * Begin, or continue, negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = (void *) SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin((SSLContextRef) conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection((SSLContextRef) conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs((SSLContextRef) conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we don't have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetPeerDomainName((SSLContextRef) conn->ssl, conn->pghost, strlen(conn->pghost));
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set peer hostname: %s\n"),
+ err_msg);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption((SSLContextRef) conn->ssl,
+ kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake((SSLContextRef) conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust((SSLContextRef) conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if ((strcmp(conn->sslmode, "require") == 0 ||
+ strcmp(conn->sslmode, "allow") == 0) && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ if (!trusted)
+ return errSecNoAccessForItem;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * pgtls_read_pending
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize((SSLContextRef) conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead((SSLContextRef) conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * pgtls_write
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+/*
+ * pgtls_init
+ * Initialize SSL system.
+ *
+ * There is little state or context to initialize for Secure Transport, the
+ * heavy lifting is performed by pgtls_open_client.
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose((SSLContextRef) conn->ssl);
+ CFRelease(conn->ssl);
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * pg_SSLSocketRead
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain for a identity with a certificate matching
+ * the passed certificate reference. If the passed keychains array reference is
+ * NULL, the default user Keychain will be searched. Keychains are searched by
+ * creating a dictionary of key/value pairs with the search criteria and then
+ * asking for a copy of the matching entry/entries to the search criteria.
+ */
+static OSStatus
+import_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ SecIdentityRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * We don't need to set a kSecMatchLimit key since the default is to only
+ * return a single reference. Older versions of macOS had issues with
+ * certificate matching on labels, but we don't support older versions so
+ * no need to extract all and match ourselves.
+ */
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+
+ if (status == noErr)
+ *identity = (SecIdentityRef) CFRetain(temp);
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_certificate_keychain(const char *common_name, SecCertificateRef *certificate,
+ CFArrayRef keychains, char *hostname)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFStringRef host = NULL;
+ CFArrayRef temp;
+ SecPolicyRef ssl_policy;
+ int i;
+
+ /*
+ * Make sure the user didn't just specify the keychain prefix as the
+ * certificate config. The passed certificate will have the keychain
+ * prefix stripped so in that case the string is expected to be empty.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ /*
+ * If we didn't get a set of Keychains passed, skip adding it to the
+ * dictionary thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * Specifying a hostname requires it to match the hostname in the leaf
+ * certificate.
+ */
+ if (hostname)
+ host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8);
+ ssl_policy = SecPolicyCreateSSL(true, host);
+ CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label has been reported to not work (revisions of 10.12), we request
+ * all and find the one we want. Copy all the results to a temp array and
+ * scan it for the certificate we are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i);
+
+ if (search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ *certificate = (SecCertificateRef) CFRetain(search_cert);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(ssl_policy);
+ CFRelease(query);
+ CFRelease(cert);
+ if (host)
+ CFRelease(host);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, char *passphrase, CFArrayRef *certificate)
+{
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+ struct stat buf;
+
+ if (!path || strlen(path) == 0)
+ return errSecInvalidValue;
+
+ if (stat(path, &buf) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return -1;
+
+ return errSecInvalidValue;
+ }
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInvalidValue;
+
+ certdata = malloc(buf.st_size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, buf.st_size, fp) != buf.st_size)
+ {
+ fclose(fp);
+ free(certdata);
+ return errSSLBadCert;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, buf.st_size);
+ free(certdata);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecAllocate;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ return SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ bool have_homedir;
+ bool cert_from_file = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecCertificateRef root_ref[1];
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+ SecKeychainRef kcref[2];
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching. If no additional keychain is specified we don't need to open
+ * the default keychain and pass to searches since Secure Transport will
+ * use the default when passing NULL instead of an array of Keychain refs.
+ */
+ if (conn->keychain)
+ {
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr && kcref[0] != NULL)
+ {
+ SecKeychainStatus kcstatus;
+ status = SecKeychainGetStatus(kcref[0], &kcstatus);
+ if (status == noErr)
+ {
+ switch (kcstatus)
+ {
+ /*
+ * If the Keychain is already unlocked, readable or
+ * writeable, we don't need to do more. If not, try to
+ * unlock it.
+ */
+ case kSecUnlockStateStatus:
+ case kSecReadPermStatus:
+ case kSecWritePermStatus:
+ break;
+ default:
+ /*
+ * TODO: we need to get the passphrase from the
+ * user, but we currently don't have a good
+ * mechanism for that. For now, we assume a blank
+ * passphrase but we need to figure out a good way
+ * to have the user enter the passphrase.
+ */
+ SecKeychainUnlock(kcref[0], 0, "", TRUE);
+ break;
+ }
+ }
+
+ /*
+ * We only need to open, and add, the default Keychain in case
+ * have a user keychain opened, else we will pass NULL to any
+ * keychain search which will use the default keychain by..
+ * default.
+ */
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the files
+ * could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Try to load the root cert from either a user defined keychain or the
+ * default Keychain in case none is specified
+ */
+ if (conn->sslrootcert &&
+ pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ root_ref[0] = NULL;
+ strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL);
+
+ if (root_ref[0])
+ conn->st_rootcert = (void *) CFArrayCreate(NULL,
+ (const void **) root_ref,
+ 1, &kCFTypeArrayCallBacks);
+ }
+
+ if (!conn->st_rootcert)
+ {
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+ }
+
+ /*
+ * If the sslcert config is prefixed with a keychain identifier, the cert
+ * must be located in either the default or the specified keychain.
+ */
+ if (conn->sslcert &&
+ pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ status = import_identity_keychain(fnbuf, &identity, keychains);
+
+ if (identity && status == noErr)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ else
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate \"%s\" from keychain: %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+
+ return status;
+ }
+ }
+ /*
+ * No prefix on the sslcert config, the certificate must thus reside in a
+ * file on disk.
+ */
+ else
+ {
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, cert_array);
+ if (status != noErr)
+ {
+ if (status == -1)
+ return noErr;
+
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ cert_from_file = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in
+ * order to create the identity.
+ */
+
+ /*
+ * The sslkey config is prefixed with keychain: indicating that the
+ * key should be loaded from Keychain instead of the filesystem.
+ * Search for the private key matching the cert_ref in the opened
+ * Keychains. If found, we get the identity returned.
+ */
+ if (conn->sslkey &&
+ pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref,
+ &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the
+ * key to be loaded from that file.
+ */
+ if (fnbuf[0] != '\0')
+ {
+
+ if (stat(fnbuf, &buf) != 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+ fnbuf);
+ return errSecInvalidKeyRef;
+ }
+#ifndef WIN32
+ if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
+ fnbuf);
+ return errSecInvalidKeyRef;
+ }
+#endif
+ status = import_pem(fnbuf, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+ }
+ }
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ if (key_ref)
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ }
+
+ if (!identity)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create identity from certificate/key\n"));
+ return errSecInvalidValue;
+ }
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (cert_from_file)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate((SSLContextRef) conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher((SSLContextRef) conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ case kTLSProtocol13:
+ attribute = "TLSv1.3";
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState((SSLContextRef) conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249bf0..5fa6734643 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -157,8 +157,10 @@ void
PQinitOpenSSL(int do_ssl, int do_crypto)
{
#ifdef USE_SSL
+#ifdef USE_OPENSSL
pgtls_init_library(do_ssl, do_crypto);
#endif
+#endif
}
/*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index bdd8f9d9b2..da53de46d8 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -474,8 +474,19 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+ bool keychain_use_default;
+
+ void *ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
@@ -728,6 +739,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
*
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
+ * It's also not supported with Apple Secure Transport.
*/
#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 97389c90f8..0c9b4cdddd 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_securetransport
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -130,6 +131,20 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### Keychains
+
+ifeq ($(with_securetransport),yes)
+
+KEYCHAINS := ssl/client.keychain
+
+# This target generates all Keychains
+keychains: $(KEYCHAINS)
+
+PWD=$(shell pwd)
+ssl/client.keychain: ssl/client.crt ssl/client.key
+ certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p=
+endif
+
.PHONY: sslfiles-clean
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
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index 3b451a360a..ae4406bcaa 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -26,12 +26,22 @@ use Test::More;
use Exporter 'import';
our @EXPORT = qw(
+ set_backend
configure_test_server_for_ssl
switch_server_cert
test_connect_fails
test_connect_ok
);
+our $current_backend;
+
+sub set_backend
+{
+ my ($backend) = @_;
+
+ $current_backend = $backend;
+}
+
# Define a couple of helper functions to test connecting to the server.
# The first argument is a base connection string to use for connection.
@@ -56,7 +66,7 @@ sub test_connect_fails
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($common_connstr, $connstr, $expected_stderr, $test_name) = @_;
+ my ($common_connstr, $connstr, $test_name, %expected_stderr) = @_;
my $cmd = [
'psql', '-X', '-A', '-t', '-c',
@@ -64,7 +74,7 @@ sub test_connect_fails
'-d', "$common_connstr $connstr"
];
- command_fails_like($cmd, $expected_stderr, $test_name);
+ command_fails_like($cmd, $expected_stderr{$current_backend}, $test_name);
return;
}
@@ -114,6 +124,7 @@ sub configure_test_server_for_ssl
print $conf "log_hostname=on\n";
print $conf "listen_addresses='$serverhost'\n";
print $conf "log_statement=all\n";
+ print $conf "ssl_keychain_use_default=on\n";
# enable SSL and set up server key
print $conf "include 'sslconfig.conf'";
@@ -155,7 +166,14 @@ sub switch_server_cert
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";
+ if (check_pg_config("#define USE_SECURETRANSPORT 1"))
+ {
+ print $sslconf "ssl_crl_file=''\n";
+ }
+ else
+ {
+ print $sslconf "ssl_crl_file='root+client.crl'\n";
+ }
close $sslconf;
$node->restart;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2b875a3c95..6352bb0737 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -6,9 +6,34 @@ use Test::More;
use ServerSetup;
use File::Copy;
+# Some tests are backend specific, so store the currently used backend and
+# some particular capabilities which are interesting for the test
+my %tls_backend;
+
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 65;
+ $tls_backend{library} = 'openssl';
+ $tls_backend{library_name} = 'OpenSSL';
+ $tls_backend{tests} = 65;
+ $tls_backend{crl_support} = 1;
+ $tls_backend{keychain_support} = 0;
+}
+elsif ($ENV{with_securetransport} eq 'yes')
+{
+ $tls_backend{library} = 'securetransport';
+ $tls_backend{library_name} = 'Secure Transport';
+ $tls_backend{tests} = 68;
+ $tls_backend{crl_support} = 0;
+ $tls_backend{keychain_support} = 1;
+}
+else
+{
+ $tls_backend{library} = undef;
+}
+
+if (defined $tls_backend{library})
+{
+ plan tests => $tls_backend{tests};
}
else
{
@@ -25,6 +50,8 @@ my $SERVERHOSTADDR = '127.0.0.1';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
+set_backend($tls_backend{library});
+
# The client's private key must not be world-readable, so take a copy
# of the key stored in the code tree and update its permissions.
copy("ssl/client.key", "ssl/client_tmp.key");
@@ -52,7 +79,7 @@ $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, $tls_backend{library_name}, 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, 'trust');
@@ -65,10 +92,28 @@ 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);
+if ($tls_backend{library} eq 'securetransport')
+{
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=invalid sslmode=require",
+ "connect without server root cert sslmode=require",
+ ( securetransport => qr/record overflow/ ));
+}
+else
+{
+ 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";
@@ -98,8 +143,8 @@ $common_connstr =
# The server should not accept non-SSL connections.
test_connect_fails(
$common_connstr, "sslmode=disable",
- qr/\Qno pg_hba.conf entry\E/,
- "server doesn't accept non-SSL connections");
+ "server doesn't accept non-SSL connections",
+ ( openssl => qr/\Qno pg_hba.conf entry\E/ ,securetransport => qr/\Qno pg_hba.conf entry\E/ ));
# Try without a root cert. In sslmode=require, this should work. In verify-ca
# or verify-full mode it should fail.
@@ -110,31 +155,50 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
- "connect without server root cert sslmode=verify-ca");
+ "connect without server root cert sslmode=verify-ca",
+ (
+ openssl => qr/root certificate file "invalid" does not exist/,
+ securetransport => qr/root certificate file "invalid" does not exist/
+ ));
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
- "connect without server root cert sslmode=verify-full");
+ "connect without server root cert sslmode=verify-full",
+ (
+ openssl => qr/root certificate file "invalid" does not exist/,
+ securetransport => qr/root certificate file "invalid" does not exist/
+ ));
# 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");
+ "connect with wrong server root cert sslmode=require",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
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");
+ "connect with wrong server root cert sslmode=verify-ca",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
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");
+ "connect with wrong server root cert sslmode=verify-full",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
# 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");
+ "connect with server CA cert, without root CA",
+ ( openssl => qr/SSL error/, securetransport => qr/SSL error/));
# And finally, with the correct root cert.
test_connect_ok(
@@ -163,24 +227,36 @@ test_connect_ok(
# 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",
- "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");
-
-# 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",
- "CRL with a non-revoked cert");
+if ($tls_backend{crl_support})
+{
+ # 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",
+ "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",
+ "CRL belonging to a different CA",
+ ( openssl => qr/SSL error/ ));
+
+ # 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",
+ "CRL with a non-revoked cert");
+}
+else
+{
+ # Test that the presence of a CRL configuration throws an error
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "unsupported sslcrl option",
+ ( securetransport => qr/CRL files are not supported/ ));
+}
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
@@ -198,8 +274,11 @@ 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/,
- "mismatch between host name and server certificate sslmode=verify-full");
+ "mismatch between host name and server certificate sslmode=verify-full",
+ (
+ openssl => qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
@@ -223,13 +302,19 @@ 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/,
- "host name not matching with X.509 Subject Alternative Names");
+ "host name not matching with X.509 Subject Alternative Names",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
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/,
- "host name not matching with X.509 Subject Alternative Names wildcard");
+ "host name not matching with X.509 Subject Alternative Names wildcard",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
@@ -246,14 +331,19 @@ 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/,
- "host name not matching with a single X.509 Subject Alternative Name");
+ "host name not matching with a single X.509 Subject Alternative Name",
+ (
+ openssl => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
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/,
- "host name not matching with a single X.509 Subject Alternative Name wildcard"
-);
+ "host name not matching with a single X.509 Subject Alternative Name wildcard",
+ (
+ openssl => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
@@ -273,8 +363,11 @@ 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/,
- "certificate with both a CN and SANs ignores CN");
+ "certificate with both a CN and SANs ignores CN",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
# 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.
@@ -289,8 +382,11 @@ 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/,
- "server certificate without CN or SANs sslmode=verify-full");
+ "server certificate without CN or SANs sslmode=verify-full",
+ (
+ openssl => qr/could not get server's host name from server certificate/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test that the CRL works
switch_server_cert($node, 'server-revoked');
@@ -303,11 +399,46 @@ test_connect_ok(
$common_connstr,
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
"connects without client-side CRL");
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
- qr/SSL error/,
- "does not connect with client-side CRL");
+if ($tls_backend{crl_support})
+{
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL",
+ ( openssl => qr/SSL error/ ));
+}
+else
+{
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL",
+ ( securetransport => qr/CRL files are not supported with Secure Transport/ ));
+}
+
+# test Secure Transport keychain support
+if ($tls_backend{keychain_support})
+{
+ $common_connstr =
+ "user=ssltestuser dbname=certdb hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ # empty keychain
+ test_connect_fails($common_connstr,
+ "keychain=invalid",
+ "invalid Keychain file reference",
+ ( securetransport => qr/The specified item has no access control/ ));
+
+ # correct client cert in keychain with and without proper label
+ test_connect_fails(
+ $common_connstr,
+ "keychain=ssl/client.keychain",
+ "client cert in keychain but without label",
+ ( securetransport => qr/The specified item has no access control/ ));
+ test_connect_ok(
+ $common_connstr,
+ "sslcert=keychain:ssltestuser keychain=ssl/client.keychain",
+ "client cert in keychain");
+}
+
### Server-side tests.
###
@@ -322,8 +453,11 @@ $common_connstr =
test_connect_fails(
$common_connstr,
"user=ssltestuser sslcert=invalid",
- qr/connection requires a valid client certificate/,
- "certificate authorization fails without client cert");
+ "certificate authorization fails without client cert",
+ (
+ openssl => qr/connection requires a valid client certificate/,
+ securetransport => qr/connection requires a valid client certificate/
+ ));
# correct client cert
test_connect_ok(
@@ -335,23 +469,30 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
- qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
- "certificate authorization fails because of file permissions");
+ "certificate authorization fails because of file permissions",
+ (
+ openssl => qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
+ securetransport => qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!
+ ));
# client cert belonging to another user
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
- "certificate authorization fails with client cert belonging to another user"
-);
+ "certificate authorization fails with client cert belonging to another user",
+ (
+ openssl => qr/certificate authentication failed for user "anotheruser"/,
+ securetransport => qr/certificate authentication failed for user "anotheruser"/
+ ));
-# revoked client cert
-test_connect_fails(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
- qr/SSL error/,
- "certificate authorization fails with revoked client cert");
+if ($tls_backend{crl_support})
+{
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "certificate authorization fails with revoked client cert",
+ ( openssl => qr/SSL error/, securetransport => qr/SSL error/ ));
+}
# 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');
@@ -363,8 +504,13 @@ test_connect_ok(
"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");
+ "intermediate client certificate is missing",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/certificate authentication failed/
+ ));
# clean up
unlink("ssl/client_tmp.key", "ssl/client_wrongperms_tmp.key",
"ssl/client-revoked_tmp.key");
+unlink("ssl/client.keychain") if ($tls_backend{keychain_support});
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index b460a7fa8a..5c36bddd06 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -8,7 +8,8 @@ use Test::More;
use ServerSetup;
use File::Copy;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_openssl} ne 'yes' &&
+ $ENV{with_securetransport} ne 'yes')
{
plan skip_all => 'SSL not supported by this build';
}
--
2.14.1.145.gb3622a4ee
On 26/09/2018 23:19, Daniel Gustafsson wrote:
It’s not clear to me just how common it is to use GCC via homebrew on macOS.
I use that all the time.
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 26/09/2018 23:19, Daniel Gustafsson wrote:
It’s not clear to me just how common it is to use GCC via homebrew on macOS.
I use that all the time.
Hm, so did 5e2217131 break anything for you? Does that version of gcc
claim to know -F or -framework switches?
regards, tom lane
On 02/10/2018 15:40, Tom Lane wrote:
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> writes:
On 26/09/2018 23:19, Daniel Gustafsson wrote:
It’s not clear to me just how common it is to use GCC via homebrew on macOS.
I use that all the time.
Hm, so did 5e2217131 break anything for you? Does that version of gcc
claim to know -F or -framework switches?
This is not a problem. The Python and Tcl build flags have included
-framework switches since time immemorial. (I suspect the compiler just
treats them as linker switches like -l and -L and passes them on.)
--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 26 Sep 2018, at 23:19, Daniel Gustafsson <daniel@yesql.se> wrote:
I’ve rebased these changes on top of your v9 patch as the attached v10.
Attached is a v11 rebased on top of todays HEAD, which had minor conflicts due
to the recent snprintf work. No functional changes are introduced over v10.
cheers ./daniel
Attachments:
secure-transport-v11.patchapplication/octet-stream; name=secure-transport-v11.patch; x-unix-mode=0644Download
From 3213c530227a7f6add972ee7aebba2f0b002379a Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Wed, 26 Sep 2018 22:52:17 +0200
Subject: [PATCH] WIP: Add support for Apple Secure Transport SSL library
This adds frontend and backend support for the Secure Transport SSL
library on macOS, but currently lacks SCRAM support. Thus, the patch
is not in a final state but is provided for hacking and testing.
---
configure | 73 ++
configure.in | 21 +
src/Makefile.global.in | 1 +
src/backend/Makefile | 4 +
src/backend/libpq/Makefile | 5 +
src/backend/libpq/be-secure-securetransport.c | 1458 +++++++++++++++++++++
src/backend/libpq/be-secure.c | 4 +
src/backend/utils/misc/guc.c | 36 +
src/include/common/securetransport.h | 514 ++++++++
src/include/common/sha2.h | 4 +-
src/include/libpq/libpq-be.h | 31 +-
src/include/libpq/libpq.h | 4 +
src/include/pg_config.h.in | 8 +
src/include/pg_config.h.win32 | 7 +
src/include/pg_config_manual.h | 12 +-
src/interfaces/libpq/Makefile | 5 +
src/interfaces/libpq/fe-connect.c | 17 +
src/interfaces/libpq/fe-secure-securetransport.c | 1496 ++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 2 +
src/interfaces/libpq/libpq-int.h | 16 +-
src/test/ssl/Makefile | 15 +
src/test/ssl/ServerSetup.pm | 24 +-
src/test/ssl/t/001_ssltests.pl | 282 +++-
src/test/ssl/t/002_scram.pl | 3 +-
24 files changed, 3960 insertions(+), 82 deletions(-)
create mode 100644 src/backend/libpq/be-secure-securetransport.c
create mode 100644 src/include/common/securetransport.h
create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c
diff --git a/configure b/configure
index 43ae8c869d..b45a351281 100755
--- a/configure
+++ b/configure
@@ -708,6 +708,7 @@ UUID_EXTRA_OBJS
with_uuid
with_systemd
with_selinux
+with_securetransport
with_openssl
with_ldap
with_krb_srvnam
@@ -854,6 +855,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_securetransport
with_selinux
with_systemd
with_readline
@@ -1554,6 +1556,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-securetransport build with Apple Secure Transport 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
@@ -7967,6 +7970,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# Apple Secure Transport
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5
+$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; }
+
+
+
+# Check whether --with-securetransport was given.
+if test "${with_securetransport+set}" = set; then :
+ withval=$with_securetransport;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_securetransport=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5
+$as_echo "$with_securetransport" >&6; }
+
+
#
# SELinux
#
@@ -12997,6 +13035,41 @@ fi
fi
+if test "$with_securetransport" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default"
+if test "x$ac_cv_header_Security_Security_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <Security/Security.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "CoreFoundation/CoreFoundation.h" "ac_cv_header_CoreFoundation_CoreFoundation_h" "$ac_includes_default"
+if test "x$ac_cv_header_CoreFoundation_CoreFoundation_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport" "$LINENO" 5
+fi
+
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+#include <Security/Security.h>
+int
+main ()
+{
+int p = kTLSProtocol13;
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ $as_echo "#define HAVE_KTLSPROTOCOL13 1" >>confdefs.h
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+
if test "$with_pam" = yes ; then
for ac_header in security/pam_appl.h
do :
diff --git a/configure.in b/configure.in
index 519ecd5e1e..9808df5f0c 100644
--- a/configure.in
+++ b/configure.in
@@ -840,6 +840,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# Apple Secure Transport
+#
+AC_MSG_CHECKING([whether to build with Apple Secure Transport support])
+PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support],
+ [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])])
+AC_MSG_RESULT([$with_securetransport])
+AC_SUBST(with_securetransport)
+
#
# SELinux
#
@@ -1395,6 +1404,18 @@ 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_securetransport" = yes ; then
+ AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file <Security/Security.h> is required for Apple Secure Transport])])
+ AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file <CoreFoundation/CoreFoundation.h> is required for Apple Secure Transport])])
+ dnl Ideally we should be able to use AC_CHECK_DECLS here but it fails to
+ dnl pick up the Corefoundation style enums, so run a compilation to be more
+ dnl fail-safe
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+ [#include <Security/Security.h>],
+ [int p = kTLSProtocol13;])],
+ [AC_DEFINE(HAVE_KTLSPROTOCOL13, 1, [])], [])
+fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index bdf394bb38..2f2556d7fa 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -185,6 +185,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_securetransport = @with_securetransport@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
with_gssapi = @with_gssapi@
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 3a58bf6685..a93b9a72bd 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -51,6 +51,10 @@ ifeq ($(with_systemd),yes)
LIBS += -lsystemd
endif
+ifeq ($(with_securetransport),yes)
+LIBS += -framework CoreFoundation -framework Security
+endif
+
##########################################################################
all: submake-libpgport submake-catalog-headers submake-utils-headers postgres $(POSTGRES_IMP)
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..3f8a8b96cb 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_securetransport),yes)
+OBJS += be-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c
new file mode 100644
index 0000000000..fb8f467434
--- /dev/null
+++ b/src/backend/libpq/be-secure-securetransport.c
@@ -0,0 +1,1458 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * TODO:
+ * - It would be good to be able to set "not applicable" on some options
+ * like compression which isn't supported in Secure Transport (and most
+ * likely any other SSL libraries supported in the future). This is a
+ * larger question than for this patch though.
+ * - Support memory allocation in Secure Transport via a custom Core
+ * Foundation allocator which is backed by a MemoryContext? Not sure it
+ * would be possible but would be interesting to investigate.
+ * - Replace usage of internal error buffer with straight calls to
+ * ereport() where possible
+ * - Doublecheck that the used OSStatus return values make sense for
+ * error reporting
+ * - Figure out if any API calls need version gating like kTLSProtocol13
+ * - Replace -framework linker flags with -l to be GCC compatible
+ * - Fix busy-wait loop in frontend like how it's fixed in backend
+ * - contrib/sslinfo
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-securetransport.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#endif
+
+#include "common/base64.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "storage/fd.h"
+#include "storage/latch.h"
+#include "tcop/tcopprot.h"
+#include "utils/backend_random.h"
+#include "utils/memutils.h"
+
+/*
+ * TODO: This dance is required due to collisions in the CoreFoundation
+ * headers. How to handle it properly?
+ */
+#define pg_ACL_DELETE ACL_DELETE
+#define pg_ACL_EXECUTE ACL_EXECUTE
+#undef ACL_EXECUTE
+#undef ACL_DELETE
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef Size
+#undef ACL_DELETE
+#undef ACL_EXECUTE
+#define pg_uint64 uint64
+#define pg_Size Size
+#define ACL_DELETE pg_ACL_DELETE
+#define ACL_EXECUTE pg_ACL_EXECUTE
+
+/*
+ * While errSecUnknownFormat has been defined as -25257 at least since 10.8
+ * Lion, there was for the longest time no translation and it was missing from
+ * the OSStatus enum. As it's easy enough, let's maintain our own.
+ */
+#ifndef errSecUnknownFormat
+#define errSecUnknownFormat -25257
+#endif
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/* ------------------------------------------------------------ */
+/* Struct definitions and Static variables */
+/* ------------------------------------------------------------ */
+
+/*
+ * For Secure Transport API functions we rely on SecCopyErrorMessageString()
+ * which will provide a human readable error message for the individual error
+ * statuses. For our static functions, we mimic the behaviour by passing
+ * errSecInternalError and setting the error message in internal_err. Since we
+ * may have encountered an error due to memory pressure, we don't want to rely
+ * on dynamically allocating memory for this error message.
+ */
+#define ERR_MSG_LEN 128
+static char internal_err[ERR_MSG_LEN];
+
+/* ------------------------------------------------------------ */
+/* Prototypes */
+/* ------------------------------------------------------------ */
+
+/*
+ * SecIdentityCreate is not an exported Secure Transport API. There is however
+ * no exported Secure Transport function that can create an identity from a
+ * SecCertificateRef and a SecKeyRef without having either of them in a
+ * Keychain. This function is commonly used in open source projects (such as
+ * ffmpeg and mono for example), but finding an alternative is a TODO.
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static bool load_key(char *name, CFArrayRef *out);
+static OSStatus load_keychain(char *name, CFArrayRef *keychains);
+static OSStatus load_certificate_file(char *name, CFArrayRef *cert_array);
+static OSStatus load_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+
+static int load_dh_file(char *filename, char **buf);
+static void load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl);
+static char * pg_SSLerrmessage(OSStatus status);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len);
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len);
+static void KeychainEnsureValid(CFArrayRef keychains);
+
+/* ------------------------------------------------------------ */
+/* Backend API */
+/* ------------------------------------------------------------ */
+
+/*
+ * be_tls_init
+ * Initialize global Secure Transport structures (if any)
+ *
+ * This is where we'd like to load and parse certificates and private keys
+ * for the connection, but since Secure Transport will spawn threads deep
+ * inside the API we must postpone this until inside a backend. This means
+ * that we won't fail on an incorrect certificate chain until a connection
+ * is attempted, unlike with OpenSSL where we fail immediately on server
+ * startup.
+ *
+ * Another reason to defer this until when in the backend is that Keychains
+ * are SQLite3 backed, and sqlite does not allow access across a fork. See
+ * https://sqlite.org/faq.html#q6 for more information.
+ *
+ * This further means that the loglevel cannot be controlled using the
+ * isServerStart parameter like to OpenSSL is using it. For Secure Transport
+ * we always fall back to isServerStart being false.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ memset(internal_err, '\0', sizeof(internal_err));
+
+ return 0;
+}
+
+/*
+ * be_tls_destroy
+ * Tear down global Secure Transport structures and return resources.
+ */
+void
+be_tls_destroy(void)
+{
+ ssl_loaded_verify_locations = false;
+}
+
+/*
+ * be_tls_open_server
+ * Attempt to negotiate a secure connection
+ *
+ * Unlike the OpenSSL backend, this function is responsible for the deferred
+ * loading of keys and certificates for use in the connection. See the comment
+ * on be_tls_init for further reasoning around this.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecTrustResultType trust_eval;
+ SecIdentityRef identity;
+ CFArrayRef root_certificates;
+ CFArrayRef certificates;
+ CFArrayRef keys;
+ CFArrayRef keychains;
+ CFMutableArrayRef chain;
+ char *dh_buf;
+ int dh_len;
+
+ Assert(!port->ssl);
+
+ /*
+ * If ssl_keychain_file has been configured we try to load a Keychain from
+ * the specified filename as well as the Default user keychain in case the
+ * configuration allows that. Else, set keychains to NULL which indicates
+ * that only the default Keychain is available.
+ */
+ if (ssl_keychain_file[0])
+ {
+ status = load_keychain(ssl_keychain_file, &keychains);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load keychain(s): %s",
+ pg_SSLerrmessage(status))));
+ }
+ else
+ keychains = NULL;
+
+ /*
+ * Ensure that we have loaded the Keychains according to the configuration
+ * before proceeding.
+ */
+ KeychainEnsureValid(keychains);
+
+ /*
+ * If the ssl_cert_file is prefixed with a keychain reference, we will try
+ * to load a complete identity from the Keychain, else treat it as a
+ * reference to a plain file on the filesystem.
+ */
+ identity = NULL;
+ certificates = NULL;
+ if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains);
+ else
+ {
+ status = load_certificate_file(ssl_cert_file, &certificates);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not load server certificate \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(status))));
+
+ if (!load_key(ssl_key_file, &keys))
+ return -1;
+
+ /*
+ * We now have a certificate and either a private key, or a search path
+ * which should contain it.
+ */
+ identity = SecIdentityCreate(NULL,
+ (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0),
+ (SecKeyRef) CFArrayGetValueAtIndex(keys, 0));
+ }
+
+ if (identity == NULL)
+ ereport(COMMERROR,
+ (errmsg("could not create identity: %s",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * SSLSetCertificate() sets the certificate(s) to use for the connection.
+ * The first element in the passed array is required to be the identity
+ * with elements 1..n being certificates.
+ */
+ chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ CFRetain(identity);
+ CFArrayInsertValueAtIndex(chain, 0, identity);
+ if (certificates != NULL)
+ CFArrayAppendArray(chain, certificates,
+ CFRangeMake(0, CFArrayGetCount(certificates)));
+
+ /*
+ * Load the Certificate Authority if configured
+ */
+ if (ssl_ca_file[0])
+ {
+ status = load_certificate_file(ssl_ca_file, &root_certificates);
+ if (status == noErr)
+ {
+ CFArrayAppendArray(chain, root_certificates,
+ CFRangeMake(0, CFArrayGetCount(root_certificates)));
+
+ ssl_loaded_verify_locations = true;
+ }
+ else
+ {
+ ereport(LOG,
+ (errmsg("could not load root certificate \"%s\": %s",
+ ssl_ca_file, pg_SSLerrmessage(status))));
+
+ ssl_loaded_verify_locations = false;
+ }
+ }
+ else
+ ssl_loaded_verify_locations = false;
+
+ /*
+ * Certificate Revocation List are not supported in the Secure Transport
+ * API, they need to be added to a Keychain using macOS userspace tools.
+ */
+ if (ssl_crl_file[0])
+ ereport(COMMERROR,
+ (errmsg("CRL files are not supported with Secure Transport")));
+
+ port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType);
+ if (!port->ssl)
+ ereport(COMMERROR,
+ (errmsg("could not create SSL context")));
+
+ port->ssl_in_use = true;
+ port->ssl_buffered = 0;
+
+ if (ssl_loaded_verify_locations)
+ SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate);
+
+ /*
+ * In case the user hasn't configured a DH parameters file, we load a pre-
+ * computed DH parameter to avoid having Secure Transport computing one for
+ * us (which is done by default unless one is set).
+ */
+ dh_buf = NULL;
+ dh_len = 0;
+ if (ssl_dh_params_file[0])
+ dh_len = load_dh_file(ssl_dh_params_file, &dh_buf);
+
+ if (!dh_buf || dh_len == 0)
+ {
+ dh_buf = pstrdup(FILE_DH2048);
+ dh_len = sizeof(FILE_DH2048);
+ }
+
+ load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl);
+
+ /*
+ * Set Tlsv1.2 as the minimum protocol version we allow for the connection
+ */
+ status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl,
+ kTLSProtocol12);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set protocol for connection: %s",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetCertificate((SSLContextRef) port->ssl,
+ (CFArrayRef) chain);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set certificate for connection: %s",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetIOFuncs((SSLContextRef) port->ssl,
+ pg_SSLSocketRead,
+ pg_SSLSocketWrite);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL IO functions: %s",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetSessionOption((SSLContextRef) port->ssl,
+ kSSLSessionOptionBreakOnClientAuth, true);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not set SSL certificate validation: %s",
+ pg_SSLerrmessage(status))));
+
+ status = SSLSetConnection((SSLContextRef) port->ssl, port);
+ if (status != noErr)
+ ereport(COMMERROR,
+ (errmsg("could not establish SSL connection: %s",
+ pg_SSLerrmessage(status))));
+
+ /*
+ * Perform handshake
+ */
+ for (;;)
+ {
+ status = SSLHandshake((SSLContextRef) port->ssl);
+ if (status == noErr)
+ break;
+
+ /*
+ * If SSLHandshake returns errSSLWouldBlock we need to call it again
+ * when we can expect the call to return a different result. Wait on
+ * the socket according to the IO direction set in port->waitfor.
+ */
+ if (status == errSSLWouldBlock)
+ {
+ WaitLatchOrSocket(MyLatch, port->waitfor, port->sock, 0,
+ WAIT_EVENT_SSL_OPEN_SERVER);
+ continue;
+ }
+
+ if (status == errSSLClosedAbort || status == errSSLClosedGraceful)
+ return -1;
+
+ if (status == errSSLPeerAuthCompleted)
+ {
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ {
+ ereport(WARNING,
+ (errmsg("SSLCopyPeerTrust returned: %s",
+ pg_SSLerrmessage(status))));
+ port->peer_cert_valid = false;
+ return 0;
+ }
+
+ if (ssl_loaded_verify_locations)
+ {
+ status = SecTrustSetAnchorCertificates(trust, root_certificates);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificates returned: %s",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, false);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustSetAnchorCertificatesOnly returned: %s",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+ }
+
+ trust_eval = 0;
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status != noErr)
+ {
+ ereport(WARNING,
+ (errmsg("SecTrustEvaluate failed, returned: %s",
+ pg_SSLerrmessage(status))));
+ return -1;
+ }
+
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then an anchor certificate was reached
+ * without encountering any explicit user trust. If 'Proceed'
+ * then the user has chosen to explicitly trust a certificate
+ * in the chain by clicking "Trust" in the Keychain app.
+ */
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ port->peer_cert_valid = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). In the frontend
+ * we can in some circumstances allow this, but in the backend
+ * this always means that the client certificate is considered
+ * untrusted.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ port->peer_cert_valid = false;
+ break;
+
+ /*
+ * Treat all other cases as rejection without further
+ * questioning.
+ */
+ default:
+ port->peer_cert_valid = false;
+ break;
+ }
+
+ if (port->peer_cert_valid)
+ {
+ SecCertificateRef usercert;
+ CFStringRef usercert_cn;
+ const char *peer_cn;
+
+ usercert = SecTrustGetCertificateAtIndex(trust, 0L);
+ SecCertificateCopyCommonName(usercert, &usercert_cn);
+
+ /* Guard against empty/missing CNs */
+ peer_cn = CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8);
+ if (!peer_cn)
+ port->peer_cn = pstrdup("");
+ else
+ port->peer_cn = pstrdup(peer_cn);
+
+ CFRelease(usercert_cn);
+ }
+
+ CFRelease(trust);
+ }
+ }
+
+ if (status != noErr)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * load_key
+ * Extracts a key from a PEM file on the filesystem
+ */
+static bool
+load_key(char *name, CFArrayRef *out)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ SecItemImportExportKeyParameters params;
+
+ if (!check_ssl_key_file_permissions(name, /* isServerStart */ false))
+ return false;
+
+ /*
+ * check_ssl_key_file_permissions() has already checked the file for
+ * existence and correct permissions, but we still need to stat it to
+ * get the filesize.
+ */
+ if (stat(name, &stat_buf) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to open",
+ name)));
+
+ buf = palloc(stat_buf.st_size);
+
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": unable to read",
+ name)));
+
+ type = kSecItemTypePrivateKey;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ /*
+ * There is no way to set a callback for acquiring the passphrase like how
+ * OpenSSL does it, so we need to re-run the import if it failed with a
+ * passphrase missing status. If no ssl_passphrase_command has been set we
+ * currently don't retry, which is something that will need to be revisited.
+ * TODO: figure out what would be the least confusing to the user here;
+ * perhaps supplying our own fallback passphrase_command? (which should be
+ * a TLS backend common function since it wouldn't be Secure Transport
+ * specific?)
+ */
+ if (status == errSecPassphraseRequired)
+ {
+ if (ssl_passphrase_command[0] && ssl_passphrase_command_supports_reload)
+ {
+ const char *prompt = "Enter PEM pass phrase: ";
+ char buf[256];
+ CFStringRef passphrase;
+
+ run_ssl_passphrase_command(prompt, false /* isServerStart */, buf, sizeof(buf));
+
+ passphrase = CFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8);
+ params.passphrase = passphrase;
+ memset(&buf, '\0', sizeof(buf));
+
+ status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out);
+
+ CFRelease(passphrase);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("private key file \"%s\" cannot be loaded because it requires a passphrase",
+ name)));
+ }
+
+ CFRelease(path);
+ CFRelease(data);
+
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not load private key \"%s\": %s",
+ name, pg_SSLerrmessage(status))));
+
+ return true;
+}
+
+/*
+ * load_keychain
+ * Open the specified, and default, Keychain
+ *
+ * Operations on keychains other than the default take the keychain references
+ * in an array. We need to copy a reference to the default keychain into the
+ * array to include it in the search.
+ *
+ * For server applications, we don't want modal dialog boxes opened for
+ * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE)
+ * will turn off all GUI interaction with the user, which may seem like what we
+ * want server side. This however has the side effect to turn off all GUI
+ * elements for all applications until some application calls
+ * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus
+ * remove wanted GUI interaction from another app, or another app might
+ * introduce it for us.
+ */
+static OSStatus
+load_keychain(char *name, CFArrayRef *keychains)
+{
+ OSStatus status;
+ struct stat stat_buf;
+ SecKeychainRef kc[2];
+ int array_len = 1;
+
+ /*
+ * If we are passing in a non-empty CFArrayRef, and fail to load the user
+ * Keychain then we would risk injecting Keychains since we will trust this
+ * array from hereon. Unconditionally error out hard immediately to avoid.
+ */
+ if (*keychains != NULL)
+ ereport(FATAL,
+ (errmsg("requesting to load Keychains into already allocated memory")));
+
+ if (stat(name, &stat_buf) != 0)
+ return errSecInvalidValue;
+
+ status = SecKeychainOpen(name, &kc[0]);
+ if (status == noErr)
+ {
+ SecKeychainUnlock(kc[0], 0, "", TRUE);
+
+ /*
+ * If we are allowed to use the default Keychain, add it to the array
+ * to include it in Keychain searches. If we are only using the default
+ * Keychain and no user defined Keychain we don't create an array at
+ * all since the recommended procedure is to pass NULL instead of an
+ * array containing only a reference to the default Keychain.
+ */
+ if (ssl_keychain_use_default)
+ SecKeychainCopyDefault(&kc[array_len++]);
+
+ *keychains = CFArrayCreate(NULL, (const void **) kc, array_len,
+ &kCFTypeArrayCallBacks);
+
+ if (!*keychains)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to allocate memory");
+ return errSecInternalError;
+ }
+ }
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain for a identity with a certificate matching
+ * the passed certificate reference. If the passed keychains array reference is
+ * NULL the default user Keychain will be searched. Keychains are searched by
+ * creating a dictionary of key/value pairs with the search criteria and then
+ * asking for a copy of the matching entry/entries to the search criteria.
+ */
+static OSStatus
+load_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFArrayRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label doesn't work, we need to request all and find the one we want.
+ * Copy all the results to a temp array and scan it for the certificate we
+ * are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ OSStatus search_stat;
+ SecIdentityRef dummy;
+ int i;
+
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i);
+ search_stat = SecIdentityCopyCertificate(dummy, &search_cert);
+
+ if (search_stat == noErr && search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ CFRelease(search_cert);
+ *identity = (SecIdentityRef) CFRetain(dummy);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+/*
+ * load_certificate_file
+ * Extracts a certificate from a PEM file on the filesystem
+ */
+static OSStatus
+load_certificate_file(char *name, CFArrayRef *cert_array)
+{
+ struct stat stat_buf;
+ int ret;
+ UInt8 *buf;
+ FILE *fd;
+ CFDataRef data;
+ SecExternalFormat format;
+ SecExternalItemType type;
+ CFStringRef path;
+ OSStatus status;
+
+ ret = stat(name, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to find file");
+ return errSecInternalError;
+ }
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((fd = AllocateFile(name, "r")) == NULL)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to open file for reading");
+ return errSecInternalError;
+ }
+
+ buf = palloc(stat_buf.st_size);
+ ret = fread(buf, 1, stat_buf.st_size, fd);
+ FreeFile(fd);
+
+ if (ret != stat_buf.st_size)
+ {
+ snprintf(internal_err, ERR_MSG_LEN, "unable to read file");
+ return errSecInternalError;
+ }
+
+ type = kSecItemTypeCertificate;
+ format = kSecFormatPEMSequence;
+ path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+ data = CFDataCreate(NULL, buf, stat_buf.st_size);
+
+ status = SecItemImport(data, path, &format, &type, 0, NULL, NULL,
+ cert_array);
+ pfree(buf);
+
+ return status;
+ }
+
+ snprintf(internal_err, ERR_MSG_LEN, "unable to open file for reading");
+ return errSecInternalError;
+}
+
+/*
+ * load_dh_file
+ * Slurp the contents of the specified file into buf
+ *
+ * Open the supplied filename and load its contents. This function is only
+ * reading the data without assessing its structure, actually parsing it is
+ * performed by load_dh_params(). The reason for splitting up the process is
+ * that we also support loading hardcoded DH params.
+ */
+static int
+load_dh_file(char *filename, char **buf)
+{
+ FILE *dh;
+ struct stat stat_buf;
+ int ret;
+
+ /*
+ * Open the DH file and slurp the contents. If the file doesn't exist it's
+ * not an error, if it can't be opened it is however an error.
+ */
+ ret = stat(filename, &stat_buf);
+ if (ret != 0 && errno == ENOENT)
+ return 0;
+ else if (ret == 0 && S_ISREG(stat_buf.st_mode))
+ {
+ if ((dh = AllocateFile(filename, "r")) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open DH parameters file \"%s\": %m",
+ filename)));
+
+ *buf = palloc(stat_buf.st_size);
+ ret = fread(*buf, 1, stat_buf.st_size, dh);
+ FreeFile(dh);
+
+ if (ret != stat_buf.st_size)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read DH parameters file \"%s\": %m",
+ filename)));
+ }
+ else
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("DH parameters file \"%s\" is not a regular file",
+ filename)));
+
+ return ret;
+}
+
+/*
+ * load_dh_params
+ * Load the specified DH params for the connection
+ *
+ * Secure Transport requires the DH params to be in DER format, but to be
+ * compatible with the OpenSSL code we also support PEM and convert to DER
+ * before loading. Conversion does rudimentary PEM parsing, if we miss the
+ * data being correct, the Secure Transport API will give an error anyways so
+ * we're just checking basic integrity.
+ *
+ * This function may scribble on the dh parameter so if that's required so stay
+ * intact in the caller, a copy should be sent.
+ */
+#define DH_HEADER "-----BEGIN DH PARAMETERS-----"
+#define DH_FOOTER "-----END DH PARAMETERS-----"
+static void
+load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl)
+{
+ OSStatus status;
+ char *der;
+ int der_len;
+
+ Assert(dh);
+
+ /* Convert PEM to DER */
+ if (is_pem)
+ {
+ char *head;
+ char *tail;
+ int pem_len = 0;
+
+ if (strstr(dh, DH_HEADER) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid PEM format for DH parameter, header missing")));
+
+ dh += strlen(DH_HEADER);
+ tail = strstr(dh, DH_FOOTER);
+ if (!tail)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid PEM format for DH parameter, footer missing")));
+ *tail = '\0';
+
+ /* In order to PEM convert it we need to remove all newlines */
+ head = dh;
+ tail = dh;
+ while (*head != '\0')
+ {
+ if (*head != '\n')
+ {
+ *tail++ = *head++;
+ pem_len++;
+ }
+ else
+ head++;
+ }
+ *tail = '\0';
+
+ der = palloc(pg_b64_dec_len(strlen(dh)) + 1);
+ der_len = pg_b64_decode(dh, strlen(dh), der);
+ der[der_len] = '\0';
+ }
+ else
+ {
+ der = dh;
+ der_len = len;
+ }
+
+ status = SSLSetDiffieHellmanParams(ssl, der, der_len);
+ if (status != noErr)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unable to load DH parameters: %s",
+ pg_SSLerrmessage(status))));
+}
+
+/*
+ * be_tls_close
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ OSStatus ssl_status;
+
+ if (!port->ssl)
+ return;
+
+ ssl_status = SSLClose((SSLContextRef) port->ssl);
+ if (ssl_status != noErr)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("error in closing SSL connection: %s",
+ pg_SSLerrmessage(ssl_status))));
+
+ CFRelease((SSLContextRef) port->ssl);
+
+ port->ssl = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * be_tls_get_version
+ * Retrieve the protocol version of the current connection
+ */
+const char *
+be_tls_get_version(Port *port)
+{
+ OSStatus status;
+ SSLProtocol protocol;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect TLS version for connection: %s",
+ pg_SSLerrmessage(status))));
+
+ if (protocol == kTLSProtocol11)
+ return "TLSv1.1";
+ else if (protocol == kTLSProtocol12)
+ return "TLSv1.2";
+#ifdef HAVE_KTLSPROTOCOL13
+ else if (protocol == kTLSProtocol13)
+ return "TLSv1.3";
+#endif
+
+ return "unknown";
+}
+
+/*
+ * be_tls_read
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ ssize_t ret;
+ OSStatus read_status;
+ SSLContextRef ssl = (SSLContextRef) port->ssl;
+
+ errno = 0;
+
+ if (len <= 0)
+ return 0;
+
+ read_status = SSLRead(ssl, ptr, len, &n);
+ switch (read_status)
+ {
+ case noErr:
+ ret = n;
+ break;
+
+ /* Function is blocked, waiting for I/O */
+ case errSSLWouldBlock:
+ if (port->ssl_buffered)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+
+ errno = EWOULDBLOCK;
+ if (n == 0)
+ ret = -1;
+ else
+ ret = n;
+
+ break;
+
+ case errSSLClosedGraceful:
+ ret = 0;
+ break;
+
+ /*
+ * If the connection was closed for an unforeseen reason, return error
+ * and set errno such that the caller can raise an appropriate ereport
+ */
+ case errSSLClosedNoNotify:
+ case errSSLClosedAbort:
+ ret = -1;
+ errno = ECONNRESET;
+ break;
+
+ default:
+ ret = -1;
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("SSL error: %s",
+ pg_SSLerrmessage(read_status))));
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * be_tls_write
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ size_t n = 0;
+ OSStatus write_status;
+
+ errno = 0;
+
+ if (len == 0)
+ return 0;
+
+ /*
+ * SSLWrite returns the number of bytes written in the 'n' argument. This
+ * however can be data either actually written to the socket, or buffered
+ * in the context. In the latter case SSLWrite will return errSSLWouldBlock
+ * and we need to call it with no new data (NULL) to drain the buffer on to
+ * the socket. We track the buffer in ssl_buffered and clear that when all
+ * data has been drained.
+ */
+ if (port->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ n = port->ssl_buffered;
+ port->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ n = -1;
+ errno = EINTR;
+ }
+ else
+ {
+ n = -1;
+ errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n);
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ /*
+ * The data was buffered in the context rather than written to the
+ * socket. Track this and repeatedly call SSLWrite to drain the
+ * buffer. See comment above.
+ */
+ case errSSLWouldBlock:
+ port->ssl_buffered = len;
+ n = 0;
+#ifdef EAGAIN
+ errno = EAGAIN;
+#else
+ errno = EINTR;
+#endif
+ break;
+
+ /* Clean disconnections */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+
+ default:
+ errno = ECONNRESET;
+ n = -1;
+ break;
+ }
+ }
+
+ return n;
+}
+
+/*
+ * be_tls_get_cipher_bits
+ * Returns the number of bits in the encryption for the current cipher
+ *
+ * Note: In case of errors, this returns 0 to match the OpenSSL implementation.
+ * A NULL encryption will however also return 0 making it complicated to
+ * differentiate between the two.
+ */
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ return 0;
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ return 0;
+
+ return pg_SSLcipherbits(cipher);
+}
+
+void
+be_tls_get_peerdn_name(Port *port, char *ptr, size_t len)
+{
+ OSStatus status;
+ SecTrustRef trust;
+ SecCertificateRef cert;
+ CFStringRef dn_str;
+
+ if (!ptr || len == 0)
+ return;
+
+ ptr[0] = '\0';
+
+ if (!(SSLContextRef) port->ssl)
+ return;
+
+ status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust);
+ if (status == noErr && trust != NULL)
+ {
+ /*
+ * TODO: copy the certificate parts with SecCertificateCopyValues and
+ * parse the OIDs to build up the DN
+ */
+ cert = SecTrustGetCertificateAtIndex(trust, 0);
+ dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL);
+ if (dn_str)
+ {
+ strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len);
+ CFRelease(dn_str);
+ }
+
+ CFRelease(trust);
+ }
+}
+
+/*
+ * be_tls_get_cipher
+ * Return the negotiated ciphersuite for the current connection.
+ */
+const char *
+be_tls_get_cipher(Port *port)
+{
+ OSStatus status;
+ SSLCipherSuite cipher;
+
+ if (!(SSLContextRef) port->ssl)
+ elog(ERROR, "No SSL connection");
+
+ status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher);
+ if (status != noErr)
+ ereport(ERROR,
+ (errmsg("could not detect cipher for connection: %s",
+ pg_SSLerrmessage(status))));
+
+ return pg_SSLciphername(cipher);
+}
+
+/*
+ * be_tls_get_compression
+ * Retrieve and return whether compression is used for the current
+ * connection.
+ *
+ * Since Secure Transport doesn't support compression at all, always return
+ * false here. Ideally we should be able to tell the caller that the option
+ * isn't applicable rather than return false, but the current SSL support
+ * doesn't allow for that.
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Translation */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified status code
+ *
+ * While only interesting to use for error cases, the function will return a
+ * translation for non-error statuses as well like noErr and errSecSuccess.
+ */
+static char *
+pg_SSLerrmessage(OSStatus status)
+{
+ CFStringRef err_msg;
+ char *err_buf;
+
+ if (status == errSecUnknownFormat)
+ return pstrdup(_("The item you are trying to import has an unknown format."));
+
+ /*
+ * The Secure Transport supplied error string for invalid passphrase only
+ * reads "invalid attribute" without any reference to a passphrase, which
+ * can be confusing. Override with our own.
+ */
+ if (status == errSecInvalidAttributePassphrase)
+ return pstrdup(_("Incorrect passphrase."));
+
+ if (status == errSSLRecordOverflow)
+ return pstrdup(_("SSL error"));
+
+ /*
+ * If the error is internal, and we have an error message in the internal
+ * buffer, then return that error and clear the internal buffer.
+ */
+ if (status == errSecInternalError && internal_err[0])
+ {
+ err_buf = pstrdup(internal_err);
+ memset(internal_err, '\0', ERR_MSG_LEN);
+ }
+ else
+ {
+ err_msg = SecCopyErrorMessageString(status, NULL);
+
+ if (err_msg)
+ {
+ err_buf = pstrdup(CFStringGetCStringPtr(err_msg,
+ kCFStringEncodingUTF8));
+ CFRelease(err_msg);
+ }
+ else
+ err_buf = pstrdup(_("unknown SSL error"));
+ }
+
+ return err_buf;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Socket IO */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_SSLSocketRead
+ *
+ * Callback for reading data from the connection. When entering the function,
+ * len is set to the number of bytes requested. Upon leaving, len should be
+ * overwritten with the actual number of bytes read.
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_read((Port *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ port->waitfor = WL_SOCKET_READABLE;
+ status = errSSLWouldBlock;
+ break;
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status;
+ int res;
+ Port *port = (Port *) conn;
+
+ res = secure_raw_write(port, data, *len);
+
+ if (res < 0)
+ {
+ switch (errno)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ port->waitfor = WL_SOCKET_WRITEABLE;
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ status = errSSLClosedAbort;
+ break;
+ }
+
+ *len = res;
+ }
+ else
+ {
+ status = noErr;
+ *len = res;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions - Misc */
+/* ------------------------------------------------------------ */
+
+/*
+ * KeychainEnsureValid
+ * Ensure the validity of using the passed Keychain array
+ *
+ * Any consumers of the Keychain array should always call this to ensure that
+ * it is set up in a manner that reflects the configuration. If it not, then
+ * this function will fatally error out as all codepaths should assume that
+ * setup has been done correctly and that there is no recovery in case it
+ * hasn't.
+ */
+static void
+KeychainEnsureValid(CFArrayRef keychains)
+{
+ int keychain_count;
+
+ /*
+ * If the keychain array is unallocated, we must be allowed to use the
+ * default user Keychain, as that will be the effect of passing NULL to the
+ * Keychain search API. If not, error out.
+ */
+ if (!keychains && ssl_keychain_use_default)
+ return;
+
+ if (keychains)
+ keychain_count = CFArrayGetCount(keychains);
+ else
+ keychain_count = 0;
+
+ /*
+ * If we have one Keychain loaded then we must have a Keychain file
+ * configured, and not be allowed to use the default Keychain. If we have
+ * two then we must have a Keychain file configured *and* be allowed to use
+ * the default user Keychain. If we have any other number of Keychains in
+ * the array then we definitely have an incorrect situation.
+ */
+ if (keychain_count == 1)
+ {
+ if (ssl_keychain_file[0] != '\0' && !ssl_keychain_use_default)
+ return;
+ }
+ else if (keychain_count == 2)
+ {
+ if (ssl_keychain_file[0] != '\0' && ssl_keychain_use_default)
+ return;
+ }
+
+ ereport(FATAL,
+ (errmsg("incorrect loading of Keychains detected")));
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 4eb21fe89d..04e7bfaab1 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -46,6 +46,10 @@ char *ssl_crl_file;
char *ssl_dh_params_file;
char *ssl_passphrase_command;
bool ssl_passphrase_command_supports_reload;
+#ifdef USE_SECURETRANSPORT
+char *ssl_keychain_file;
+bool ssl_keychain_use_default = false;
+#endif
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 2317e8be6b..22593fe321 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -512,6 +512,7 @@ static char *locale_ctype;
static char *server_encoding_string;
static char *server_version_string;
static int server_version_num;
+static char *ssl_library_string;
static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
@@ -1830,6 +1831,17 @@ static struct config_bool ConfigureNamesBool[] =
true,
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_use_default", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Allow using the default Keychain of the current user."),
+ NULL
+ },
+ &ssl_keychain_use_default,
+ false,
+ NULL, NULL, NULL
+ },
+#endif
/* End-of-list marker */
{
@@ -3517,6 +3529,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ /* Can't be set in postgresql.conf */
+ {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS,
+ gettext_noop("Shows the SSL library used."),
+ NULL,
+ GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &ssl_library_string,
+ SSL_LIBRARY,
+ NULL, NULL, NULL
+ },
+
{
/* Not for general use --- used by SET ROLE */
{"role", PGC_USERSET, UNGROUPED,
@@ -3768,6 +3792,18 @@ static struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+#ifdef USE_SECURETRANSPORT
+ {
+ {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the Keychain file."),
+ NULL
+ },
+ &ssl_keychain_file,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h
new file mode 100644
index 0000000000..f66555b382
--- /dev/null
+++ b/src/include/common/securetransport.h
@@ -0,0 +1,514 @@
+/*-------------------------------------------------------------------------
+ *
+ * securetransport.h
+ * Apple Secure Transport support
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2017, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/securetransport.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SECURETRANSPORT_H
+#define SECURETRANSPORT_H
+
+#include <Security/SecureTransport.h>
+
+/*
+ * pg_SSLciphername
+ *
+ * Translate a SSLCipherSuite code into a string literal suitable for printing
+ * in log/informational messages to the user. Since this implementation of the
+ * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted.
+ *
+ * The SSLCipherSuite enum is defined in Security/CipherSuite.h
+ *
+ * This only removes the TLS_ portion of the SSLCipherSuite enum label for the
+ * ciphers to match what most Secure Transport implementations seem to be doing
+ */
+static const char *
+pg_SSLciphername(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ case TLS_NULL_WITH_NULL_NULL:
+ return "NULL";
+
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ return "RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ return "DH_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ return "DH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return "DH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ return "RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ return "DH_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ return "DH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return "DH_anon_WITH_AES_256_CBC_SHA";
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ return "ECDH_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return "ECDH_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ return "ECDHE_ECDSA_WITH_NULL_SHA";
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ return "ECDHE_ECDSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_NULL_SHA:
+ return "ECDH_RSA_WITH_NULL_SHA";
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return "ECDH_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_NULL_SHA:
+ return "ECDHE_RSA_WITH_NULL_SHA";
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ return "ECDHE_RSA_WITH_RC4_128_SHA";
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA";
+ case TLS_ECDH_anon_WITH_NULL_SHA:
+ return "ECDH_anon_WITH_NULL_SHA";
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ return "ECDH_anon_WITH_RC4_128_SHA";
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "ECDH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ return "ECDH_anon_WITH_AES_128_CBC_SHA";
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return "ECDH_anon_WITH_AES_256_CBC_SHA";
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_NULL_MD5:
+ return "RSA_WITH_NULL_MD5";
+ case TLS_RSA_WITH_NULL_SHA:
+ return "RSA_WITH_NULL_SHA";
+ case TLS_RSA_WITH_RC4_128_MD5:
+ return "RSA_WITH_RC4_128_MD5";
+ case TLS_RSA_WITH_RC4_128_SHA:
+ return "RSA_WITH_RC4_128_SHA";
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_WITH_NULL_SHA256:
+ return "RSA_WITH_NULL_SHA256";
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return "RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return "RSA_WITH_AES_256_CBC_SHA256";
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DH_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DH_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_DSS_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_RSA_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ return "DH_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ return "DH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ return "DH_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ return "DH_RSA_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ return "DHE_DSS_WITH_AES_256_CBC_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return "DHE_RSA_WITH_AES_256_CBC_SHA256";
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ return "DH_anon_WITH_RC4_128_MD5";
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return "DH_anon_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return "DH_anon_WITH_AES_128_CBC_SHA256";
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return "DH_anon_WITH_AES_256_CBC_SHA256";
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_RC4_128_SHA:
+ return "PSK_WITH_RC4_128_SHA";
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ return "PSK_WITH_AES_128_CBC_SHA";
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ return "PSK_WITH_AES_256_CBC_SHA";
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ return "DHE_PSK_WITH_RC4_128_SHA";
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "DHE_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA";
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return "RSA_PSK_WITH_RC4_128_SHA";
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return "RSA_PSK_WITH_3DES_EDE_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA";
+
+ /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ case TLS_PSK_WITH_NULL_SHA:
+ return "PSK_WITH_NULL_SHA";
+ case TLS_DHE_PSK_WITH_NULL_SHA:
+ return "DHE_PSK_WITH_NULL_SHA";
+ case TLS_RSA_PSK_WITH_NULL_SHA:
+ return "RSA_PSK_WITH_NULL_SHA";
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return "RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return "RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "DHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "DHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ return "DH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ return "DH_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ return "DHE_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ return "DHE_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ return "DH_DSS_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ return "DH_DSS_WITH_AES_256_GCM_SHA384";
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return "DH_anon_WITH_AES_128_GCM_SHA256";
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return "DH_anon_WITH_AES_256_GCM_SHA384";
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ return "PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ return "PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ return "DHE_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ return "DHE_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return "RSA_PSK_WITH_AES_128_GCM_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return "RSA_PSK_WITH_AES_256_GCM_SHA384";
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ return "PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ return "PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_PSK_WITH_NULL_SHA256:
+ return "PSK_WITH_NULL_SHA256";
+ case TLS_PSK_WITH_NULL_SHA384:
+ return "PSK_WITH_NULL_SHA384";
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ return "DHE_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ return "DHE_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_DHE_PSK_WITH_NULL_SHA256:
+ return "DHE_PSK_WITH_NULL_SHA256";
+ case TLS_DHE_PSK_WITH_NULL_SHA384:
+ return "DHE_PSK_WITH_NULL_SHA384";
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return "RSA_PSK_WITH_AES_128_CBC_SHA256";
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return "RSA_PSK_WITH_AES_256_CBC_SHA384";
+ case TLS_RSA_PSK_WITH_NULL_SHA256:
+ return "RSA_PSK_WITH_NULL_SHA256";
+ case TLS_RSA_PSK_WITH_NULL_SHA384:
+ return "RSA_PSK_WITH_NULL_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_CBC_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return "ECDH_RSA_WITH_AES_128_CBC_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return "ECDH_RSA_WITH_AES_256_CBC_SHA384";
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDHE_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDHE_RSA_WITH_AES_256_GCM_SHA384";
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return "ECDH_RSA_WITH_AES_128_GCM_SHA256";
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return "ECDH_RSA_WITH_AES_256_GCM_SHA384";
+
+ default:
+ break;
+ }
+
+ return "unknown";
+}
+
+#ifndef FRONTEND
+/*
+ * pg_SSLcipherbits
+ *
+ * Return the number of bits in the encryption for the specified cipher.
+ * Ciphers with NULL encryption are omitted from the switch statement. This
+ * function is currently only used in the libpq backend.
+ */
+static int
+pg_SSLcipherbits(SSLCipherSuite cipher)
+{
+ switch (cipher)
+ {
+ /* TLS addenda using AES, per RFC 3268 */
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* ECDSA addenda, RFC 4492 */
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+
+ case TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+ case TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return 128;
+
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /* Server provided RSA certificate for key exchange. */
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_RSA_WITH_RC4_128_MD5:
+ case TLS_RSA_WITH_RC4_128_SHA:
+ case TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /*
+ * Server-authenticated (and optionally client-authenticated)
+ * Diffie-Hellman.
+ */
+ case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Completely anonymous Diffie-Hellman */
+ case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_DH_anon_WITH_RC4_128_MD5:
+ case TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+ return 256;
+
+ /* Addendum from RFC 4279, TLS PSK */
+ case TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ return 112;
+ case TLS_PSK_WITH_RC4_128_SHA:
+ case TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case TLS_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return 128;
+ case TLS_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return 256;
+
+ /*
+ * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites
+ * for TLS.
+ */
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+ case TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+ return 128;
+
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+ case TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+
+
+ case TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ case TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ case TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * HMAC SHA-256/384.
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ return 256;
+
+ /*
+ * Addenda from RFC 5289, Elliptic Curve Cipher Suites with
+ * SHA-256/384 and AES Galois Counter Mode (GCM)
+ */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ return 128;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+ return 256;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+#endif
+
+#endif /* SECURETRANSPORT_H */
diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h
index f3fd0d0d28..b446d491ec 100644
--- a/src/include/common/sha2.h
+++ b/src/include/common/sha2.h
@@ -50,7 +50,7 @@
#ifndef _PG_SHA2_H_
#define _PG_SHA2_H_
-#ifdef USE_SSL
+#if defined(USE_OPENSSL)
#include <openssl/sha.h>
#endif
@@ -69,7 +69,7 @@
#define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1)
/* Context Structures for SHA-1/224/256/384/512 */
-#ifdef USE_SSL
+#if defined(USE_OPENSSL)
typedef SHA256_CTX pg_sha256_ctx;
typedef SHA512_CTX pg_sha512_ctx;
typedef SHA256_CTX pg_sha224_ctx;
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index b2c59df54e..8f3de29655 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -22,6 +22,14 @@
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
+#elif USE_SECURETRANSPORT
+/*
+ * Ideally we should include the Secure Transport headers here but doing so
+ * cause namespace collisions with CoreFoundation on, among others "Size"
+ * and ACL definitions. To avoid polluting with workarounds, use void * for
+ * instead of the actual Secure Transport variables and perform type casting
+ * in the Secure Transport implementation.
+ */
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
@@ -183,12 +191,28 @@ 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 library structures. (Keep these last so that the locations of
+ * other fields are the same whether or not you build with SSL.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
+#elif USE_SECURETRANSPORT
+
+ /*
+ * ssl is an SSLContextRef, but including the appropriate header file from
+ * Secure Transport causes a clash on the Size type so we need to cast it
+ * from a void pointer when used.
+ */
+ void *ssl;
+
+ /*
+ * When writing data to the socket, Secure Transport can buffer some of
+ * the data instead of writing it to the socket. ssl_buffered tracks the
+ * number of buffered bytes to ensure that we drain the buffer.
+ */
+ int ssl_buffered;
+ int waitfor;
#endif
} Port;
@@ -214,7 +238,7 @@ CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
/*
* These functions are implemented by the glue code specific to each
- * SSL implementation (e.g. be-secure-openssl.c)
+ * SSL implementation (e.g. be-secure-<implementation>.c)
*/
/*
@@ -269,6 +293,7 @@ extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
*
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
+ * It's also not supported with Apple Secure Transport.
*/
#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
#define HAVE_BE_TLS_GET_CERTIFICATE_HASH
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index c7762f68a6..369eb53c9b 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -83,6 +83,10 @@ extern char *ssl_crl_file;
extern char *ssl_dh_params_file;
extern char *ssl_passphrase_command;
extern bool ssl_passphrase_command_supports_reload;
+#ifdef USE_SECURETRANSPORT
+extern char *ssl_keychain_file;
+extern bool ssl_keychain_use_default;
+#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 9798bd24b4..e797b7916c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -915,6 +915,14 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with Apple Secure Transport support.
+ (--with-securetransport) */
+#undef USE_SECURETRANSPORT
+
+/* Define to allow using TLSv1.3 in Apple Secure Transport (introduced in
+ macOS 10.13 High Sierra) */
+#undef HAVE_KTLSPROTOCOL13
+
/* 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.h.win32 b/src/include/pg_config.h.win32
index f7a051d112..5b6fb94732 100644
--- a/src/include/pg_config.h.win32
+++ b/src/include/pg_config.h.win32
@@ -701,6 +701,13 @@
/* Define to use OpenSSL for random number generation */
/* #undef USE_OPENSSL_RANDOM */
+/* Define to build with Apple Secure Transport support. (--with-securetransport) */
+/* #undef USE_SECURETRANSPORT */
+
+/* Define to allow using TLSv1.3 in Apple Secure Transport (introduced in
+ macOS 10.13 High Sierra) */
+/* #undef HAVE_KTLSPROTOCOL13 */
+
/* Define to 1 to build with PAM support. (--with-pam) */
/* #undef USE_PAM */
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index cc5eedfc41..b7e437984e 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -164,11 +164,17 @@
/*
* 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_SECURETRANSPORT)
#define USE_SSL
+#if defined(USE_OPENSSL)
+#define SSL_LIBRARY "OpenSSL"
+#elif defined(USE_SECURETRANSPORT)
+#define SSL_LIBRARY "Secure Transport"
+#endif
+#else
+#define SSL_LIBRARY "None"
#endif
/*
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c2171d0856..8f425edbf8 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -38,6 +38,11 @@ ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o fe-secure-common.o
endif
+ifeq ($(with_securetransport), yes)
+OBJS += fe-secure-securetransport.o
+override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings
+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 d001bc513d..cfe46b9f62 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -325,6 +325,16 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+#if defined(USE_SECURETRANSPORT)
+ {"keychain_use_default", NULL, NULL, NULL,
+ "UseDefaultKeychain", "", 1,
+ offsetof(struct pg_conn, keychain_use_default)},
+
+ {"keychain", "PGKEYCHAIN", NULL, NULL,
+ "Keychain", "", 64,
+ offsetof(struct pg_conn, keychain)},
+#endif
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -3536,6 +3546,9 @@ makeEmptyPGconn(void)
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
+#ifdef USE_SECURETRANSPORT
+ conn->keychain_use_default = true;
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@@ -3672,6 +3685,10 @@ freePGconn(PGconn *conn)
#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
if (conn->gsslib)
free(conn->gsslib);
+#endif
+#ifdef USE_SECURETRANSPORT
+ if (conn->keychain)
+ free(conn->keychain);
#endif
/* Note that conn->Pfdebug is not ours to close or free */
if (conn->last_query)
diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c
new file mode 100644
index 0000000000..4aa3dfa5c5
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-securetransport.c
@@ -0,0 +1,1496 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-securetransport.c
+ * Apple Secure Transport support
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-securetransport.c
+ *
+ * NOTES
+ * Unlike the OpenSSL support there is no shared state between connections
+ * so there is no special handling for ENABLE_THREAD_SAFETY.
+ *
+ * There are a lot of functions (mostly the Core Foundation CF* family) that
+ * pass NULL as the first parameter. This is because they allow for a custom
+ * allocator to be used for memory allocations which is referenced with the
+ * first parameter. We are using the standard allocator however, and that
+ * means passing NULL all the time. Defining a suitably named preprocessor
+ * macro would aid readablitity in case this is confusing (and/or ugly).
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <signal.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+#include <sys/socket.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif
+#include <arpa/inet.h>
+
+#include <sys/stat.h>
+
+#define Size pg_Size
+#define uint64 pg_uint64
+#include <Security/Security.h>
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "common/securetransport.h"
+#undef uint64
+#undef Size
+#define pg_uint64 uint64
+#define pg_Size Size
+
+#define KC_PREFIX "keychain:"
+#define KC_PREFIX_LEN (strlen("keychain:"))
+
+/*
+ * Private API call used in the Webkit code for creating an identity from a
+ * certificate with a key. While stable and used in many open source projects
+ * it should be replaced with a published API call since private APIs aren't
+ * subject to the same deprecation rules. Could potentially be replaced by
+ * using SecIdentityCreateWithCertificate() ?
+ */
+extern SecIdentityRef SecIdentityCreate(CFAllocatorRef allocator,
+ SecCertificateRef certificate,
+ SecKeyRef privateKey);
+
+static char * pg_SSLerrmessage(OSStatus errcode);
+static void pg_SSLerrfree(char *err_buf);
+static int pg_SSLsessionstate(PGconn *conn, char *msg, size_t len);
+
+static OSStatus pg_SSLSocketRead(SSLConnectionRef conn, void *data,
+ size_t *len);
+static OSStatus pg_SSLSocketWrite(SSLConnectionRef conn, const void *data,
+ size_t *len);
+static OSStatus pg_SSLOpenClient(PGconn *conn);
+static OSStatus pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array,
+ CFArrayRef *rootcert_array);
+
+static OSStatus import_certificate_keychain(const char *common_name,
+ SecCertificateRef *certificate,
+ CFArrayRef keychains,
+ char *hostname);
+static OSStatus import_identity_keychain(const char *common_name,
+ SecIdentityRef *identity,
+ CFArrayRef keychains);
+static OSStatus import_pem(const char *path, char *passphrase,
+ CFArrayRef *cert_arr);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+
+/*
+ * pgtls_init_library
+ *
+ * Exported function to allow application to tell us it's already initialized
+ * the SSL library and/or libcrypto. Since the current implementation only
+ * allow do_crypto be set for the OpenSSL backend, this is a no-op for Secure
+ * Transport.
+ */
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ return;
+}
+
+/*
+ * pgtls_open_client
+ * Begin, or continue, negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ OSStatus open_status;
+ CFArrayRef certificate = NULL;
+ CFArrayRef key = NULL;
+ CFArrayRef rootcert = NULL;
+
+ /*
+ * There is no API to load CRL lists in Secure Transport, they can however
+ * be imported into a Keychain with the commandline application "certtool".
+ * For libpq to use them, the certificate/key and root certificate needs to
+ * be using an identity in a Keychain into which the CRL have been
+ * imported. That needs to be documented.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CRL files are not supported with Secure Transport\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If the SSL context hasn't been set up then initiate it, else continue
+ * with handshake
+ */
+ if (conn->ssl == NULL)
+ {
+ conn->ssl_key_bits = 0;
+ conn->ssl_buffered = 0;
+ conn->st_rootcert = NULL;
+
+ conn->ssl = (void *) SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if (!conn->ssl)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create SSL context\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetProtocolVersionMin((SSLContextRef) conn->ssl, kTLSProtocol12);
+ if (open_status != noErr)
+ goto error;
+
+ open_status = SSLSetConnection((SSLContextRef) conn->ssl, conn);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Set the low level functions for reading and writing off a socket
+ */
+ open_status = SSLSetIOFuncs((SSLContextRef) conn->ssl, pg_SSLSocketRead, pg_SSLSocketWrite);
+ if (open_status != noErr)
+ goto error;
+
+ /*
+ * Load client certificate, private key, and trusted CA certs. The
+ * conn->errorMessage will be populated by the certificate loading
+ * so we can return without altering it in case of error.
+ */
+ if (pg_SSLLoadCertificate(conn, &certificate, &key, &rootcert) != noErr)
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we are asked to verify the peer hostname, set it as a requirement
+ * on the connection. This must be set before calling SSLHandshake().
+ */
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ /* If we are asked to verify a hostname we don't have, error out */
+ if (!conn->pghost)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("hostname missing for verify-full\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ open_status = SSLSetPeerDomainName((SSLContextRef) conn->ssl, conn->pghost, strlen(conn->pghost));
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set peer hostname: %s\n"),
+ err_msg);
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ }
+
+ /*
+ * Perform handshake
+ */
+ open_status = pg_SSLOpenClient(conn);
+ if (open_status == noErr)
+ {
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+ }
+
+error:
+ if (open_status != noErr)
+ {
+ char *err_msg = pg_SSLerrmessage(open_status);
+ if (conn->errorMessage.len > 0)
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext(", ssl error: %s\n"), err_msg);
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not establish SSL connection: %s\n"),
+ err_msg);
+ pg_SSLerrfree(err_msg);
+
+ pgtls_close(conn);
+ }
+
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * pg_SSLOpenClient
+ * Validates remote certificate and performs handshake.
+ *
+ * If the user has supplied a root certificate we add that to the chain here
+ * before initiating validation. The caller is responsible for invoking error
+ * logging in the case of errors returned.
+ */
+static OSStatus
+pg_SSLOpenClient(PGconn *conn)
+{
+ OSStatus status;
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_eval = 0;
+ bool trusted = false;
+ bool only_anchor = true;
+
+ SSLSetSessionOption((SSLContextRef) conn->ssl,
+ kSSLSessionOptionBreakOnServerAuth, true);
+
+ /*
+ * Call SSLHandshake until we get another response than errSSLWouldBlock.
+ * Busy-waiting is pretty silly, but what is commonly used for handshakes
+ * in Secure Transport. Setting an upper bound on retries should be done
+ * though, and perhaps a small timeout to play nice.
+ */
+ do
+ {
+ status = SSLHandshake((SSLContextRef) conn->ssl);
+ /* busy-wait loop */
+ }
+ while (status == errSSLWouldBlock || status == -1);
+
+ if (status != errSSLServerAuthCompleted)
+ return status;
+
+ /*
+ * Get peer server certificate and validate it. SSLCopyPeerTrust() is not
+ * supposed to return a NULL trust on noErr but have been reported to do
+ * in the past so add a belts-and-suspenders check
+ */
+ status = SSLCopyPeerTrust((SSLContextRef) conn->ssl, &trust);
+ if (status != noErr || trust == NULL)
+ return (trust == noErr ? errSecInternalError : status);
+
+ /*
+ * If we have our own root certificate configured then add it to the chain
+ * of trust and specify that it should be trusted.
+ */
+ if (conn->st_rootcert)
+ {
+ status = SecTrustSetAnchorCertificates(trust,
+ (CFArrayRef) conn->st_rootcert);
+ if (status != noErr)
+ return status;
+
+ /* We have a trusted local root cert, trust more than anchor */
+ only_anchor = false;
+ }
+
+ status = SecTrustSetAnchorCertificatesOnly(trust, only_anchor);
+ if (status != noErr)
+ return status;
+
+ status = SecTrustEvaluate(trust, &trust_eval);
+ if (status == errSecSuccess)
+ {
+ switch (trust_eval)
+ {
+ /*
+ * If 'Unspecified' then a valid anchor certificate was verified
+ * without encountering any explicit user trust. If 'Proceed' then
+ * the user has chosen to explicitly trust a certificate in the
+ * chain by clicking "Trust" in the Keychain app. Both cases are
+ * considered valid so trust the chain.
+ */
+ case kSecTrustResultUnspecified:
+ trusted = true;
+ break;
+ case kSecTrustResultProceed:
+ trusted = true;
+ break;
+
+ /*
+ * 'RecoverableTrustFailure' indicates that the certificate was
+ * rejected but might be trusted with minor changes to the eval
+ * context (ignoring expired certificate etc). For the verify
+ * sslmodes there is little to do here, but in require sslmode we
+ * can recover in some cases.
+ */
+ case kSecTrustResultRecoverableTrustFailure:
+ {
+ CFArrayRef trust_prop;
+ CFDictionaryRef trust_dict;
+ CFStringRef trust_error;
+ const char *error;
+
+ /* Assume the error is in fact not recoverable */
+ trusted = false;
+
+ /*
+ * In sslmode "require" we accept some certificate verification
+ * failures when we don't have a rootcert since MITM protection
+ * isn't enforced. Check the reported failure and trust in case
+ * the cert is missing, self signed or expired/future.
+ */
+ if ((strcmp(conn->sslmode, "require") == 0 ||
+ strcmp(conn->sslmode, "allow") == 0) && !conn->st_rootcert)
+ {
+ trust_prop = SecTrustCopyProperties(trust);
+ trust_dict = CFArrayGetValueAtIndex(trust_prop, 0);
+ trust_error = CFDictionaryGetValue(trust_dict,
+ kSecPropertyTypeError);
+ if (trust_error)
+ {
+ error = CFStringGetCStringPtr(trust_error,
+ kCFStringEncodingUTF8);
+
+ /* Self signed, or missing CA */
+ if (strcmp(error, "CSSMERR_TP_INVALID_ANCHOR_CERT") == 0 ||
+ strcmp(error, "CSSMERR_TP_NOT_TRUSTED") == 0 ||
+ strcmp(error, "CSSMERR_TP_INVALID_CERT_AUTHORITY") == 0)
+ trusted = true;
+ /* Expired or future dated */
+ else if (strcmp(error, "CSSMERR_TP_CERT_EXPIRED") == 0 ||
+ strcmp(error, "CSSMERR_TP_CERT_NOT_VALID_YET") == 0)
+ trusted = true;
+ }
+
+ CFRelease(trust_prop);
+ }
+
+ break;
+ }
+
+ /*
+ * The below results are all cases where the certificate should be
+ * rejected without further questioning.
+ */
+
+ /*
+ * 'Deny' means that the user has explicitly set the certificate to
+ * untrusted.
+ */
+ case kSecTrustResultDeny:
+ /* fall-through */
+ case kSecTrustResultInvalid:
+ /* fall-through */
+ case kSecTrustResultFatalTrustFailure:
+ /* fall-through */
+ case kSecTrustResultOtherError:
+ /* fall-through */
+ default:
+ trusted = false;
+ break;
+ }
+ }
+
+ CFRelease(trust);
+
+ if (!trusted)
+ return errSecNoAccessForItem;
+
+ /*
+ * If we reach here the documentation states we need to run the Handshake
+ * again after validating the trust
+ */
+ return pg_SSLOpenClient(conn);
+}
+
+/*
+ * pgtls_read_pending
+ * Is there unread data waiting in the SSL read buffer?
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ OSStatus read_status;
+ size_t len = 0;
+
+ read_status = SSLGetBufferedReadSize((SSLContextRef) conn->ssl, &len);
+
+ /*
+ * Should we get an error back then we assume that subsequent read
+ * operations will fail as well.
+ */
+ return (read_status == noErr && len > 0);
+}
+
+/*
+ * pgtls_read
+ * Read data from a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ OSStatus read_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int read_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state for
+ * reading before attempting to pull any data off the wire.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ read_status = SSLRead((SSLContextRef) conn->ssl, ptr, len, &n);
+ ret = (ssize_t) n;
+
+ switch (read_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ /* Only set read_errno to EINTR iff we didn't get any data back */
+ if (n == 0)
+ read_errno = EINTR;
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), read_status);
+ read_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return ret;
+}
+
+/*
+ * pgtls_write
+ * Write data to a secure connection.
+ *
+ * On failure, this function is responsible for putting a suitable message into
+ * conn->errorMessage. The caller must still inspect errno, but only to decide
+ * whether to continue or retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ OSStatus write_status;
+ size_t n = 0;
+ ssize_t ret = 0;
+ int write_errno = 0;
+ char sess_msg[25];
+
+ /*
+ * Double-check that we have a connection which is in the correct state
+ * for writing before attempting to push any data on to the wire or the
+ * local SSL buffer.
+ */
+ if (pg_SSLsessionstate(conn, sess_msg, sizeof(sess_msg)) == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection is: %s\n"), sess_msg);
+ write_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (conn->ssl_buffered > 0)
+ {
+ write_status = SSLWrite((SSLContextRef) conn->ssl, NULL, 0, &n);
+
+ if (write_status == noErr)
+ {
+ ret = conn->ssl_buffered;
+ conn->ssl_buffered = 0;
+ }
+ else if (write_status == errSSLWouldBlock || write_status == -1)
+ {
+ ret = 0;
+ write_errno = EINTR;
+ }
+ else
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error: %d\n"), write_status);
+ ret = -1;
+ write_errno = ECONNRESET;
+ }
+ }
+ else
+ {
+ write_status = SSLWrite((SSLContextRef) conn->ssl, ptr, len, &n);
+ ret = n;
+
+ switch (write_status)
+ {
+ case noErr:
+ break;
+
+ case errSSLWouldBlock:
+ conn->ssl_buffered = len;
+ ret = 0;
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ /*
+ * Clean disconnections
+ */
+ case errSSLClosedNoNotify:
+ /* fall through */
+ case errSSLClosedGraceful:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL connection has been closed unexpectedly\n"));
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unrecognized SSL error %d\n"), write_status);
+ write_errno = ECONNRESET;
+ ret = -1;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return ret;
+}
+
+/*
+ * pgtls_init
+ * Initialize SSL system.
+ *
+ * There is little state or context to initialize for Secure Transport, the
+ * heavy lifting is performed by pgtls_open_client.
+ */
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_buffered = 0;
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+/*
+ * pgtls_close
+ * Close SSL connection.
+ *
+ * This function must cope with connections in all states of disrepair since
+ * it will be called from pgtls_open_client to clean up any potentially used
+ * resources in case it breaks halfway.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ if (!conn->ssl)
+ return;
+
+ if (conn->st_rootcert != NULL)
+ CFRelease((CFArrayRef) conn->st_rootcert);
+
+ SSLClose((SSLContextRef) conn->ssl);
+ CFRelease(conn->ssl);
+
+ conn->ssl = NULL;
+ conn->ssl_in_use = false;
+}
+
+/*
+ * pg_SSLSocketRead
+ * The amount of read bytes is returned in the len variable
+ */
+static OSStatus
+pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_read((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+ case ENOENT:
+ status = errSSLClosedGraceful;
+ break;
+
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+ }
+
+ *len = 0;
+ }
+ else
+ *len = res;
+
+ return status;
+}
+
+static OSStatus
+pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len)
+{
+ OSStatus status = noErr;
+ int res;
+
+ res = pqsecure_raw_write((PGconn *) conn, data, *len);
+
+ if (res < 0)
+ {
+ switch (SOCK_ERRNO)
+ {
+#ifdef EAGAIN
+ case EAGAIN:
+#endif
+#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
+ case EWOULDBLOCK:
+#endif
+ case EINTR:
+ status = errSSLWouldBlock;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ *len = res;
+
+ return status;
+}
+
+/*
+ * import_identity_keychain
+ * Import the identity for the specified certificate from a Keychain
+ *
+ * Queries the specified Keychain for a identity with a certificate matching
+ * the passed certificate reference. If the passed keychains array reference is
+ * NULL, the default user Keychain will be searched. Keychains are searched by
+ * creating a dictionary of key/value pairs with the search criteria and then
+ * asking for a copy of the matching entry/entries to the search criteria.
+ */
+static OSStatus
+import_identity_keychain(const char *common_name, SecIdentityRef *identity,
+ CFArrayRef keychains)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ SecIdentityRef temp;
+
+ /*
+ * Make sure the user didn't just specify keychain: as the sslcert config.
+ * The passed certificate will have the keychain prefix stripped so in that
+ * case the string is expected to be empty here.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+
+ /*
+ * If we didn't get a Keychain passed, skip adding it to the dictionary
+ * thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * We don't need to set a kSecMatchLimit key since the default is to only
+ * return a single reference. Older versions of macOS had issues with
+ * certificate matching on labels, but we don't support older versions so
+ * no need to extract all and match ourselves.
+ */
+ CFDictionaryAddValue(query, kSecClass, kSecClassIdentity);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL));
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+
+ if (status == noErr)
+ *identity = (SecIdentityRef) CFRetain(temp);
+
+ CFRelease(query);
+ CFRelease(cert);
+
+ return status;
+}
+
+static OSStatus
+import_certificate_keychain(const char *common_name, SecCertificateRef *certificate,
+ CFArrayRef keychains, char *hostname)
+{
+ OSStatus status = errSecItemNotFound;
+ CFMutableDictionaryRef query;
+ CFStringRef cert;
+ CFStringRef host = NULL;
+ CFArrayRef temp;
+ SecPolicyRef ssl_policy;
+ int i;
+
+ /*
+ * Make sure the user didn't just specify the keychain prefix as the
+ * certificate config. The passed certificate will have the keychain
+ * prefix stripped so in that case the string is expected to be empty.
+ */
+ if (strlen(common_name) == 0)
+ return errSecInvalidValue;
+
+ query = CFDictionaryCreateMutable(NULL, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8);
+ CFDictionaryAddValue(query, kSecAttrLabel, cert);
+
+ CFDictionaryAddValue(query, kSecClass, kSecClassCertificate);
+ CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue);
+ CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
+
+ /*
+ * If we didn't get a set of Keychains passed, skip adding it to the
+ * dictionary thus prompting a search in the users default Keychain.
+ */
+ if (keychains)
+ CFDictionaryAddValue(query, kSecMatchSearchList, keychains);
+
+ /*
+ * Specifying a hostname requires it to match the hostname in the leaf
+ * certificate.
+ */
+ if (hostname)
+ host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8);
+ ssl_policy = SecPolicyCreateSSL(true, host);
+ CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy);
+
+ /*
+ * Normally we could have used kSecMatchLimitOne in the above dictionary
+ * but since there are versions of macOS where the certificate matching on
+ * the label has been reported to not work (revisions of 10.12), we request
+ * all and find the one we want. Copy all the results to a temp array and
+ * scan it for the certificate we are interested in.
+ */
+ status = SecItemCopyMatching(query, (CFTypeRef *) &temp);
+ if (status == noErr)
+ {
+ for (i = 0; i < CFArrayGetCount(temp); i++)
+ {
+ SecCertificateRef search_cert;
+ CFStringRef cn;
+
+ search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i);
+
+ if (search_cert != NULL)
+ {
+ SecCertificateCopyCommonName(search_cert, &cn);
+ if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo)
+ {
+ CFRelease(cn);
+ *certificate = (SecCertificateRef) CFRetain(search_cert);
+ break;
+ }
+
+ CFRelease(cn);
+ CFRelease(search_cert);
+ }
+ }
+
+ CFRelease(temp);
+ }
+
+ CFRelease(ssl_policy);
+ CFRelease(query);
+ CFRelease(cert);
+ if (host)
+ CFRelease(host);
+
+ return status;
+}
+
+static OSStatus
+import_pem(const char *path, char *passphrase, CFArrayRef *certificate)
+{
+ CFDataRef data_ref;
+ CFStringRef file_type;
+ SecExternalItemType item_type;
+ SecItemImportExportKeyParameters params;
+ SecExternalFormat format;
+ FILE *fp;
+ UInt8 *certdata;
+ struct stat buf;
+
+ if (!path || strlen(path) == 0)
+ return errSecInvalidValue;
+
+ if (stat(path, &buf) != 0)
+ {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return -1;
+
+ return errSecInvalidValue;
+ }
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return errSecInvalidValue;
+
+ certdata = malloc(buf.st_size);
+ if (!certdata)
+ {
+ fclose(fp);
+ return errSecAllocate;
+ }
+
+ if (fread(certdata, 1, buf.st_size, fp) != buf.st_size)
+ {
+ fclose(fp);
+ free(certdata);
+ return errSSLBadCert;
+ }
+ fclose(fp);
+
+ data_ref = CFDataCreate(NULL, certdata, buf.st_size);
+ free(certdata);
+
+ memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters));
+ params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ /* Set OS default access control on the imported key */
+ params.flags = kSecKeyNoAccessControl;
+ if (passphrase)
+ params.passphrase = CFStringCreateWithCString(NULL, passphrase,
+ kCFStringEncodingUTF8);
+
+ /*
+ * Though we explicitly say this is a PEM file, Secure Transport will
+ * consider that a mere hint. Providing a file ending and a file format is
+ * what we can do to assist.
+ */
+ file_type = CFSTR(".pem");
+ if (!file_type)
+ return errSecAllocate;
+
+ format = kSecFormatPEMSequence;
+ item_type = kSecItemTypeCertificate;
+
+ return SecItemImport(data_ref, file_type, &format, &item_type,
+ 0 /* flags */, ¶ms, NULL /* keychain */,
+ certificate);
+}
+
+/*
+ * Secure Transport has the concept of an identity, which is a packaging of a
+ * private key and the certificate which contains the public key. The identity
+ * is what is used for verifying the connection, so we need to provide a
+ * SecIdentityRef identity to the API.
+ *
+ * A complete, and packaged, identity can be contained in a Keychain, in which
+ * case we can load it directly without having to create anything ourselves.
+ * In the case where we don't have a prepared identity in a Keychain, we need
+ * to create it from its components (certificate and key). The certificate must
+ * in that case be be located in a PEM file on the local filesystem. The key
+ * can either be in a PEM file or in the Keychain.
+ *
+ * While keeping identities in the Keychain is the macOS thing to do, we want
+ * to be functionally compatible with the OpenSSL support in libpq. Thus we not
+ * only need to support creating an identity from a private key contained in a
+ * PEM file, it needs to be the default. The order in which we discover the
+ * identity is:
+ *
+ * 1. Certificate and key in local files
+ * 2. Certificate in local file and key in Keychain
+ * 3. Identity in Keychain
+ *
+ * Since failures can come from multiple places, the PGconn errorMessage is
+ * populated here even for SSL library errors.
+ */
+static OSStatus
+pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array,
+ CFArrayRef *key_array, CFArrayRef *rootcert_array)
+{
+ OSStatus status;
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ bool have_homedir;
+ bool cert_from_file = false;
+ char *ssl_err_msg;
+ SecIdentityRef identity = NULL;
+ SecCertificateRef cert_ref;
+ SecCertificateRef root_ref[1];
+ SecKeyRef key_ref = NULL;
+ CFArrayRef keychains = NULL;
+ SecKeychainRef kcref[2];
+ CFMutableArrayRef cert_connection;
+
+ /*
+ * If we have a keychain configured, open the referenced keychain as well
+ * as the default keychain and prepare an array with the references for
+ * searching. If no additional keychain is specified we don't need to open
+ * the default keychain and pass to searches since Secure Transport will
+ * use the default when passing NULL instead of an array of Keychain refs.
+ */
+ if (conn->keychain)
+ {
+ if (stat(conn->keychain, &buf) == 0)
+ {
+ status = SecKeychainOpen(conn->keychain, &kcref[0]);
+ if (status == noErr && kcref[0] != NULL)
+ {
+ SecKeychainStatus kcstatus;
+ status = SecKeychainGetStatus(kcref[0], &kcstatus);
+ if (status == noErr)
+ {
+ switch (kcstatus)
+ {
+ /*
+ * If the Keychain is already unlocked, readable or
+ * writeable, we don't need to do more. If not, try to
+ * unlock it.
+ */
+ case kSecUnlockStateStatus:
+ case kSecReadPermStatus:
+ case kSecWritePermStatus:
+ break;
+ default:
+ /*
+ * TODO: we need to get the passphrase from the
+ * user, but we currently don't have a good
+ * mechanism for that. For now, we assume a blank
+ * passphrase but we need to figure out a good way
+ * to have the user enter the passphrase.
+ */
+ SecKeychainUnlock(kcref[0], 0, "", TRUE);
+ break;
+ }
+ }
+
+ /*
+ * We only need to open, and add, the default Keychain in case
+ * have a user keychain opened, else we will pass NULL to any
+ * keychain search which will use the default keychain by..
+ * default.
+ */
+ SecKeychainCopyDefault(&kcref[1]);
+ keychains = CFArrayCreate(NULL, (const void **) kcref, 2,
+ &kCFTypeArrayCallBacks);
+ }
+ }
+ }
+
+ /*
+ * We'll need the home directory if any of the relevant parameters are
+ * defaulted. If pqGetHomeDirectory fails, act as though none of the files
+ * could be found.
+ */
+ if (!(conn->sslcert && strlen(conn->sslcert) > 0) ||
+ !(conn->sslkey && strlen(conn->sslkey) > 0) ||
+ !(conn->sslrootcert && strlen(conn->sslrootcert) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /*
+ * Try to load the root cert from either a user defined keychain or the
+ * default Keychain in case none is specified
+ */
+ if (conn->sslrootcert &&
+ pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ root_ref[0] = NULL;
+ strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL);
+
+ if (root_ref[0])
+ conn->st_rootcert = (void *) CFArrayCreate(NULL,
+ (const void **) root_ref,
+ 1, &kCFTypeArrayCallBacks);
+ }
+
+ if (!conn->st_rootcert)
+ {
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * stat() failed; assume root file doesn't exist. If sslmode is
+ * verify-ca or verify-full, this is an error. Otherwise, continue
+ * without performing any server cert verification.
+ */
+ if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */
+ {
+ /*
+ * The only way to reach here with an empty filename is if
+ * pqGetHomeDirectory failed. That's a sufficiently unusual
+ * case that it seems worth having a specialized error message
+ * for it.
+ */
+ if (fnbuf[0] == '\0')
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get home directory to locate root certificate file\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("root certificate file \"%s\" does not exist\n"
+ "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf);
+ return errSecInternalError;
+ }
+ }
+ else
+ {
+ status = import_pem(fnbuf, NULL, rootcert_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load root certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (*rootcert_array != NULL)
+ conn->st_rootcert = (void *) CFRetain(*rootcert_array);
+ }
+ }
+ }
+
+ /*
+ * If the sslcert config is prefixed with a keychain identifier, the cert
+ * must be located in either the default or the specified keychain.
+ */
+ if (conn->sslcert &&
+ pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf));
+
+ status = import_identity_keychain(fnbuf, &identity, keychains);
+
+ if (identity && status == noErr)
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ else
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate \"%s\" from keychain: %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+
+ return status;
+ }
+ }
+ /*
+ * No prefix on the sslcert config, the certificate must thus reside in a
+ * file on disk.
+ */
+ else
+ {
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0')
+ {
+ status = import_pem(fnbuf, NULL, cert_array);
+ if (status != noErr)
+ {
+ if (status == -1)
+ return noErr;
+
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load certificate file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0);
+ cert_from_file = true;
+
+ /*
+ * We now have a certificate, so we need a private key as well in
+ * order to create the identity.
+ */
+
+ /*
+ * The sslkey config is prefixed with keychain: indicating that the
+ * key should be loaded from Keychain instead of the filesystem.
+ * Search for the private key matching the cert_ref in the opened
+ * Keychains. If found, we get the identity returned.
+ */
+ if (conn->sslkey &&
+ pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0)
+ {
+ status = SecIdentityCreateWithCertificate(keychains, cert_ref,
+ &identity);
+ if (status != noErr)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"),
+ fnbuf);
+ return errSecInternalError;
+ }
+
+ SecIdentityCopyPrivateKey(identity, &key_ref);
+ }
+ else
+ {
+ if (conn->sslkey && strlen(conn->sslkey) > 0)
+ strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ /*
+ * If there is a matching file on the filesystem, require the
+ * key to be loaded from that file.
+ */
+ if (fnbuf[0] != '\0')
+ {
+
+ if (stat(fnbuf, &buf) != 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+ fnbuf);
+ return errSecInvalidKeyRef;
+ }
+#ifndef WIN32
+ if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
+ fnbuf);
+ return errSecInvalidKeyRef;
+ }
+#endif
+ status = import_pem(fnbuf, NULL, key_array);
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0);
+ }
+ }
+
+ /*
+ * We have certificate and a key loaded from files on disk, now we
+ * can create an identity from this pair.
+ */
+ if (key_ref)
+ identity = SecIdentityCreate(NULL, cert_ref, key_ref);
+ }
+ }
+
+ if (!identity)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create identity from certificate/key\n"));
+ return errSecInvalidValue;
+ }
+
+ /*
+ * If we have created the identity "by hand" without involving the
+ * Keychain we need to include the certificates in the array passed to
+ * SSLSetCertificate()
+ */
+ if (cert_from_file)
+ {
+ cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array);
+ CFArraySetValueAtIndex(cert_connection, 0, identity);
+ }
+ else
+ {
+ cert_connection = CFArrayCreateMutable(NULL, 1L,
+ &kCFTypeArrayCallBacks);
+ CFArrayInsertValueAtIndex(cert_connection, 0,
+ (const void *) identity);
+ }
+
+ status = SSLSetCertificate((SSLContextRef) conn->ssl, cert_connection);
+
+ if (status != noErr)
+ {
+ ssl_err_msg = pg_SSLerrmessage(status);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not set certificate for connection: (%d) %s\n"),
+ status, ssl_err_msg);
+ pg_SSLerrfree(ssl_err_msg);
+ return status;
+ }
+
+ if (key_ref)
+ {
+ conn->ssl_key_bits = SecKeyGetBlockSize(key_ref);
+ CFRelease(key_ref);
+ }
+
+ return noErr;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL");
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+ if (strcmp(struct_name, "SecureTransport") == 0)
+ return conn->ssl;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "key_bits",
+ "cipher",
+ "protocol",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SSLCipherSuite cipher;
+ SSLProtocol protocol;
+ OSStatus status;
+ const char *attribute = NULL;
+
+ if (!conn || !conn->ssl)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ attribute = "SecureTransport";
+ else if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ if (conn->ssl_key_bits > 0)
+ {
+ static char sslbits_str[10];
+ snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits);
+ attribute = sslbits_str;
+ }
+ }
+ else if (strcmp(attribute_name, "cipher") == 0)
+ {
+ status = SSLGetNegotiatedCipher((SSLContextRef) conn->ssl, &cipher);
+ if (status == noErr)
+ return pg_SSLciphername(cipher);
+ }
+ else if (strcmp(attribute_name, "protocol") == 0)
+ {
+ status = SSLGetNegotiatedProtocolVersion((SSLContextRef) conn->ssl, &protocol);
+ if (status == noErr)
+ {
+ switch (protocol)
+ {
+ case kTLSProtocol11:
+ attribute = "TLSv1.1";
+ break;
+ case kTLSProtocol12:
+ attribute = "TLSv1.2";
+ break;
+ case kTLSProtocol13:
+ attribute = "TLSv1.3";
+ default:
+ break;
+ }
+ }
+ }
+
+ return attribute;
+}
+
+/* ------------------------------------------------------------ */
+/* Secure Transport Information Functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * Obtain reason string for passed SSL errcode
+ */
+static char ssl_noerr[] = "no SSL error reported";
+static char ssl_nomem[] = "out of memory allocating error description";
+#define SSL_ERR_LEN 128
+
+static char *
+pg_SSLerrmessage(OSStatus errcode)
+{
+ char *err_buf;
+ const char *tmp;
+ CFStringRef err_msg;
+
+ if (errcode == noErr || errcode == errSecSuccess)
+ return ssl_noerr;
+
+ err_buf = malloc(SSL_ERR_LEN);
+ if (!err_buf)
+ return ssl_nomem;
+
+ err_msg = SecCopyErrorMessageString(errcode, NULL);
+ if (err_msg)
+ {
+ tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8);
+ strlcpy(err_buf, tmp, SSL_ERR_LEN);
+ CFRelease(err_msg);
+ }
+ else
+ snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode);
+
+ return err_buf;
+}
+
+static void
+pg_SSLerrfree(char *err_buf)
+{
+ if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr)
+ free(err_buf);
+}
+
+/*
+ * pg_SSLsessionstate
+ *
+ * Returns 0 if the connection is open and -1 in case the connection is closed,
+ * or its status unknown. If msg is non-NULL the current state is copied with
+ * at most len - 1 characters ensuring a NUL terminated returned string.
+ */
+static int
+pg_SSLsessionstate(PGconn *conn, char *msg, size_t len)
+{
+ SSLSessionState state;
+ OSStatus status;
+ const char *status_msg;
+
+ /*
+ * If conn->ssl isn't defined we will report "Unknown" which it could be
+ * argued being correct or not, but since we don't know if there has ever
+ * been a connection at all it's not more correct to say "Closed" or
+ * "Aborted".
+ */
+ if (conn->ssl)
+ status = SSLGetSessionState((SSLContextRef) conn->ssl, &state);
+ else
+ {
+ status = errSecInternalError;
+ state = -1;
+ }
+
+ switch (state)
+ {
+ case kSSLConnected:
+ status_msg = "Connected";
+ status = 0;
+ break;
+ case kSSLHandshake:
+ status_msg = "Handshake";
+ status = 0;
+ break;
+ case kSSLIdle:
+ status_msg = "Idle";
+ status = 0;
+ break;
+ case kSSLClosed:
+ status_msg = "Closed";
+ status = -1;
+ break;
+ case kSSLAborted:
+ status_msg = "Aborted";
+ status = -1;
+ break;
+ default:
+ status_msg = "Unknown";
+ status = -1;
+ break;
+ }
+
+ if (msg)
+ strlcpy(msg, status_msg, len);
+
+ return (status == noErr ? 0 : -1);
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index a06fc7dc82..e7fbc88be5 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -157,8 +157,10 @@ void
PQinitOpenSSL(int do_ssl, int do_crypto)
{
#ifdef USE_SSL
+#ifdef USE_OPENSSL
pgtls_init_library(do_ssl, do_crypto);
#endif
+#endif
}
/*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 975ab33d02..db061253f8 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -474,8 +474,19 @@ struct pg_conn
void *engine; /* dummy field to keep struct the same if
* OpenSSL version changes */
#endif
-#endif /* USE_OPENSSL */
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
+
+#ifdef USE_SECURETRANSPORT
+ char *keychain;
+ bool keychain_use_default;
+
+ void *ssl; /* SSL context reference */
+ void *st_rootcert;
+ ssize_t ssl_buffered;
+ int ssl_key_bits;
+#endif /* USE_SECURETRANSPORT */
+
+#endif /* USE_SSL */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
@@ -728,6 +739,7 @@ extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
*
* This is not supported with old versions of OpenSSL that don't have
* the X509_get_signature_nid() function.
+ * It's also not supported with Apple Secure Transport.
*/
#if defined(USE_OPENSSL) && defined(HAVE_X509_GET_SIGNATURE_NID)
#define HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 97389c90f8..0c9b4cdddd 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_securetransport
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -130,6 +131,20 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl
ssl/root+client.crl: ssl/root.crl ssl/client.crl
cat $^ > $@
+#### Keychains
+
+ifeq ($(with_securetransport),yes)
+
+KEYCHAINS := ssl/client.keychain
+
+# This target generates all Keychains
+keychains: $(KEYCHAINS)
+
+PWD=$(shell pwd)
+ssl/client.keychain: ssl/client.crt ssl/client.key
+ certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p=
+endif
+
.PHONY: sslfiles-clean
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
diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm
index 3b451a360a..ae4406bcaa 100644
--- a/src/test/ssl/ServerSetup.pm
+++ b/src/test/ssl/ServerSetup.pm
@@ -26,12 +26,22 @@ use Test::More;
use Exporter 'import';
our @EXPORT = qw(
+ set_backend
configure_test_server_for_ssl
switch_server_cert
test_connect_fails
test_connect_ok
);
+our $current_backend;
+
+sub set_backend
+{
+ my ($backend) = @_;
+
+ $current_backend = $backend;
+}
+
# Define a couple of helper functions to test connecting to the server.
# The first argument is a base connection string to use for connection.
@@ -56,7 +66,7 @@ sub test_connect_fails
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($common_connstr, $connstr, $expected_stderr, $test_name) = @_;
+ my ($common_connstr, $connstr, $test_name, %expected_stderr) = @_;
my $cmd = [
'psql', '-X', '-A', '-t', '-c',
@@ -64,7 +74,7 @@ sub test_connect_fails
'-d', "$common_connstr $connstr"
];
- command_fails_like($cmd, $expected_stderr, $test_name);
+ command_fails_like($cmd, $expected_stderr{$current_backend}, $test_name);
return;
}
@@ -114,6 +124,7 @@ sub configure_test_server_for_ssl
print $conf "log_hostname=on\n";
print $conf "listen_addresses='$serverhost'\n";
print $conf "log_statement=all\n";
+ print $conf "ssl_keychain_use_default=on\n";
# enable SSL and set up server key
print $conf "include 'sslconfig.conf'";
@@ -155,7 +166,14 @@ sub switch_server_cert
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";
+ if (check_pg_config("#define USE_SECURETRANSPORT 1"))
+ {
+ print $sslconf "ssl_crl_file=''\n";
+ }
+ else
+ {
+ print $sslconf "ssl_crl_file='root+client.crl'\n";
+ }
close $sslconf;
$node->restart;
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 2b875a3c95..6352bb0737 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -6,9 +6,34 @@ use Test::More;
use ServerSetup;
use File::Copy;
+# Some tests are backend specific, so store the currently used backend and
+# some particular capabilities which are interesting for the test
+my %tls_backend;
+
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 65;
+ $tls_backend{library} = 'openssl';
+ $tls_backend{library_name} = 'OpenSSL';
+ $tls_backend{tests} = 65;
+ $tls_backend{crl_support} = 1;
+ $tls_backend{keychain_support} = 0;
+}
+elsif ($ENV{with_securetransport} eq 'yes')
+{
+ $tls_backend{library} = 'securetransport';
+ $tls_backend{library_name} = 'Secure Transport';
+ $tls_backend{tests} = 68;
+ $tls_backend{crl_support} = 0;
+ $tls_backend{keychain_support} = 1;
+}
+else
+{
+ $tls_backend{library} = undef;
+}
+
+if (defined $tls_backend{library})
+{
+ plan tests => $tls_backend{tests};
}
else
{
@@ -25,6 +50,8 @@ my $SERVERHOSTADDR = '127.0.0.1';
# Allocation of base connection string shared among multiple tests.
my $common_connstr;
+set_backend($tls_backend{library});
+
# The client's private key must not be world-readable, so take a copy
# of the key stored in the code tree and update its permissions.
copy("ssl/client.key", "ssl/client_tmp.key");
@@ -52,7 +79,7 @@ $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, $tls_backend{library_name}, 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, 'trust');
@@ -65,10 +92,28 @@ 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);
+if ($tls_backend{library} eq 'securetransport')
+{
+ command_ok(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart succeeds with password-protected key file with wrong password');
+ $node->_update_pid(1);
+
+ $common_connstr =
+ "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test";
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=invalid sslmode=require",
+ "connect without server root cert sslmode=require",
+ ( securetransport => qr/record overflow/ ));
+}
+else
+{
+ 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";
@@ -98,8 +143,8 @@ $common_connstr =
# The server should not accept non-SSL connections.
test_connect_fails(
$common_connstr, "sslmode=disable",
- qr/\Qno pg_hba.conf entry\E/,
- "server doesn't accept non-SSL connections");
+ "server doesn't accept non-SSL connections",
+ ( openssl => qr/\Qno pg_hba.conf entry\E/ ,securetransport => qr/\Qno pg_hba.conf entry\E/ ));
# Try without a root cert. In sslmode=require, this should work. In verify-ca
# or verify-full mode it should fail.
@@ -110,31 +155,50 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
- "connect without server root cert sslmode=verify-ca");
+ "connect without server root cert sslmode=verify-ca",
+ (
+ openssl => qr/root certificate file "invalid" does not exist/,
+ securetransport => qr/root certificate file "invalid" does not exist/
+ ));
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
- "connect without server root cert sslmode=verify-full");
+ "connect without server root cert sslmode=verify-full",
+ (
+ openssl => qr/root certificate file "invalid" does not exist/,
+ securetransport => qr/root certificate file "invalid" does not exist/
+ ));
# 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");
+ "connect with wrong server root cert sslmode=require",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
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");
+ "connect with wrong server root cert sslmode=verify-ca",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
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");
+ "connect with wrong server root cert sslmode=verify-full",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/The specified item has no access control/
+ ));
# 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");
+ "connect with server CA cert, without root CA",
+ ( openssl => qr/SSL error/, securetransport => qr/SSL error/));
# And finally, with the correct root cert.
test_connect_ok(
@@ -163,24 +227,36 @@ test_connect_ok(
# 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",
- "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");
-
-# 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",
- "CRL with a non-revoked cert");
+if ($tls_backend{crl_support})
+{
+ # 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",
+ "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",
+ "CRL belonging to a different CA",
+ ( openssl => qr/SSL error/ ));
+
+ # 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",
+ "CRL with a non-revoked cert");
+}
+else
+{
+ # Test that the presence of a CRL configuration throws an error
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "unsupported sslcrl option",
+ ( securetransport => qr/CRL files are not supported/ ));
+}
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
@@ -198,8 +274,11 @@ 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/,
- "mismatch between host name and server certificate sslmode=verify-full");
+ "mismatch between host name and server certificate sslmode=verify-full",
+ (
+ openssl => qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name "wronghost.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
@@ -223,13 +302,19 @@ 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/,
- "host name not matching with X.509 Subject Alternative Names");
+ "host name not matching with X.509 Subject Alternative Names",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
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/,
- "host name not matching with X.509 Subject Alternative Names wildcard");
+ "host name not matching with X.509 Subject Alternative Names wildcard",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test certificate with a single Subject Alternative Name. (this gives a
# slightly different error message, that's all)
@@ -246,14 +331,19 @@ 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/,
- "host name not matching with a single X.509 Subject Alternative Name");
+ "host name not matching with a single X.509 Subject Alternative Name",
+ (
+ openssl => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "wronghost.alt-name.pg-ssltest.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
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/,
- "host name not matching with a single X.509 Subject Alternative Name wildcard"
-);
+ "host name not matching with a single X.509 Subject Alternative Name wildcard",
+ (
+ openssl => qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN
# should be ignored when the certificate has both.
@@ -273,8 +363,11 @@ 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/,
- "certificate with both a CN and SANs ignores CN");
+ "certificate with both a CN and SANs ignores CN",
+ (
+ openssl => 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/,
+ securetransport => qr/The specified item has no access control/
+ ));
# 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.
@@ -289,8 +382,11 @@ 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/,
- "server certificate without CN or SANs sslmode=verify-full");
+ "server certificate without CN or SANs sslmode=verify-full",
+ (
+ openssl => qr/could not get server's host name from server certificate/,
+ securetransport => qr/The specified item has no access control/
+ ));
# Test that the CRL works
switch_server_cert($node, 'server-revoked');
@@ -303,11 +399,46 @@ test_connect_ok(
$common_connstr,
"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
"connects without client-side CRL");
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
- qr/SSL error/,
- "does not connect with client-side CRL");
+if ($tls_backend{crl_support})
+{
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL",
+ ( openssl => qr/SSL error/ ));
+}
+else
+{
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "does not connect with client-side CRL",
+ ( securetransport => qr/CRL files are not supported with Secure Transport/ ));
+}
+
+# test Secure Transport keychain support
+if ($tls_backend{keychain_support})
+{
+ $common_connstr =
+ "user=ssltestuser dbname=certdb hostaddr=$SERVERHOSTADDR sslmode=verify-full";
+ # empty keychain
+ test_connect_fails($common_connstr,
+ "keychain=invalid",
+ "invalid Keychain file reference",
+ ( securetransport => qr/The specified item has no access control/ ));
+
+ # correct client cert in keychain with and without proper label
+ test_connect_fails(
+ $common_connstr,
+ "keychain=ssl/client.keychain",
+ "client cert in keychain but without label",
+ ( securetransport => qr/The specified item has no access control/ ));
+ test_connect_ok(
+ $common_connstr,
+ "sslcert=keychain:ssltestuser keychain=ssl/client.keychain",
+ "client cert in keychain");
+}
+
### Server-side tests.
###
@@ -322,8 +453,11 @@ $common_connstr =
test_connect_fails(
$common_connstr,
"user=ssltestuser sslcert=invalid",
- qr/connection requires a valid client certificate/,
- "certificate authorization fails without client cert");
+ "certificate authorization fails without client cert",
+ (
+ openssl => qr/connection requires a valid client certificate/,
+ securetransport => qr/connection requires a valid client certificate/
+ ));
# correct client cert
test_connect_ok(
@@ -335,23 +469,30 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_wrongperms_tmp.key",
- qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
- "certificate authorization fails because of file permissions");
+ "certificate authorization fails because of file permissions",
+ (
+ openssl => qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!,
+ securetransport => qr!\Qprivate key file "ssl/client_wrongperms_tmp.key" has group or world access\E!
+ ));
# client cert belonging to another user
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
- "certificate authorization fails with client cert belonging to another user"
-);
+ "certificate authorization fails with client cert belonging to another user",
+ (
+ openssl => qr/certificate authentication failed for user "anotheruser"/,
+ securetransport => qr/certificate authentication failed for user "anotheruser"/
+ ));
-# revoked client cert
-test_connect_fails(
- $common_connstr,
- "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
- qr/SSL error/,
- "certificate authorization fails with revoked client cert");
+if ($tls_backend{crl_support})
+{
+ test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked_tmp.key",
+ "certificate authorization fails with revoked client cert",
+ ( openssl => qr/SSL error/, securetransport => qr/SSL error/ ));
+}
# 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');
@@ -363,8 +504,13 @@ test_connect_ok(
"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");
+ "intermediate client certificate is missing",
+ (
+ openssl => qr/SSL error/,
+ securetransport => qr/certificate authentication failed/
+ ));
# clean up
unlink("ssl/client_tmp.key", "ssl/client_wrongperms_tmp.key",
"ssl/client-revoked_tmp.key");
+unlink("ssl/client.keychain") if ($tls_backend{keychain_support});
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index b460a7fa8a..5c36bddd06 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -8,7 +8,8 @@ use Test::More;
use ServerSetup;
use File::Copy;
-if ($ENV{with_openssl} ne 'yes')
+if ($ENV{with_openssl} ne 'yes' &&
+ $ENV{with_securetransport} ne 'yes')
{
plan skip_all => 'SSL not supported by this build';
}
--
2.14.1.145.gb3622a4ee
On Sun, Oct 28, 2018 at 11:42 PM Daniel Gustafsson <daniel@yesql.se> wrote:
On 26 Sep 2018, at 23:19, Daniel Gustafsson <daniel@yesql.se> wrote:
I’ve rebased these changes on top of your v9 patch as the attached v10.
Attached is a v11 rebased on top of todays HEAD, which had minor conflicts due
to the recent snprintf work. No functional changes are introduced over v10.
Thank you,
Unfortunately, patch needs to be fixed - it doesn't pass "make -C ssl check"
t/001_ssltests.pl .. 1/65 Bailout called. Further testing stopped:
system pg_ctl failed
FAILED--Further testing stopped: system pg_ctl failed
Could you post an updated version?
On Fri, Nov 30, 2018 at 02:00:00PM +0100, Dmitry Dolgov wrote:
Unfortunately, patch needs to be fixed - it doesn't pass "make -C ssl check"
t/001_ssltests.pl .. 1/65 Bailout called. Further testing stopped:
system pg_ctl failed
FAILED--Further testing stopped: system pg_ctl failedCould you post an updated version?
This has not been answered yet, and a couple of months have gone by,
so I am marking the patch as returned with feedback.
--
Michael