From 3ff493649db46e9fcf14fb2551c7df7a3f6c4726 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson 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 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 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 is required for OpenSSL])]) fi +if test "$with_securetransport" = yes ; then + AC_CHECK_HEADER(Security/Security.h, [], [AC_MSG_ERROR([header file is required for Apple Secure Transport])]) + AC_CHECK_HEADER(CoreFoundation/CoreFoundation.h, [], [AC_MSG_ERROR([header file 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 +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_NETINET_TCP_H +#include +#include +#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 +#include +#include +#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 + +/* + * 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 #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 #include +#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 @@ -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-.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 +#include +#include + +#include "libpq-fe.h" +#include "fe-auth.h" +#include "libpq-int.h" + +#include +#include +#include +#include +#ifdef HAVE_NETINET_TCP_H +#include +#endif +#include + +#include + +#include +#include +#include +#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 +#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