From 8ca4571baf3811e8da5c73f362f8666fd0178fbe Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Sun, 21 Jan 2018 23:40:04 +0100 Subject: [PATCH 1/3] WIP: Add support for Apple Secure Transport SSL library This adds frontend and backend support for the Secure Transport SSL library on macOS, but currently lacks SCRAM support. Thus, the patch is not in a final state but is provided for hacking and testing. --- configure | 57 + configure.in | 14 + src/Makefile.global.in | 1 + src/backend/Makefile | 6 +- src/backend/libpq/Makefile | 5 + src/backend/libpq/be-secure-securetransport.c | 1319 +++++++++++++++++++ src/backend/libpq/be-secure.c | 3 + src/backend/utils/misc/guc.c | 25 + src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/common/securetransport.h | 514 ++++++++ src/include/common/sha2.h | 4 +- src/include/libpq/libpq-be.h | 17 +- src/include/libpq/libpq.h | 3 + src/include/pg_config.h.in | 3 + src/include/pg_config_manual.h | 12 +- src/interfaces/libpq/Makefile | 6 +- src/interfaces/libpq/fe-connect.c | 6 + src/interfaces/libpq/fe-secure-securetransport.c | 1499 ++++++++++++++++++++++ src/interfaces/libpq/libpq-int.h | 22 +- src/test/ssl/Makefile | 14 + src/test/ssl/ServerSetup.pm | 9 +- src/test/ssl/t/001_ssltests.pl | 109 +- src/test/ssl/t/002_scram.pl | 30 +- 23 files changed, 3639 insertions(+), 40 deletions(-) create mode 100644 src/backend/libpq/be-secure-securetransport.c create mode 100644 src/include/common/securetransport.h create mode 100644 src/interfaces/libpq/fe-secure-securetransport.c diff --git a/configure b/configure index 7dcca506f8..b9db714127 100755 --- a/configure +++ b/configure @@ -707,6 +707,7 @@ UUID_EXTRA_OBJS with_uuid with_systemd with_selinux +with_securetransport with_openssl krb_srvtab with_python @@ -835,6 +836,7 @@ with_bsd_auth with_ldap with_bonjour with_openssl +with_securetransport with_selinux with_systemd with_readline @@ -1529,6 +1531,7 @@ Optional Packages: --with-ldap build with LDAP support --with-bonjour build with Bonjour support --with-openssl build with OpenSSL support + --with-securetransport build with Apple Secure Transport support --with-selinux build with SELinux support --with-systemd build with systemd support --without-readline do not use GNU Readline nor BSD Libedit for editing @@ -5995,6 +5998,41 @@ fi $as_echo "$with_openssl" >&6; } +# +# Apple Secure Transport +# +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with Apple Secure Transport support" >&5 +$as_echo_n "checking whether to build with Apple Secure Transport support... " >&6; } + + + +# Check whether --with-securetransport was given. +if test "${with_securetransport+set}" = set; then : + withval=$with_securetransport; + case $withval in + yes) + +$as_echo "#define USE_SECURETRANSPORT 1" >>confdefs.h + + ;; + no) + : + ;; + *) + as_fn_error $? "no argument expected for --with-securetransport option" "$LINENO" 5 + ;; + esac + +else + with_securetransport=no + +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_securetransport" >&5 +$as_echo "$with_securetransport" >&6; } + + # # SELinux # @@ -10978,6 +11016,25 @@ else fi +fi + +if test "$with_securetransport" = yes ; then + ac_fn_c_check_header_mongrel "$LINENO" "Security/Security.h" "ac_cv_header_Security_Security_h" "$ac_includes_default" +if test "x$ac_cv_header_Security_Security_h" = xyes; then : + +else + as_fn_error $? "header file 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 4d26034579..20f3d55c07 100644 --- a/configure.in +++ b/configure.in @@ -703,6 +703,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support], AC_MSG_RESULT([$with_openssl]) AC_SUBST(with_openssl) +# +# Apple Secure Transport +# +AC_MSG_CHECKING([whether to build with Apple Secure Transport support]) +PGAC_ARG_BOOL(with, securetransport, no, [build with Apple Secure Transport support], + [AC_DEFINE([USE_SECURETRANSPORT], 1, [Define to build with Apple Secure Transport support. (--with-securetransport)])]) +AC_MSG_RESULT([$with_securetransport]) +AC_SUBST(with_securetransport) + # # SELinux # @@ -1225,6 +1234,11 @@ if test "$with_openssl" = yes ; then AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file 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 d980f81046..9f60a3dd0d 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -184,6 +184,7 @@ with_perl = @with_perl@ with_python = @with_python@ with_tcl = @with_tcl@ with_openssl = @with_openssl@ +with_securetransport = @with_securetransport@ with_selinux = @with_selinux@ with_systemd = @with_systemd@ with_libxml = @with_libxml@ diff --git a/src/backend/Makefile b/src/backend/Makefile index 4a28267339..fc11b7271c 100644 --- a/src/backend/Makefile +++ b/src/backend/Makefile @@ -49,6 +49,10 @@ ifeq ($(with_systemd),yes) LIBS += -lsystemd endif +ifeq ($(with_securetransport),yes) +LDFLAGS += -framework CoreFoundation -framework Security +endif + ########################################################################## all: submake-libpgport submake-schemapg postgres $(POSTGRES_IMP) @@ -58,7 +62,7 @@ ifneq ($(PORTNAME), win32) ifneq ($(PORTNAME), aix) postgres: $(OBJS) - $(CC) $(CFLAGS) $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@ + $(CC) $(CFLAGS) -framework CoreFoundation -framework Security $(LDFLAGS) $(LDFLAGS_EX) $(export_dynamic) $(call expand_subsys,$^) $(LIBS) -o $@ endif endif diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 7fa2b02743..3a835235b9 100644 --- a/src/backend/libpq/Makefile +++ b/src/backend/libpq/Makefile @@ -21,4 +21,9 @@ ifeq ($(with_openssl),yes) OBJS += be-secure-openssl.o endif +ifeq ($(with_securetransport),yes) +OBJS += be-secure-securetransport.o +override CFLAGS += -framework Security -framework CoreFoundation +endif + include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/libpq/be-secure-securetransport.c b/src/backend/libpq/be-secure-securetransport.c new file mode 100644 index 0000000000..ff8af56047 --- /dev/null +++ b/src/backend/libpq/be-secure-securetransport.c @@ -0,0 +1,1319 @@ +/*------------------------------------------------------------------------- + * + * be-secure-securetransport.c + * Apple Secure Transport support + * + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * TODO: + * - Load DH keys from file + * - It would be good to be able to set "not applicable" on some options + * like compression which isn't supported in Secure Transport (and most + * likely any other SSL libraries supported in the future). + * - Support memory allocation in Secure Transport via a custom Core + * Foundation allocator which is backed by a MemoryContext? Not sure it + * would be possible but would be interested to investigate. + * + * + * + * IDENTIFICATION + * src/backend/libpq/be-secure-securetransport.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include +#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); + +/* ------------------------------------------------------------ */ +/* Backend API */ +/* ------------------------------------------------------------ */ + +/* + * be_tls_init + * Initialize global Secure Transport structures (if any) + * + * This is where we'd like to load and parse certificates and private keys + * for the connection, but since Secure Transport will spawn threads deep + * inside the API we must postpone this until inside a backend. This means + * that we won't fail on an incorrect certificate chain until a connection + * is attempted, unlike with OpenSSL where we fail immediately on server + * startup. + * + * Another reason to defer this until when in the backend is that Keychains + * are SQLite3 backed, and sqlite does not allow access across a fork. See + * https://sqlite.org/faq.html#q6 for more information. + */ +int +be_tls_init(bool isServerStart) +{ + memset(internal_err, '\0', sizeof(internal_err)); + return 0; +} + +/* + * be_tls_destroy + * Tear down global Secure Transport structures and return resources. + */ +void +be_tls_destroy(void) +{ + ssl_loaded_verify_locations = false; +} + +/* + * bt_tls_open_server + * Attempt to negotiate a secure connection + * + * Unlike the OpenSSL backend, this function is responsible for loading keys + * and certificates for use in the connection. See the comment on be_tls_init + * for further reasoning around this. + */ +int +be_tls_open_server(Port *port) +{ + OSStatus status; + SecTrustRef trust; + SecTrustResultType trust_eval; + SecIdentityRef identity; + CFArrayRef root_certificates; + CFArrayRef certificates; + CFArrayRef keys; + CFArrayRef keychains; + CFMutableArrayRef chain; + char *dh_buf; + int dh_len; + + Assert(!port->ssl); + + if (ssl_keychain_file[0]) + load_keychain(ssl_keychain_file, &keychains); + + /* + * If the ssl_cert_file is prefixed with a keychain reference, we will try + * to load a complete identity from the Keychain. + */ + if (pg_strncasecmp(ssl_cert_file, KC_PREFIX, KC_PREFIX_LEN) == 0) + status = load_identity_keychain(ssl_cert_file + KC_PREFIX_LEN, &identity, keychains); + else + { + status = load_certificate_file(ssl_cert_file, &certificates); + if (status != noErr) + ereport(COMMERROR, + (errmsg("could not load server certificate \"%s\": \"%s\"", + ssl_cert_file, pg_SSLerrmessage(status)))); + + load_key(ssl_key_file, &keys); + + /* + * We now have a certificate and either a private key, or a search path + * which should contain it. + */ + identity = SecIdentityCreate(NULL, + (SecCertificateRef) CFArrayGetValueAtIndex(certificates, 0), + (SecKeyRef) CFArrayGetValueAtIndex(keys, 0)); + } + + if (identity == NULL) + ereport(COMMERROR, + (errmsg("could not create identity: \"%s\"", + pg_SSLerrmessage(status)))); + + /* + * SSLSetCertificate() sets the certificate(s) to use for the connection. + * The first element in the passed array is required to be the identity + * with elements 1..n being certificates. + */ + chain = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + CFRetain(identity); + CFArrayInsertValueAtIndex(chain, 0, identity); + CFArrayAppendArray(chain, certificates, + CFRangeMake(0, CFArrayGetCount(certificates))); + + /* + * Load the Certificate Authority if configured + */ + if (ssl_ca_file[0]) + { + status = load_certificate_file(ssl_ca_file, &root_certificates); + if (status == noErr) + { + CFArrayAppendArray(chain, root_certificates, + CFRangeMake(0, CFArrayGetCount(root_certificates))); + + ssl_loaded_verify_locations = true; + } + else + { + ereport(LOG, + (errmsg("could not load root certificate \"%s\": \"%s\"", + ssl_ca_file, pg_SSLerrmessage(status)))); + + ssl_loaded_verify_locations = false; + } + } + else + ssl_loaded_verify_locations = false; + + /* + * Certificate Revocation List are not supported in the Secure Transport + * API + */ + if (ssl_crl_file[0]) + ereport(FATAL, + (errmsg("CRL files not supported with Secure Transport"))); + + port->ssl = (void *) SSLCreateContext(NULL, kSSLServerSide, kSSLStreamType); + if (!port->ssl) + ereport(COMMERROR, + (errmsg("could not create SSL context"))); + + port->ssl_in_use = true; + port->ssl_buffered = 0; + + /* + * We use kTryAuthenticate here since we don't know which sslmode the + * client is using. If we were to use kAlwaysAuthenticate then sslmode + * require won't work as intended. + */ + if (ssl_loaded_verify_locations) + SSLSetClientSideAuthenticate((SSLContextRef) port->ssl, kTryAuthenticate); + + /* + * In case the user hasn't configured a DH parameters file, we load a pre- + * computed DH parameter to avoid having Secure Transport computing one for + * us (which is done by default unless one is set). + */ + dh_buf = NULL; + if (ssl_dh_params_file[0]) + dh_len = load_dh_file(ssl_dh_params_file, &dh_buf); + if (!dh_buf || dh_len == 0) + { + dh_buf = pstrdup(FILE_DH2048); + dh_len = sizeof(FILE_DH2048); + } + + load_dh_params(dh_buf, dh_len, true, (SSLContextRef) port->ssl); + + /* + * Set Tlsv1.2 as the minimum protocol version we allow for the connection + */ + status = SSLSetProtocolVersionMin((SSLContextRef) port->ssl, + kTLSProtocol12); + if (status != noErr) + ereport(COMMERROR, + (errmsg("could not set protocol for connection: \"%s\"", + pg_SSLerrmessage(status)))); + + status = SSLSetCertificate((SSLContextRef) port->ssl, + (CFArrayRef) chain); + if (status != noErr) + ereport(COMMERROR, + (errmsg("could not set certificate for connection: \"%s\"", + pg_SSLerrmessage(status)))); + + status = SSLSetIOFuncs((SSLContextRef) port->ssl, + pg_SSLSocketRead, + pg_SSLSocketWrite); + if (status != noErr) + ereport(COMMERROR, + (errmsg("could not set SSL IO functions: \"%s\"", + pg_SSLerrmessage(status)))); + + status = SSLSetSessionOption((SSLContextRef) port->ssl, + kSSLSessionOptionBreakOnClientAuth, true); + if (status != noErr) + ereport(COMMERROR, + (errmsg("could not set SSL certificate validation: \"%s\"", + pg_SSLerrmessage(status)))); + + status = SSLSetConnection((SSLContextRef) port->ssl, port); + if (status != noErr) + ereport(COMMERROR, + (errmsg("could not establish SSL connection: \"%s\"", + pg_SSLerrmessage(status)))); + + /* + * Perform handshake + */ + for (;;) + { + status = SSLHandshake((SSLContextRef) port->ssl); + if (status == noErr) + break; + + if (status == errSSLWouldBlock) + continue; + + if (status == errSSLClosedAbort || status == errSSLClosedGraceful) + return -1; + + if (status == errSSLPeerAuthCompleted) + { + status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust); + if (status != noErr || trust == NULL) + { + ereport(WARNING, + (errmsg("SSLCopyPeerTrust returned: \"%s\"", + pg_SSLerrmessage(status)))); + port->peer_cert_valid = false; + return 0; + } + + if (ssl_loaded_verify_locations) + { + status = SecTrustSetAnchorCertificates(trust, root_certificates); + if (status != noErr) + { + ereport(WARNING, + (errmsg("SecTrustSetAnchorCertificates returned: \"%s\"", + pg_SSLerrmessage(status)))); + return -1; + } + + status = SecTrustSetAnchorCertificatesOnly(trust, false); + if (status != noErr) + { + ereport(WARNING, + (errmsg("SecTrustSetAnchorCertificatesOnly returned: \"%s\"", + pg_SSLerrmessage(status)))); + return -1; + } + } + + trust_eval = 0; + status = SecTrustEvaluate(trust, &trust_eval); + if (status != noErr) + { + ereport(WARNING, + (errmsg("SecTrustEvaluate failed, returned: \"%s\"", + pg_SSLerrmessage(status)))); + return -1; + } + + switch (trust_eval) + { + /* + * If 'Unspecified' then an anchor certificate was reached + * without encountering any explicit user trust. If 'Proceed' + * then the user has chosen to explicitly trust a certificate + * in the chain by clicking "Trust" in the Keychain app. + */ + case kSecTrustResultUnspecified: + case kSecTrustResultProceed: + port->peer_cert_valid = true; + break; + + /* + * 'RecoverableTrustFailure' indicates that the certificate was + * rejected but might be trusted with minor changes to the eval + * context (ignoring expired certificate etc). In the frontend + * we can in some circumstances allow this, but in the backend + * this always means that the client certificate is considered + * untrusted. + */ + case kSecTrustResultRecoverableTrustFailure: + port->peer_cert_valid = false; + break; + + /* + * Treat all other cases as rejection without further + * questioning. + */ + default: + port->peer_cert_valid = false; + break; + } + + if (port->peer_cert_valid) + { + SecCertificateRef usercert = SecTrustGetCertificateAtIndex(trust, 0L); + + CFStringRef usercert_cn; + SecCertificateCopyCommonName(usercert, &usercert_cn); + port->peer_cn = pstrdup(CFStringGetCStringPtr(usercert_cn, kCFStringEncodingUTF8)); + + CFRelease(usercert_cn); + } + + CFRelease(trust); + } + } + + if (status != noErr) + return -1; + + return 0; +} + +/* + * load_key + * Extracts a key from a PEM file on the filesystem + * + * Loads a private key from the specified filename. Unless the key loads this + * will not return but will error out. + */ +static void +load_key(char *name, CFArrayRef *out) +{ + OSStatus status; + struct stat stat_buf; + int ret; + UInt8 *buf; + FILE *fd; + CFDataRef data; + SecExternalFormat format; + SecExternalItemType type; + CFStringRef path; + + if (!check_ssl_key_file_permissions(name, true)) + return; + + if ((fd = AllocateFile(name, "r")) == NULL) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not load private key \"%s\": unable to open", + name))); + + /* + * check_ssl_key_file_permissions() has already checked the file for + * existence and correct permissions, but we still need to stat it to + * get the filesize. + */ + stat(name, &stat_buf); + buf = palloc(stat_buf.st_size); + + ret = fread(buf, 1, stat_buf.st_size, fd); + FreeFile(fd); + + if (ret != stat_buf.st_size) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not load private key \"%s\": unable to read", + name))); + + type = kSecItemTypePrivateKey; + format = kSecFormatPEMSequence; + path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8); + data = CFDataCreate(NULL, buf, stat_buf.st_size); + + SecItemImportExportKeyParameters params; + memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters)); + params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; + /* Set OS default access control on the imported key */ + params.flags = kSecKeyNoAccessControl; + + status = SecItemImport(data, path, &format, &type, 0, ¶ms, NULL, out); + + CFRelease(path); + CFRelease(data); + + if (status != noErr) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("could not load private key \"%s\": \"%s\"", + name, pg_SSLerrmessage(status)))); +} + +/* + * load_keychain + * Open the specified, and default, Keychain + * + * Operations on keychains other than the default take the keychain references + * in an array. We need to copy a reference to the default keychain into the + * array to include it in the search. + * + * For server applications, we don't want modal dialog boxes opened for + * Keychain interaction. Calling SecKeychainSetUserInteractionAllowed(FALSE) + * will turn off all GUI interaction with the user, which may seem like what we + * want server side. This however has the side effect to turn off all GUI + * elements for all applications until some application calls + * SecKeychainSetUserInteractionAllowed(TRUE) or reboots the box. We might thus + * remove wanted GUI interaction from another app, or another app might + * introduce it for + * us. + */ +static OSStatus +load_keychain(char *name, CFArrayRef *keychains) +{ + OSStatus status; + struct stat stat_buf; + SecKeychainRef kc[2]; + + if (stat(name, &stat_buf) != 0) + return errSecInvalidValue; + + status = SecKeychainOpen(name, &kc[0]); + if (status == noErr) + { + SecKeychainUnlock(kc[0], 0, "", TRUE); + /* + * We only need to open, and add, the default Keychain in case have a + * user keychain opened, else we will pass NULL to any keychain search + * which will use the default keychain by.. default. + */ + SecKeychainCopyDefault(&kc[1]); + *keychains = CFArrayCreate(NULL, (const void **) kc, 2, + &kCFTypeArrayCallBacks); + } + else + elog(LOG, "XXX: could not load keychain \"%s\" : %d", name, status); + + return status; +} + +/* + * import_identity_keychain + * Import the identity for the specified certificate from a Keychain + * + * Queries the specified Keychain, or the default unless not defined, for a + * identity with a certificate matching the passed certificate reference. + * Keychains are searched by creating a dictionary of key/value pairs with the + * search criteria and then asking for a copy of the matching entry/entries to + * the search criteria. + */ +static OSStatus +load_identity_keychain(const char *common_name, SecIdentityRef *identity, + CFArrayRef keychains) +{ + OSStatus status = errSecItemNotFound; + CFMutableDictionaryRef query; + CFStringRef cert; + CFArrayRef temp; + + /* + * Make sure the user didn't just specify keychain: as the sslcert config. + * The passed certificate will have the keychain prefix stripped so in that + * case the string is expected to be empty here. + */ + if (strlen(common_name) == 0) + return errSecInvalidValue; + + query = CFDictionaryCreateMutable(NULL, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8); + + /* + * If we didn't get a Keychain passed, skip adding it to the dictionary + * thus prompting a search in the users default Keychain. + */ + if (keychains) + CFDictionaryAddValue(query, kSecMatchSearchList, keychains); + + CFDictionaryAddValue(query, kSecClass, kSecClassIdentity); + CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue); + CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll); + CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL)); + CFDictionaryAddValue(query, kSecAttrLabel, cert); + + /* + * Normally we could have used kSecMatchLimitOne in the above dictionary + * but since there are versions of macOS where the certificate matching on + * the label doesn't work, we need to request all and find the one we want. + * Copy all the results to a temp array and scan it for the certificate we + * are interested in. + */ + status = SecItemCopyMatching(query, (CFTypeRef *) &temp); + if (status == noErr) + { + OSStatus search_stat; + SecIdentityRef dummy; + int i; + + for (i = 0; i < CFArrayGetCount(temp); i++) + { + SecCertificateRef search_cert; + CFStringRef cn; + + dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i); + search_stat = SecIdentityCopyCertificate(dummy, &search_cert); + + if (search_stat == noErr && search_cert != NULL) + { + SecCertificateCopyCommonName(search_cert, &cn); + if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo) + { + CFRelease(cn); + CFRelease(search_cert); + *identity = (SecIdentityRef) CFRetain(dummy); + break; + } + + CFRelease(cn); + CFRelease(search_cert); + } + } + + CFRelease(temp); + } + + CFRelease(query); + CFRelease(cert); + + return status; +} + +/* + * load_certificate_file + * Extracts a certificate from a PEM file on the filesystem + * + * TODO: figure out better returncodes + */ +static OSStatus +load_certificate_file(char *name, CFArrayRef *cert_array) +{ + struct stat stat_buf; + int ret; + UInt8 *buf; + FILE *fd; + CFDataRef data; + SecExternalFormat format; + SecExternalItemType type; + CFStringRef path; + OSStatus status; + + /* + * If the configured ssl_cert_file filename is set to a non-existing + * file, assume it's referencing a Keychain label and attempt to load + * the certificate from the Keychain instead. + */ + ret = stat(name, &stat_buf); + if (ret != 0 && errno == ENOENT) + { + /* + * TODO: Do we want to search keychains for certificates serverside + * like we do clientside, or do we want to stick to a single way to + * configure the server? Since CRL files aren't supported outside + * keychains I guess we need to, but worth a discussion. + */ + return errSecInternalError; + } + else if (ret == 0 && S_ISREG(stat_buf.st_mode)) + { + if ((fd = AllocateFile(name, "r")) == NULL) + return errSecInternalError; + + buf = palloc(stat_buf.st_size); + ret = fread(buf, 1, stat_buf.st_size, fd); + FreeFile(fd); + + if (ret != stat_buf.st_size) + return errSecInternalError; + + type = kSecItemTypeCertificate; + format = kSecFormatPEMSequence; + path = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8); + data = CFDataCreate(NULL, buf, stat_buf.st_size); + + status = SecItemImport(data, path, &format, &type, 0, NULL, NULL, cert_array); + pfree(buf); + + return status; + } + + return errSecInternalError; +} + +/* + * XXX: Make this file loading and slurping only + */ +static int +load_dh_file(char *filename, char **buf) +{ + FILE *dh; + struct stat stat_buf; + int ret; + + /* + * Open the DH file and slurp the contents. If the file doesn't exist it's + * not an error, if it can't be opened it is however an error. + */ + ret = stat(filename, &stat_buf); + if (ret != 0 && errno == ENOENT) + return 0; + else if (ret == 0 && S_ISREG(stat_buf.st_mode)) + { + if ((dh = AllocateFile(filename, "r")) == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open DH parameters file \"%s\": %m", + filename))); + + *buf = palloc(stat_buf.st_size); + ret = fread(*buf, 1, stat_buf.st_size, dh); + FreeFile(dh); + + if (ret != stat_buf.st_size) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read DH parameters file \"%s\": %m", + filename))); + } + else + ereport(ERROR, + (errcode_for_file_access(), + errmsg("DH parameters file \"%s\" is not a regular file", + filename))); + + return ret; +} + +/* + * load_dh_params + * Load the specified DH params for the connection + * + * Secure Transport requires the DH params to be in DER format, but to be + * compatible with the OpenSSL code we also support PEM and convert to DER + * before loading. Conversion does rudimentary PEM parsing, if we miss the + * data being correct, the Secure Transport API will give an error anyways so + * we're just checking basic integrity. + * + * This function may scribble on the dh parameter so if that's required so stay + * intact in the caller, a copy should be sent. + */ +#define DH_HEADER "-----BEGIN DH PARAMETERS-----" +#define DH_FOOTER "-----END DH PARAMETERS-----" +static void +load_dh_params(char *dh, int len, bool is_pem, SSLContextRef ssl) +{ + OSStatus status; + char *der; + int der_len; + + Assert(dh); + + /* + * Convert PEM to DER + */ + if (is_pem) + { + char *head; + char *tail; + int pem_len = 0; + + if (strstr(dh, DH_HEADER) == NULL) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("Invalid PEM format for DH parameter, header missing"))); + + dh += strlen(DH_HEADER); + tail = strstr(dh, DH_FOOTER); + if (!tail) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("Invalid PEM format for DH parameter, footer missing"))); + *tail = '\0'; + + /* In order to PEM convert it we need to remove all newlines */ + head = dh; + tail = dh; + while (*head != '\0') + { + if (*head != '\n') + { + *tail++ = *head++; + pem_len++; + } + else + head++; + } + *tail = '\0'; + + der = palloc(pg_b64_dec_len(strlen(dh)) + 1); + der_len = pg_b64_decode(dh, strlen(dh), der); + der[der_len] = '\0'; + } + else + { + der = dh; + der_len = len; + } + + status = SSLSetDiffieHellmanParams(ssl, der, der_len); + if (status != noErr) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("unable to load DH parameters: %s", + pg_SSLerrmessage(status)))); +} + +/* + * This function should return the expected TLS Finished message information + * from the client, but there is no API for this in Secure Transport. As a + * result, channel binding is not supported by Secure Transport. + */ +char * +be_tls_get_peer_finished(Port *port, size_t *len) +{ + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("channel binding is not supported by this build"))); + return NULL; +} + +char * +be_tls_get_certificate_hash(Port *port, size_t *len) +{ + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("channel binding is not supported by this build"))); + return NULL; +} + +/* + * Close SSL connection. + */ +void +be_tls_close(Port *port) +{ + OSStatus ssl_status; + + if (!port->ssl) + return; + + ssl_status = SSLClose((SSLContextRef) port->ssl); + if (ssl_status != noErr) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("error in closing SSL connection: %s", + pg_SSLerrmessage(ssl_status)))); + + CFRelease((SSLContextRef) port->ssl); + + port->ssl = NULL; + port->ssl_in_use = false; +} + +/* + * be_tls_get_version + * Retrieve the protocol version of the current connection + */ +void +be_tls_get_version(Port *port, char *ptr, size_t len) +{ + OSStatus status; + SSLProtocol protocol; + + if (ptr == NULL || len == 0) + return; + + ptr[0] = '\0'; + + if (!(SSLContextRef) port->ssl) + return; + + status = SSLGetNegotiatedProtocolVersion((SSLContextRef) port->ssl, &protocol); + if (status == noErr) + { + switch (protocol) + { + case kTLSProtocol11: + strlcpy(ptr, "TLSv1.1", len); + break; + case kTLSProtocol12: + strlcpy(ptr, "TLSv1.2", len); + break; + default: + strlcpy(ptr, "unknown", len); + break; + } + } +} + +/* + * Read data from a secure connection. + */ +ssize_t +be_tls_read(Port *port, void *ptr, size_t len, int *waitfor) +{ + size_t n = 0; + ssize_t ret; + OSStatus read_status; + SSLContextRef ssl = (SSLContextRef) port->ssl; + + errno = 0; + + if (len <= 0) + return 0; + + read_status = SSLRead(ssl, ptr, len, &n); + switch (read_status) + { + case noErr: + ret = n; + break; + + /* Function is blocked, waiting for I/O */ + case errSSLWouldBlock: + if (port->ssl_buffered) + *waitfor = WL_SOCKET_WRITEABLE; + else + *waitfor = WL_SOCKET_READABLE; + + errno = EWOULDBLOCK; + if (n == 0) + ret = -1; + else + ret = n; + + break; + + case errSSLClosedGraceful: + ret = 0; + break; + + /* + * If the connection was closed for an unforeseen reason, return error + * and set errno such that the caller can raise an appropriate ereport + */ + case errSSLClosedNoNotify: + case errSSLClosedAbort: + ret = -1; + errno = ECONNRESET; + break; + + default: + ret = -1; + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("SSL error: %s", + pg_SSLerrmessage(read_status)))); + break; + } + + return ret; +} + +/* + * Write data to a secure connection. + */ +ssize_t +be_tls_write(Port *port, void *ptr, size_t len, int *waitfor) +{ + size_t n = 0; + OSStatus write_status; + + errno = 0; + + if (len == 0) + return 0; + + /* + * SSLWrite returns the number of bytes written in the 'n' argument. This + * however can be data either actually written to the socket, or buffered + * in the context. In the latter case SSLWrite will return errSSLWouldBlock + * and we need to call it with no new data (NULL) to drain the buffer on to + * the socket. We track the buffer in ssl_buffered and clear that when all + * data has been drained. + */ + if (port->ssl_buffered > 0) + { + write_status = SSLWrite((SSLContextRef) port->ssl, NULL, 0, &n); + + if (write_status == noErr) + { + n = port->ssl_buffered; + port->ssl_buffered = 0; + } + else if (write_status == errSSLWouldBlock || write_status == -1) + { + n = -1; + errno = EINTR; + } + else + { + n = -1; + errno = ECONNRESET; + } + } + else + { + write_status = SSLWrite((SSLContextRef) port->ssl, ptr, len, &n); + + switch (write_status) + { + case noErr: + break; + + /* + * The data was buffered in the context rather than written to the + * socket. Track this and repeatedly call SSLWrite to drain the + * buffer. See comment above. + */ + case errSSLWouldBlock: + port->ssl_buffered = len; + n = 0; +#ifdef EAGAIN + errno = EAGAIN; +#else + errno = EINTR; +#endif + break; + + /* Clean disconnections */ + case errSSLClosedNoNotify: + /* fall through */ + case errSSLClosedGraceful: + errno = ECONNRESET; + n = -1; + break; + + default: + errno = ECONNRESET; + n = -1; + break; + } + } + + return n; +} + +/* + * be_tls_get_cipher_bits + * Returns the number of bits in the encryption for the current cipher + * + * Note: In case of errors, this returns 0 to match the OpenSSL implementation. + * A NULL encryption will however also return 0 making it complicated to + * differentiate between the two. + */ +int +be_tls_get_cipher_bits(Port *port) +{ + OSStatus status; + SSLCipherSuite cipher; + + if (!(SSLContextRef) port->ssl) + return 0; + + status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher); + if (status != noErr) + return 0; + + return pg_SSLcipherbits(cipher); +} + +void +be_tls_get_peerdn_name(Port *port, char *ptr, size_t len) +{ + OSStatus status; + SecTrustRef trust; + SecCertificateRef cert; + CFStringRef dn_str; + + if (!ptr || len == 0) + return; + + ptr[0] = '\0'; + + if (!(SSLContextRef) port->ssl) + return; + + status = SSLCopyPeerTrust((SSLContextRef) port->ssl, &trust); + if (status == noErr) + { + /* + * TODO: copy the certificate parts with SecCertificateCopyValues and + * parse the OIDs to build up the DN + */ + cert = SecTrustGetCertificateAtIndex(trust, 0); + dn_str = SecCertificateCopyLongDescription(NULL, cert, NULL); + if (dn_str) + { + strlcpy(ptr, CFStringGetCStringPtr(dn_str, kCFStringEncodingASCII), len); + CFRelease(dn_str); + } + + CFRelease(trust); + } +} + +/* + * be_tls_get_cipher + * Return the negotiated ciphersuite for the current connection. + * + * Returns NULL in case we weren't able to either get the negotiated cipher, or + * translate it into a human readable string. + */ +void +be_tls_get_cipher(Port *port, char *ptr, size_t len) +{ + OSStatus status; + SSLCipherSuite cipher; + const char *cipher_name; + + if (!ptr || len == 0) + return; + + ptr[0] = '\0'; + + if (!(SSLContextRef) port->ssl) + return; + + status = SSLGetNegotiatedCipher((SSLContextRef) port->ssl, &cipher); + if (status != noErr) + return; + + cipher_name = pg_SSLciphername(cipher); + if (cipher_name != NULL) + strlcpy(ptr, cipher_name, len); +} + +/* + * be_tls_get_compression + * Retrieve and return whether compression is used for the current + * connection. + * + * Since Secure Transport doesn't support compression at all, always return + * false here. Ideally we should be able to tell the caller that the option + * isn't applicable rather than return false, but the current SSL support + * doesn't allow for that. + */ +bool +be_tls_get_compression(Port *port) +{ + return false; +} + +/* ------------------------------------------------------------ */ +/* Internal functions - Translation */ +/* ------------------------------------------------------------ */ + +/* + * pg_SSLerrmessage + * Create and return a human readable error message given + * the specified status code + * + * While only interesting to use for error cases, the function will return a + * translation for non-error statuses as well like noErr and errSecSuccess. + */ +static char * +pg_SSLerrmessage(OSStatus status) +{ + CFStringRef err_msg; + char *err_buf; + + /* + * While errSecUnknownFormat has been defined as -25257 at least since 10.8 + * Lion, there still is no translation for it in 10.11 El Capitan, so we + * maintain our own + */ + if (status == errSecUnknownFormat) + return pstrdup(_("The item you are trying to import has an unknown format.")); + + /* + * If the error is internal, and we have an error message in the internal + * buffer, then return that error and clear the internal buffer. + */ + if (status == errSecInternalError && internal_err[0]) + { + err_buf = pstrdup(internal_err); + memset(internal_err, '\0', ERR_MSG_LEN); + } + else + { + err_msg = SecCopyErrorMessageString(status, NULL); + + if (err_msg) + { + err_buf = pstrdup(CFStringGetCStringPtr(err_msg, + kCFStringEncodingUTF8)); + CFRelease(err_msg); + } + else + err_buf = pstrdup(_("unknown SSL error")); + } + + return err_buf; +} + +/* ------------------------------------------------------------ */ +/* Internal functions - Socket IO */ +/* ------------------------------------------------------------ */ + +/* + * pg_SSLSocketRead + * + * Callback for reading data from the connection. When entering the function, + * len is set to the number of bytes requested. Upon leaving, len should be + * overwritten with the actual number of bytes read. + */ +static OSStatus +pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len) +{ + OSStatus status; + int res; + + res = secure_raw_read((Port *) conn, data, *len); + + if (res < 0) + { + switch (errno) + { +#ifdef EAGAIN + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) + case EWOULDBLOCK: +#endif + case EINTR: + status = errSSLWouldBlock; + break; + case ENOENT: + status = errSSLClosedGraceful; + break; + + default: + status = errSSLClosedAbort; + break; + } + + *len = 0; + } + else + { + status = noErr; + *len = res; + } + + return status; +} + +static OSStatus +pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len) +{ + OSStatus status; + int res; + Port *port = (Port *) conn; + + res = secure_raw_write(port, data, *len); + + if (res < 0) + { + switch (errno) + { +#ifdef EAGAIN + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) + case EWOULDBLOCK: +#endif + case EINTR: + status = errSSLWouldBlock; + break; + + default: + status = errSSLClosedAbort; + break; + } + + *len = res; + } + else + { + status = noErr; + *len = res; + } + + return status; +} diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 76c0a9e39b..5c292150aa 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -45,6 +45,9 @@ char *ssl_key_file; char *ssl_ca_file; char *ssl_crl_file; char *ssl_dh_params_file; +#ifdef USE_SECURETRANSPORT +char *ssl_keychain_file; +#endif #ifdef USE_SSL bool ssl_loaded_verify_locations = false; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 5884fa905e..a694d218b0 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -501,6 +501,7 @@ static char *locale_ctype; static char *server_encoding_string; static char *server_version_string; static int server_version_num; +static char *ssl_library_string; static char *timezone_string; static char *log_timezone_string; static char *timezone_abbreviations_string; @@ -3324,6 +3325,18 @@ static struct config_string ConfigureNamesString[] = NULL, NULL, NULL }, + { + /* Can't be set in postgresql.conf */ + {"ssl_library", PGC_INTERNAL, PRESET_OPTIONS, + gettext_noop("Shows the SSL library used."), + NULL, + GUC_REPORT | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &ssl_library_string, + SSL_LIBRARY, + NULL, NULL, NULL + }, + { /* Not for general use --- used by SET ROLE */ {"role", PGC_USERSET, UNGROUPED, @@ -3571,6 +3584,18 @@ static struct config_string ConfigureNamesString[] = NULL, NULL, NULL }, +#ifdef USE_SECURETRANSPORT + { + {"ssl_keychain_file", PGC_SIGHUP, CONN_AUTH_SSL, + gettext_noop("Location of the Keychain file."), + NULL + }, + &ssl_keychain_file, + "", + NULL, NULL, NULL + }, +#endif + { {"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR, gettext_noop("Writes temporary statistics files to the specified directory."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index abffde6b2b..4f65250ed1 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -104,6 +104,7 @@ #ssl_prefer_server_ciphers = on #ssl_ecdh_curve = 'prime256v1' #ssl_dh_params_file = '' +#ssl_keychain_file = '' #------------------------------------------------------------------------------ diff --git a/src/include/common/securetransport.h b/src/include/common/securetransport.h new file mode 100644 index 0000000000..d3a38e2a5c --- /dev/null +++ b/src/include/common/securetransport.h @@ -0,0 +1,514 @@ +/*------------------------------------------------------------------------- + * + * securetransport_common.h + * Apple Secure Transport support + * + * These definitions are used by both frontend and backend code. + * + * Copyright (c) 2017, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/include/common/securetransport.h + * + *------------------------------------------------------------------------- + */ +#ifndef SECURETRANSPORT_H +#define SECURETRANSPORT_H + +#include + +/* + * pg_SSLciphername + * + * Translate a SSLCipherSuite code into a string literal suitable for printing + * in log/informational messages to the user. Since this implementation of the + * Secure Transport lib doesn't support SSLv2/v3 these ciphernames are omitted. + * + * The SSLCipherSuite enum is defined in Security/CipherSuite.h + * + * This only removes the TLS_ portion of the SSLCipherSuite enum label for the + * ciphers to match what most Secure Transport implementations seem to be doing + */ +static const char * +pg_SSLciphername(SSLCipherSuite cipher) +{ + switch (cipher) + { + case TLS_NULL_WITH_NULL_NULL: + return "NULL"; + + /* TLS addenda using AES, per RFC 3268 */ + case TLS_RSA_WITH_AES_128_CBC_SHA: + return "RSA_WITH_AES_128_CBC_SHA"; + case TLS_DH_DSS_WITH_AES_128_CBC_SHA: + return "DH_DSS_WITH_AES_128_CBC_SHA"; + case TLS_DH_RSA_WITH_AES_128_CBC_SHA: + return "DH_RSA_WITH_AES_128_CBC_SHA"; + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + return "DHE_DSS_WITH_AES_128_CBC_SHA"; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + return "DHE_RSA_WITH_AES_128_CBC_SHA"; + case TLS_DH_anon_WITH_AES_128_CBC_SHA: + return "DH_anon_WITH_AES_128_CBC_SHA"; + case TLS_RSA_WITH_AES_256_CBC_SHA: + return "RSA_WITH_AES_256_CBC_SHA"; + case TLS_DH_DSS_WITH_AES_256_CBC_SHA: + return "DH_DSS_WITH_AES_256_CBC_SHA"; + case TLS_DH_RSA_WITH_AES_256_CBC_SHA: + return "DH_RSA_WITH_AES_256_CBC_SHA"; + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + return "DHE_DSS_WITH_AES_256_CBC_SHA"; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + return "DHE_RSA_WITH_AES_256_CBC_SHA"; + case TLS_DH_anon_WITH_AES_256_CBC_SHA: + return "DH_anon_WITH_AES_256_CBC_SHA"; + + /* ECDSA addenda, RFC 4492 */ + case TLS_ECDH_ECDSA_WITH_NULL_SHA: + return "ECDH_ECDSA_WITH_NULL_SHA"; + case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + return "ECDH_ECDSA_WITH_RC4_128_SHA"; + case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + return "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + return "ECDH_ECDSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + return "ECDH_ECDSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_NULL_SHA: + return "ECDHE_ECDSA_WITH_NULL_SHA"; + case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + return "ECDHE_ECDSA_WITH_RC4_128_SHA"; + case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + return "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDH_RSA_WITH_NULL_SHA: + return "ECDH_RSA_WITH_NULL_SHA"; + case TLS_ECDH_RSA_WITH_RC4_128_SHA: + return "ECDH_RSA_WITH_RC4_128_SHA"; + case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + return "ECDH_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + return "ECDH_RSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + return "ECDH_RSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_NULL_SHA: + return "ECDHE_RSA_WITH_NULL_SHA"; + case TLS_ECDHE_RSA_WITH_RC4_128_SHA: + return "ECDHE_RSA_WITH_RC4_128_SHA"; + case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + return "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + return "ECDHE_RSA_WITH_AES_128_CBC_SHA"; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + return "ECDHE_RSA_WITH_AES_256_CBC_SHA"; + case TLS_ECDH_anon_WITH_NULL_SHA: + return "ECDH_anon_WITH_NULL_SHA"; + case TLS_ECDH_anon_WITH_RC4_128_SHA: + return "ECDH_anon_WITH_RC4_128_SHA"; + case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: + return "ECDH_anon_WITH_3DES_EDE_CBC_SHA"; + case TLS_ECDH_anon_WITH_AES_128_CBC_SHA: + return "ECDH_anon_WITH_AES_128_CBC_SHA"; + case TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + return "ECDH_anon_WITH_AES_256_CBC_SHA"; + + /* Server provided RSA certificate for key exchange. */ + case TLS_RSA_WITH_NULL_MD5: + return "RSA_WITH_NULL_MD5"; + case TLS_RSA_WITH_NULL_SHA: + return "RSA_WITH_NULL_SHA"; + case TLS_RSA_WITH_RC4_128_MD5: + return "RSA_WITH_RC4_128_MD5"; + case TLS_RSA_WITH_RC4_128_SHA: + return "RSA_WITH_RC4_128_SHA"; + case TLS_RSA_WITH_3DES_EDE_CBC_SHA: + return "RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_RSA_WITH_NULL_SHA256: + return "RSA_WITH_NULL_SHA256"; + case TLS_RSA_WITH_AES_128_CBC_SHA256: + return "RSA_WITH_AES_128_CBC_SHA256"; + case TLS_RSA_WITH_AES_256_CBC_SHA256: + return "RSA_WITH_AES_256_CBC_SHA256"; + + /* + * Server-authenticated (and optionally client-authenticated) + * Diffie-Hellman. + */ + case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + return "DH_DSS_WITH_3DES_EDE_CBC_SHA"; + case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + return "DH_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + return "DHE_DSS_WITH_3DES_EDE_CBC_SHA"; + case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + return "DHE_RSA_WITH_3DES_EDE_CBC_SHA"; + case TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + return "DH_DSS_WITH_AES_128_CBC_SHA256"; + case TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + return "DH_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + return "DHE_DSS_WITH_AES_128_CBC_SHA256"; + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + return "DHE_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + return "DH_DSS_WITH_AES_256_CBC_SHA256"; + case TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + return "DH_RSA_WITH_AES_256_CBC_SHA256"; + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + return "DHE_DSS_WITH_AES_256_CBC_SHA256"; + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + return "DHE_RSA_WITH_AES_256_CBC_SHA256"; + + /* Completely anonymous Diffie-Hellman */ + case TLS_DH_anon_WITH_RC4_128_MD5: + return "DH_anon_WITH_RC4_128_MD5"; + case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA: + return "DH_anon_WITH_3DES_EDE_CBC_SHA"; + case TLS_DH_anon_WITH_AES_128_CBC_SHA256: + return "DH_anon_WITH_AES_128_CBC_SHA256"; + case TLS_DH_anon_WITH_AES_256_CBC_SHA256: + return "DH_anon_WITH_AES_256_CBC_SHA256"; + + /* Addendum from RFC 4279, TLS PSK */ + case TLS_PSK_WITH_RC4_128_SHA: + return "PSK_WITH_RC4_128_SHA"; + case TLS_PSK_WITH_3DES_EDE_CBC_SHA: + return "PSK_WITH_3DES_EDE_CBC_SHA"; + case TLS_PSK_WITH_AES_128_CBC_SHA: + return "PSK_WITH_AES_128_CBC_SHA"; + case TLS_PSK_WITH_AES_256_CBC_SHA: + return "PSK_WITH_AES_256_CBC_SHA"; + case TLS_DHE_PSK_WITH_RC4_128_SHA: + return "DHE_PSK_WITH_RC4_128_SHA"; + case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + return "DHE_PSK_WITH_3DES_EDE_CBC_SHA"; + case TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + return "DHE_PSK_WITH_AES_128_CBC_SHA"; + case TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + return "DHE_PSK_WITH_AES_256_CBC_SHA"; + case TLS_RSA_PSK_WITH_RC4_128_SHA: + return "RSA_PSK_WITH_RC4_128_SHA"; + case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + return "RSA_PSK_WITH_3DES_EDE_CBC_SHA"; + case TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + return "RSA_PSK_WITH_AES_128_CBC_SHA"; + case TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + return "RSA_PSK_WITH_AES_256_CBC_SHA"; + + /* RFC 4785, Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */ + case TLS_PSK_WITH_NULL_SHA: + return "PSK_WITH_NULL_SHA"; + case TLS_DHE_PSK_WITH_NULL_SHA: + return "DHE_PSK_WITH_NULL_SHA"; + case TLS_RSA_PSK_WITH_NULL_SHA: + return "RSA_PSK_WITH_NULL_SHA"; + + /* + * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites + * for TLS. + */ + case TLS_RSA_WITH_AES_128_GCM_SHA256: + return "RSA_WITH_AES_128_GCM_SHA256"; + case TLS_RSA_WITH_AES_256_GCM_SHA384: + return "RSA_WITH_AES_256_GCM_SHA384"; + case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + return "DHE_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + return "DHE_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + return "DH_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + return "DH_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + return "DHE_DSS_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + return "DHE_DSS_WITH_AES_256_GCM_SHA384"; + case TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + return "DH_DSS_WITH_AES_128_GCM_SHA256"; + case TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + return "DH_DSS_WITH_AES_256_GCM_SHA384"; + case TLS_DH_anon_WITH_AES_128_GCM_SHA256: + return "DH_anon_WITH_AES_128_GCM_SHA256"; + case TLS_DH_anon_WITH_AES_256_GCM_SHA384: + return "DH_anon_WITH_AES_256_GCM_SHA384"; + + /* RFC 5487 - PSK with SHA-256/384 and AES GCM */ + case TLS_PSK_WITH_AES_128_GCM_SHA256: + return "PSK_WITH_AES_128_GCM_SHA256"; + case TLS_PSK_WITH_AES_256_GCM_SHA384: + return "PSK_WITH_AES_256_GCM_SHA384"; + case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + return "DHE_PSK_WITH_AES_128_GCM_SHA256"; + case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + return "DHE_PSK_WITH_AES_256_GCM_SHA384"; + case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + return "RSA_PSK_WITH_AES_128_GCM_SHA256"; + case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + return "RSA_PSK_WITH_AES_256_GCM_SHA384"; + case TLS_PSK_WITH_AES_128_CBC_SHA256: + return "PSK_WITH_AES_128_CBC_SHA256"; + case TLS_PSK_WITH_AES_256_CBC_SHA384: + return "PSK_WITH_AES_256_CBC_SHA384"; + case TLS_PSK_WITH_NULL_SHA256: + return "PSK_WITH_NULL_SHA256"; + case TLS_PSK_WITH_NULL_SHA384: + return "PSK_WITH_NULL_SHA384"; + case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + return "DHE_PSK_WITH_AES_128_CBC_SHA256"; + case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + return "DHE_PSK_WITH_AES_256_CBC_SHA384"; + case TLS_DHE_PSK_WITH_NULL_SHA256: + return "DHE_PSK_WITH_NULL_SHA256"; + case TLS_DHE_PSK_WITH_NULL_SHA384: + return "DHE_PSK_WITH_NULL_SHA384"; + case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + return "RSA_PSK_WITH_AES_128_CBC_SHA256"; + case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + return "RSA_PSK_WITH_AES_256_CBC_SHA384"; + case TLS_RSA_PSK_WITH_NULL_SHA256: + return "RSA_PSK_WITH_NULL_SHA256"; + case TLS_RSA_PSK_WITH_NULL_SHA384: + return "RSA_PSK_WITH_NULL_SHA384"; + + /* + * Addenda from RFC 5289, Elliptic Curve Cipher Suites with + * HMAC SHA-256/384. + */ + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + return "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + return "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + return "ECDH_ECDSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + return "ECDH_ECDSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + return "ECDHE_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + return "ECDHE_RSA_WITH_AES_256_CBC_SHA384"; + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + return "ECDH_RSA_WITH_AES_128_CBC_SHA256"; + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + return "ECDH_RSA_WITH_AES_256_CBC_SHA384"; + + /* + * Addenda from RFC 5289, Elliptic Curve Cipher Suites with + * SHA-256/384 and AES Galois Counter Mode (GCM) + */ + case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + return "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + return "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + return "ECDH_ECDSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + return "ECDH_ECDSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + return "ECDHE_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + return "ECDHE_RSA_WITH_AES_256_GCM_SHA384"; + case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + return "ECDH_RSA_WITH_AES_128_GCM_SHA256"; + case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + return "ECDH_RSA_WITH_AES_256_GCM_SHA384"; + + default: + break; + } + + return NULL; +} + +#ifndef FRONTEND +/* + * pg_SSLcipherbits + * + * Return the number of bits in the encryption for the specified cipher. + * Ciphers with NULL encryption are omitted from the switch statement. This + * function is currently only used in the libpq backend. + */ +static int +pg_SSLcipherbits(SSLCipherSuite cipher) +{ + switch (cipher) + { + /* TLS addenda using AES, per RFC 3268 */ + case TLS_RSA_WITH_AES_128_CBC_SHA: + case TLS_DH_DSS_WITH_AES_128_CBC_SHA: + case TLS_DH_RSA_WITH_AES_128_CBC_SHA: + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case TLS_DH_anon_WITH_AES_128_CBC_SHA: + return 128; + + case TLS_RSA_WITH_AES_256_CBC_SHA: + case TLS_DH_DSS_WITH_AES_256_CBC_SHA: + case TLS_DH_RSA_WITH_AES_256_CBC_SHA: + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + case TLS_DH_anon_WITH_AES_256_CBC_SHA: + return 256; + + /* ECDSA addenda, RFC 4492 */ + case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: + case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + return 112; + + case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case TLS_ECDH_anon_WITH_AES_128_CBC_SHA: + case TLS_ECDH_anon_WITH_RC4_128_SHA: + case TLS_ECDHE_RSA_WITH_RC4_128_SHA: + case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: + case TLS_ECDH_RSA_WITH_RC4_128_SHA: + return 128; + + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + case TLS_ECDH_anon_WITH_AES_256_CBC_SHA: + return 256; + + /* Server provided RSA certificate for key exchange. */ + case TLS_RSA_WITH_3DES_EDE_CBC_SHA: + return 112; + case TLS_RSA_WITH_RC4_128_MD5: + case TLS_RSA_WITH_RC4_128_SHA: + case TLS_RSA_WITH_AES_128_CBC_SHA256: + return 128; + case TLS_RSA_WITH_AES_256_CBC_SHA256: + return 256; + + /* + * Server-authenticated (and optionally client-authenticated) + * Diffie-Hellman. + */ + case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + return 112; + case TLS_DH_DSS_WITH_AES_128_CBC_SHA256: + case TLS_DH_RSA_WITH_AES_128_CBC_SHA256: + case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: + case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: + return 128; + case TLS_DH_DSS_WITH_AES_256_CBC_SHA256: + case TLS_DH_RSA_WITH_AES_256_CBC_SHA256: + case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: + case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: + return 256; + + /* Completely anonymous Diffie-Hellman */ + case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA: + return 112; + case TLS_DH_anon_WITH_RC4_128_MD5: + case TLS_DH_anon_WITH_AES_128_CBC_SHA256: + return 128; + case TLS_DH_anon_WITH_AES_256_CBC_SHA256: + return 256; + + /* Addendum from RFC 4279, TLS PSK */ + case TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + return 112; + case TLS_PSK_WITH_RC4_128_SHA: + case TLS_DHE_PSK_WITH_RC4_128_SHA: + case TLS_PSK_WITH_AES_128_CBC_SHA: + case TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case TLS_RSA_PSK_WITH_RC4_128_SHA: + case TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + return 128; + case TLS_PSK_WITH_AES_256_CBC_SHA: + case TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + case TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + return 256; + + /* + * Addenda from RFC 5288, AES Galois Counter Mode (GCM) Cipher Suites + * for TLS. + */ + case TLS_RSA_WITH_AES_128_GCM_SHA256: + case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: + case TLS_DH_RSA_WITH_AES_128_GCM_SHA256: + case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: + case TLS_DH_DSS_WITH_AES_128_GCM_SHA256: + case TLS_DH_anon_WITH_AES_128_GCM_SHA256: + return 128; + + case TLS_RSA_WITH_AES_256_GCM_SHA384: + case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: + case TLS_DH_RSA_WITH_AES_256_GCM_SHA384: + case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: + case TLS_DH_DSS_WITH_AES_256_GCM_SHA384: + case TLS_DH_anon_WITH_AES_256_GCM_SHA384: + return 256; + + /* RFC 5487 - PSK with SHA-256/384 and AES GCM */ + + + case TLS_PSK_WITH_AES_128_GCM_SHA256: + case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: + case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: + case TLS_PSK_WITH_AES_128_CBC_SHA256: + case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: + case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: + return 128; + case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: + case TLS_PSK_WITH_AES_256_GCM_SHA384: + case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: + case TLS_PSK_WITH_AES_256_CBC_SHA384: + case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: + case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: + return 256; + + /* + * Addenda from RFC 5289, Elliptic Curve Cipher Suites with + * HMAC SHA-256/384. + */ + case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: + case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: + case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: + case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: + return 128; + case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: + case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: + case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: + case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: + return 256; + + /* + * Addenda from RFC 5289, Elliptic Curve Cipher Suites with + * SHA-256/384 and AES Galois Counter Mode (GCM) + */ + case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: + case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: + case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: + case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: + return 128; + case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: + case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: + case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: + return 256; + + default: + break; + } + + return 0; +} +#endif + +#endif /* SECURETRANSPORT_H */ diff --git a/src/include/common/sha2.h b/src/include/common/sha2.h index f3fd0d0d28..6d35f47864 100644 --- a/src/include/common/sha2.h +++ b/src/include/common/sha2.h @@ -50,7 +50,7 @@ #ifndef _PG_SHA2_H_ #define _PG_SHA2_H_ -#ifdef USE_SSL +#if defined(USE_SSL) && defined(USE_OPENSSL) #include #endif @@ -69,7 +69,7 @@ #define PG_SHA512_DIGEST_STRING_LENGTH (PG_SHA512_DIGEST_LENGTH * 2 + 1) /* Context Structures for SHA-1/224/256/384/512 */ -#ifdef USE_SSL +#if defined(USE_SSL) && defined(USE_OPENSSL) typedef SHA256_CTX pg_sha256_ctx; typedef SHA512_CTX pg_sha512_ctx; typedef SHA256_CTX pg_sha224_ctx; diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 584f794b9e..800dc56a61 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -22,6 +22,14 @@ #ifdef USE_OPENSSL #include #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,12 +191,15 @@ typedef struct Port bool peer_cert_valid; /* - * OpenSSL structures. (Keep these last so that the locations of other - * fields are the same whether or not you build with OpenSSL.) + * SSL library structures. (Keep these last so that the locations of + * other fields are the same whether or not you build with SSL.) */ #ifdef USE_OPENSSL SSL *ssl; X509 *peer; +#elif USE_SECURETRANSPORT + void *ssl; + int ssl_buffered; #endif } Port; @@ -214,7 +225,7 @@ CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\ /* * These functions are implemented by the glue code specific to each - * SSL implementation (e.g. be-secure-openssl.c) + * SSL implementation (e.g. be-secure-.c) */ /* diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 255222acd7..9cc91323f6 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -80,6 +80,9 @@ extern char *ssl_key_file; extern char *ssl_ca_file; extern char *ssl_crl_file; extern char *ssl_dh_params_file; +#ifdef USE_SECURETRANSPORT +extern char *ssl_keychain_file; +#endif extern int secure_initialize(bool isServerStart); extern bool secure_loaded_verify_locations(void); diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in index f98f773ff0..a1d71a6473 100644 --- a/src/include/pg_config.h.in +++ b/src/include/pg_config.h.in @@ -856,6 +856,9 @@ /* Define to use OpenSSL for random number generation */ #undef USE_OPENSSL_RANDOM +/* Define to build with Apple Secure Transport support. (--with-securetransport) */ +#undef USE_SECURETRANSPORT + /* Define to 1 to build with PAM support. (--with-pam) */ #undef USE_PAM diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h index b309395f11..5469473d72 100644 --- a/src/include/pg_config_manual.h +++ b/src/include/pg_config_manual.h @@ -162,11 +162,17 @@ /* * USE_SSL code should be compiled only when compiling with an SSL - * implementation. (Currently, only OpenSSL is supported, but we might add - * more implementations in the future.) + * implementation. */ -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) || defined(USE_SECURETRANSPORT) #define USE_SSL +#if defined(USE_OPENSSL) +#define SSL_LIBRARY "OpenSSL" +#elif defined(USE_SECURETRANSPORT) +#define SSL_LIBRARY "Secure Transport" +#endif +#else +#define SSL_LIBRARY "None" #endif /* diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile index 0bf1e7ef04..fe8662d1b6 100644 --- a/src/interfaces/libpq/Makefile +++ b/src/interfaces/libpq/Makefile @@ -57,6 +57,11 @@ else OBJS += sha2.o endif +ifeq ($(with_securetransport), yes) +OBJS += fe-secure-securetransport.o +override CFLAGS += -framework Security -framework CoreFoundation -fconstant-cfstrings +endif + ifeq ($(PORTNAME), cygwin) override shlib = cyg$(NAME)$(DLSUFFIX) endif @@ -112,7 +117,6 @@ ip.c md5.c base64.c scram-common.c sha2.c sha2_openssl.c saslprep.c unicode_norm encnames.c wchar.c: % : $(backend_src)/utils/mb/% rm -f $@ && $(LN_S) $< . - distprep: libpq-dist.rc libpq.rc libpq-dist.rc: libpq.rc.in diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 77eebb0ba1..ede910f250 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -330,6 +330,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */ offsetof(struct pg_conn, target_session_attrs)}, +#if defined(USE_SECURETRANSPORT) + {"keychain", "PGKEYCHAIN", NULL, NULL, + "Keychain", "", 64, + offsetof(struct pg_conn, keychain)}, +#endif + /* Terminating entry --- MUST BE LAST */ {NULL, NULL, NULL, NULL, NULL, NULL, 0} diff --git a/src/interfaces/libpq/fe-secure-securetransport.c b/src/interfaces/libpq/fe-secure-securetransport.c new file mode 100644 index 0000000000..e8e1d68bcf --- /dev/null +++ b/src/interfaces/libpq/fe-secure-securetransport.c @@ -0,0 +1,1499 @@ +/*------------------------------------------------------------------------- + * + * fe-secure-securetransport.c + * Apple Secure Transport support + * + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/interfaces/libpq/fe-secure-securetransport.c + * + * NOTES + * Unlike the OpenSSL support there is no shared state between connections + * so there is no special handling for ENABLE_THREAD_SAFETY. + * + * There are a lot of functions (mostly the Core Foundation CF* family) that + * pass NULL as the first parameter. This is because they allow for a custom + * allocator to be used for memory allocations which is referenced with the + * first parameter. We are using the standard allocator however, and that + * means passing NULL all the time. Defining a suitably named preprocessor + * macro would aid readiblitity in case this is confusing (and/or ugly). + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include +#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; +} + +/* + */ +char * +pgtls_get_finished(PGconn *conn, size_t *len) +{ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("channel binding type \"%s\" is not supported by this build\n"), + conn->scram_channel_binding); + return NULL; +} +char * +pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len) +{ + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("channel binding type \"%s\" is not supported by this build\n"), + conn->scram_channel_binding); + return NULL; +} + +/* + * Initialize SSL system, in particular creating the SSL_context object + * that will be shared by all SSL-using connections in this process. + * + * In threadsafe mode, this includes setting up libcrypto callback functions + * to do thread locking. + * + * If the caller has told us (through PQinitOpenSSL) that he's taking care + * of libcrypto, we expect that callbacks are already set, and won't try to + * override it. + * + * The conn parameter is only used to be able to pass back an error + * message - no connection-local setup is made here. + * + * Returns 0 if OK, -1 on failure (with a message in conn->errorMessage). + */ +int +pgtls_init(PGconn *conn) +{ + conn->ssl_buffered = 0; + conn->ssl_in_use = false; + + return 0; +} + +/* + * pgtls_close + * Close SSL connection. + * + * This function must cope with connections in all states of disrepair since + * it will be called from pgtls_open_client to clean up any potentially used + * resources in case it breaks halfway. + */ +void +pgtls_close(PGconn *conn) +{ + if (!conn->ssl) + return; + + if (conn->st_rootcert != NULL) + CFRelease((CFArrayRef) conn->st_rootcert); + + SSLClose(conn->ssl); + CFRelease(conn->ssl); + + /* TODO: Release any certificates loaded */ + + conn->ssl = NULL; + conn->ssl_in_use = false; +} + +/* + * The amount of read bytes is returned in the len variable + */ +static OSStatus +pg_SSLSocketRead(SSLConnectionRef conn, void *data, size_t *len) +{ + OSStatus status = noErr; + int res; + + res = pqsecure_raw_read((PGconn *) conn, data, *len); + + if (res < 0) + { + switch (SOCK_ERRNO) + { + case ENOENT: + status = errSSLClosedGraceful; + break; + +#ifdef EAGAIN + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) + case EWOULDBLOCK: +#endif + case EINTR: + status = errSSLWouldBlock; + break; + } + + *len = 0; + } + else + *len = res; + + return status; +} + +static OSStatus +pg_SSLSocketWrite(SSLConnectionRef conn, const void *data, size_t *len) +{ + OSStatus status = noErr; + int res; + + res = pqsecure_raw_write((PGconn *) conn, data, *len); + + if (res < 0) + { + switch (SOCK_ERRNO) + { +#ifdef EAGAIN + case EAGAIN: +#endif +#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) + case EWOULDBLOCK: +#endif + case EINTR: + status = errSSLWouldBlock; + break; + + default: + break; + } + } + + *len = res; + + return status; +} + +/* + * import_identity_keychain + * Import the identity for the specified certificate from a Keychain + * + * Queries the specified Keychain, or the default unless not defined, for a + * identity with a certificate matching the passed certificate reference. + * Keychains are searched by creating a dictionary of key/value pairs with the + * search criteria and then asking for a copy of the matching entry/entries to + * the search criteria. + */ +static OSStatus +import_identity_keychain(const char *common_name, SecIdentityRef *identity, + CFArrayRef keychains) +{ + OSStatus status = errSecItemNotFound; + CFMutableDictionaryRef query; + CFStringRef cert; + CFArrayRef temp; + + /* + * Make sure the user didn't just specify keychain: as the sslcert config. + * The passed certificate will have the keychain prefix stripped so in that + * case the string is expected to be empty here. + */ + if (strlen(common_name) == 0) + return errSecInvalidValue; + + query = CFDictionaryCreateMutable(NULL, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8); + + /* + * If we didn't get a Keychain passed, skip adding it to the dictionary + * thus prompting a search in the users default Keychain. + */ + if (keychains) + CFDictionaryAddValue(query, kSecMatchSearchList, keychains); + + CFDictionaryAddValue(query, kSecClass, kSecClassIdentity); + CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue); + CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll); + CFDictionaryAddValue(query, kSecMatchPolicy, SecPolicyCreateSSL(true, NULL)); + CFDictionaryAddValue(query, kSecAttrLabel, cert); + + /* + * Normally we could have used kSecMatchLimitOne in the above dictionary + * but since there are versions of macOS where the certificate matching on + * the label doesn't work, we need to request all and find the one we want. + * Copy all the results to a temp array and scan it for the certificate we + * are interested in. + */ + status = SecItemCopyMatching(query, (CFTypeRef *) &temp); + if (status == noErr) + { + OSStatus search_stat; + SecIdentityRef dummy; + int i; + + for (i = 0; i < CFArrayGetCount(temp); i++) + { + SecCertificateRef search_cert; + CFStringRef cn; + + dummy = (SecIdentityRef) CFArrayGetValueAtIndex(temp, i); + search_stat = SecIdentityCopyCertificate(dummy, &search_cert); + + if (search_stat == noErr && search_cert != NULL) + { + SecCertificateCopyCommonName(search_cert, &cn); + if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo) + { + CFRelease(cn); + CFRelease(search_cert); + *identity = (SecIdentityRef) CFRetain(dummy); + break; + } + + CFRelease(cn); + CFRelease(search_cert); + } + } + + CFRelease(temp); + } + + CFRelease(query); + CFRelease(cert); + + return status; +} + +static OSStatus +import_certificate_keychain(const char *common_name, SecCertificateRef *certificate, + CFArrayRef keychains, char *hostname) +{ + OSStatus status = errSecItemNotFound; + CFMutableDictionaryRef query; + CFStringRef cert; + CFStringRef host = NULL; + CFArrayRef temp; + SecPolicyRef ssl_policy; + int i; + + /* + * Make sure the user didn't just specify the keychain prefix as the + * certificate config. The passed certificate will have the keychain + * prefix stripped so in that case the string is expected to be empty. + */ + if (strlen(common_name) == 0) + return errSecInvalidValue; + + query = CFDictionaryCreateMutable(NULL, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + cert = CFStringCreateWithCString(NULL, common_name, kCFStringEncodingUTF8); + CFDictionaryAddValue(query, kSecAttrLabel, cert); + + CFDictionaryAddValue(query, kSecClass, kSecClassCertificate); + CFDictionaryAddValue(query, kSecReturnRef, kCFBooleanTrue); + CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll); + + /* + * If we didn't get a set of Keychains passed, skip adding it to the + * dictionary thus prompting a search in the users default Keychain. + */ + if (keychains) + CFDictionaryAddValue(query, kSecMatchSearchList, keychains); + + /* + * Specifying a hostname requires it to match the hostname in the leaf + * certificate. + */ + if (hostname) + host = CFStringCreateWithCString(NULL, hostname, kCFStringEncodingUTF8); + ssl_policy = SecPolicyCreateSSL(true, host); + CFDictionaryAddValue(query, kSecMatchPolicy, ssl_policy); + + /* + * Normally we could have used kSecMatchLimitOne in the above dictionary + * but since there are versions of macOS where the certificate matching on + * the label has been reported to not work (revisions of 10.12), we request + * all and find the one we want. Copy all the results to a temp array and + * scan it for the certificate we are interested in. + */ + status = SecItemCopyMatching(query, (CFTypeRef *) &temp); + if (status == noErr) + { + for (i = 0; i < CFArrayGetCount(temp); i++) + { + SecCertificateRef search_cert; + CFStringRef cn; + + search_cert = (SecCertificateRef) CFArrayGetValueAtIndex(temp, i); + + if (search_cert != NULL) + { + SecCertificateCopyCommonName(search_cert, &cn); + if (CFStringCompare(cn, cert, 0) == kCFCompareEqualTo) + { + CFRelease(cn); + *certificate = (SecCertificateRef) CFRetain(search_cert); + break; + } + + CFRelease(cn); + CFRelease(search_cert); + } + } + + CFRelease(temp); + } + + CFRelease(ssl_policy); + CFRelease(query); + CFRelease(cert); + if (host) + CFRelease(host); + + return status; +} + +static OSStatus +import_pem(const char *path, char *passphrase, CFArrayRef *certificate) +{ + CFDataRef data_ref; + CFStringRef file_type; + SecExternalItemType item_type; + SecItemImportExportKeyParameters params; + SecExternalFormat format; + FILE *fp; + UInt8 *certdata; + struct stat buf; + + if (!path || strlen(path) == 0) + return errSecInvalidValue; + + if (stat(path, &buf) != 0) + { + if (errno == ENOENT || errno == ENOTDIR) + return -1; + + return errSecInvalidValue; + } + + fp = fopen(path, "r"); + if (!fp) + return errSecInvalidValue; + + certdata = malloc(buf.st_size); + if (!certdata) + { + fclose(fp); + return errSecAllocate; + } + + if (fread(certdata, 1, buf.st_size, fp) != buf.st_size) + { + fclose(fp); + free(certdata); + return errSSLBadCert; + } + fclose(fp); + + data_ref = CFDataCreate(NULL, certdata, buf.st_size); + free(certdata); + + memset(¶ms, 0, sizeof(SecItemImportExportKeyParameters)); + params.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; + /* Set OS default access control on the imported key */ + params.flags = kSecKeyNoAccessControl; + if (passphrase) + params.passphrase = CFStringCreateWithCString(NULL, passphrase, + kCFStringEncodingUTF8); + + /* + * Though we explicitly say this is a PEM file, Secure Transport will + * consider that a mere hint. Providing a file ending and a file format is + * what we can do to assist. + */ + file_type = CFSTR(".pem"); + if (!file_type) + return errSecAllocate; + + format = kSecFormatPEMSequence; + item_type = kSecItemTypeCertificate; + + return SecItemImport(data_ref, file_type, &format, &item_type, + 0 /* flags */, ¶ms, NULL /* keychain */, + certificate); +} + +/* + * Secure Transport has the concept of an identity, which is a packaging of a + * private key and the certificate which contains the public key. The identity + * is what is used for verifying the connection, so we need to provide a + * SecIdentityRef identity to the API. + * + * A complete, and packaged, identity can be contained in a Keychain, in which + * case we can load it directly without having to create anything ourselves. + * In the case where we don't have a prepared identity in a Keychain, we need + * to create it from its components (certificate and key). The certificate must + * in that case be be located in a PEM file on the local filesystem. The key + * can either be in a PEM file or in the Keychain. + * + * While keeping identities in the Keychain is the macOS thing to do, we want + * to be functionally compatible with the OpenSSL support in libpq. Thus we not + * only need to support creating an identity from a private key contained in a + * PEM file, it needs to be the default. The order in which we discover the + * identity is: + * + * 1. Certificate and key in local files + * 2. Certificate in local file and key in Keychain + * 3. Identity in Keychain + * + * Since failures can come from multiple places, the PGconn errorMessage is + * populated here even for SSL library errors. + */ +static OSStatus +pg_SSLLoadCertificate(PGconn *conn, CFArrayRef *cert_array, + CFArrayRef *key_array, CFArrayRef *rootcert_array) +{ + OSStatus status; + struct stat buf; + char homedir[MAXPGPATH]; + char fnbuf[MAXPGPATH]; + bool have_homedir; + bool cert_from_file = false; + char *ssl_err_msg; + SecIdentityRef identity = NULL; + SecCertificateRef cert_ref; + SecCertificateRef root_ref[1]; + SecKeyRef key_ref = NULL; + CFArrayRef keychains = NULL; + SecKeychainRef kcref[2]; + CFMutableArrayRef cert_connection; + + /* + * If we have a keychain configured, open the referenced keychain as well + * as the default keychain and prepare an array with the references for + * searching. If no additional keychain is specified we don't need to open + * the default keychain and pass to searches since Secure Transport will + * use the default when passing NULL instead of an array of Keychain refs. + */ + if (conn->keychain) + { + if (stat(conn->keychain, &buf) == 0) + { + status = SecKeychainOpen(conn->keychain, &kcref[0]); + if (status == noErr && kcref[0] != NULL) + { + SecKeychainStatus kcstatus; + status = SecKeychainGetStatus(kcref[0], &kcstatus); + if (status == noErr) + { + switch (kcstatus) + { + /* + * If the Keychain is already unlocked, readable or + * writeable, we don't need to do more. If not, try to + * unlock it. + */ + case kSecUnlockStateStatus: + case kSecReadPermStatus: + case kSecWritePermStatus: + break; + default: + /* XXX: fix passphrase param */ + SecKeychainUnlock(kcref[0], 0, "", TRUE); + break; + } + } + + /* + * We only need to open, and add, the default Keychain in case + * have a user keychain opened, else we will pass NULL to any + * keychain search which will use the default keychain by.. + * default. + */ + SecKeychainCopyDefault(&kcref[1]); + keychains = CFArrayCreate(NULL, (const void **) kcref, 2, + &kCFTypeArrayCallBacks); + } + } + } + + /* + * We'll need the home directory if any of the relevant parameters are + * defaulted. If pqGetHomeDirectory fails, act as though none of the files + * could be found. + */ + if (!(conn->sslcert && strlen(conn->sslcert) > 0) || + !(conn->sslkey && strlen(conn->sslkey) > 0) || + !(conn->sslrootcert && strlen(conn->sslrootcert) > 0)) + have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir)); + else /* won't need it */ + have_homedir = false; + + /* + * Try to load the root cert from either a user defined keychain or the + * default Keychain in case none is specified + */ + if (conn->sslrootcert && + pg_strncasecmp(conn->sslrootcert, KC_PREFIX, KC_PREFIX_LEN) == 0) + { + root_ref[0] = NULL; + strlcpy(fnbuf, conn->sslrootcert + KC_PREFIX_LEN, sizeof(fnbuf)); + + import_certificate_keychain(fnbuf, &root_ref[0], keychains, NULL); + + if (root_ref[0]) + conn->st_rootcert = (void *) CFArrayCreate(NULL, + (const void **) root_ref, + 1, &kCFTypeArrayCallBacks); + } + + if (!conn->st_rootcert) + { + if (conn->sslrootcert && strlen(conn->sslrootcert) > 0) + strlcpy(fnbuf, conn->sslrootcert, sizeof(fnbuf)); + else if (have_homedir) + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CERT_FILE); + else + fnbuf[0] = '\0'; + + if (fnbuf[0] != '\0') + { + if (stat(fnbuf, &buf) != 0) + { + /* + * stat() failed; assume root file doesn't exist. If sslmode is + * verify-ca or verify-full, this is an error. Otherwise, continue + * without performing any server cert verification. + */ + if (conn->sslmode[0] == 'v') /* "verify-ca" or "verify-full" */ + { + /* + * The only way to reach here with an empty filename is if + * pqGetHomeDirectory failed. That's a sufficiently unusual + * case that it seems worth having a specialized error message + * for it. + */ + if (fnbuf[0] == '\0') + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not get home directory to locate root certificate file\n" + "Either provide the file or change sslmode to disable server certificate verification.\n")); + else + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("root certificate file \"%s\" does not exist\n" + "Either provide the file or change sslmode to disable server certificate verification.\n"), fnbuf); + return errSecInternalError; + } + } + else + { + status = import_pem(fnbuf, NULL, rootcert_array); + if (status != noErr) + { + ssl_err_msg = pg_SSLerrmessage(status); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not load root certificate file \"%s\": %s\n"), + fnbuf, ssl_err_msg); + pg_SSLerrfree(ssl_err_msg); + return status; + } + + if (*rootcert_array != NULL) + conn->st_rootcert = (void *) CFRetain(*rootcert_array); + } + } + } + + /* + * If the sslcert config is prefixed with a keychain identifier, the cert + * must be located in either the default or the specified keychain. + */ + if (conn->sslcert && + pg_strncasecmp(conn->sslcert, KC_PREFIX, KC_PREFIX_LEN) == 0) + { + strlcpy(fnbuf, conn->sslcert + KC_PREFIX_LEN, sizeof(fnbuf)); + + import_identity_keychain(fnbuf, &identity, keychains); + + if (identity) + SecIdentityCopyPrivateKey(identity, &key_ref); + } + /* + * No prefix on the sslcert config, the certificate must thus reside in a + * file on disk. + */ + else + { + if (conn->sslcert && strlen(conn->sslcert) > 0) + strlcpy(fnbuf, conn->sslcert, sizeof(fnbuf)); + else if (have_homedir) + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_CERT_FILE); + else + fnbuf[0] = '\0'; + + if (fnbuf[0] != '\0') + { + status = import_pem(fnbuf, NULL, cert_array); + if (status != noErr) + { + if (status == -1) + return noErr; + + ssl_err_msg = pg_SSLerrmessage(status); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not load certificate file \"%s\": %s\n"), + fnbuf, ssl_err_msg); + pg_SSLerrfree(ssl_err_msg); + return status; + } + + cert_ref = (SecCertificateRef) CFArrayGetValueAtIndex(*cert_array, 0); + cert_from_file = true; + + /* + * We now have a certificate, so we need a private key as well in + * order to create the identity. + */ + + /* + * The sslkey config is prefixed with keychain: indicating that the + * key should be loaded from Keychain instead of the filesystem. + * Search for the private key matching the cert_ref in the opened + * Keychains. If found, we get the identity returned. + */ + if (conn->sslkey && + pg_strncasecmp(conn->sslkey, KC_PREFIX, KC_PREFIX_LEN) == 0) + { + status = SecIdentityCreateWithCertificate(keychains, cert_ref, + &identity); + if (status != noErr) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("certificate present, but private key \"%s\" not found in Keychain\n"), + fnbuf); + return errSecInternalError; + } + + SecIdentityCopyPrivateKey(identity, &key_ref); + } + else + { + if (conn->sslkey && strlen(conn->sslkey) > 0) + strlcpy(fnbuf, conn->sslkey, sizeof(fnbuf)); + else if (have_homedir) + snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE); + else + fnbuf[0] = '\0'; + + /* + * If there is a matching file on the filesystem, require the key to be + * loaded from that file. + */ + if (fnbuf[0] != '\0') + { + status = import_pem(fnbuf, NULL, key_array); + if (status != noErr) + { + ssl_err_msg = pg_SSLerrmessage(status); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not load private key file \"%s\": %s\n"), + fnbuf, ssl_err_msg); + pg_SSLerrfree(ssl_err_msg); + return status; + } + + key_ref = (SecKeyRef) CFArrayGetValueAtIndex(*key_array, 0); + } + } + + /* + * We have certificate and a key loaded from files on disk, now we + * can create an identity from this pair. + */ + if (key_ref) + identity = SecIdentityCreate(NULL, cert_ref, key_ref); + } + } + + if (!identity) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not create identity from certificate/key\n")); + return errSecInvalidValue; + } + + /* + * If we have created the identity "by hand" without involving the + * Keychain we need to include the certificates in the array passed to + * SSLSetCertificate() + */ + if (cert_from_file) + { + cert_connection = CFArrayCreateMutableCopy(NULL, 0, *cert_array); + CFArraySetValueAtIndex(cert_connection, 0, identity); + } + else + { + cert_connection = CFArrayCreateMutable(NULL, 1L, + &kCFTypeArrayCallBacks); + CFArrayInsertValueAtIndex(cert_connection, 0, + (const void *) identity); + } + + status = SSLSetCertificate(conn->ssl, cert_connection); + + if (status != noErr) + { + ssl_err_msg = pg_SSLerrmessage(status); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not set certificate for connection: (%d) %s\n"), + status, ssl_err_msg); + pg_SSLerrfree(ssl_err_msg); + return status; + } + + if (key_ref) + { + conn->ssl_key_bits = SecKeyGetBlockSize(key_ref); + CFRelease(key_ref); + } + + return noErr; +} + +/* ------------------------------------------------------------ */ +/* SSL information functions */ +/* ------------------------------------------------------------ */ + +void * +PQgetssl(PGconn *conn) +{ + /* + * Always return NULL as this is legacy and defined to be equal to + * PQsslStruct(conn, "OpenSSL"); + */ + return NULL; +} + +void * +PQsslStruct(PGconn *conn, const char *struct_name) +{ + if (!conn) + return NULL; + if (strcmp(struct_name, "SecureTransport") == 0) + return conn->ssl; + return NULL; +} + +const char *const * +PQsslAttributeNames(PGconn *conn) +{ + static const char *const result[] = { + "library", + "key_bits", + "cipher", + "protocol", + NULL + }; + + return result; +} + +const char * +PQsslAttribute(PGconn *conn, const char *attribute_name) +{ + SSLCipherSuite cipher; + SSLProtocol protocol; + OSStatus status; + const char *attribute = NULL; + + if (!conn || !conn->ssl) + return NULL; + + if (strcmp(attribute_name, "library") == 0) + attribute = "SecureTransport"; + else if (strcmp(attribute_name, "key_bits") == 0) + { + if (conn->ssl_key_bits > 0) + { + static char sslbits_str[10]; + snprintf(sslbits_str, sizeof(sslbits_str), "%d", conn->ssl_key_bits); + attribute = sslbits_str; + } + } + else if (strcmp(attribute_name, "cipher") == 0) + { + status = SSLGetNegotiatedCipher(conn->ssl, &cipher); + if (status == noErr) + return pg_SSLciphername(cipher); + } + else if (strcmp(attribute_name, "protocol") == 0) + { + status = SSLGetNegotiatedProtocolVersion(conn->ssl, &protocol); + if (status == noErr) + { + switch (protocol) + { + case kTLSProtocol11: + attribute = "TLSv1.1"; + break; + case kTLSProtocol12: + attribute = "TLSv1.2"; + break; + default: + break; + } + } + } + + return attribute; +} + +/* ------------------------------------------------------------ */ +/* Secure Transport Information Functions */ +/* ------------------------------------------------------------ */ + +/* + * Obtain reason string for passed SSL errcode + */ +static char ssl_noerr[] = "no SSL error reported"; +static char ssl_nomem[] = "out of memory allocating error description"; +#define SSL_ERR_LEN 128 + +static char * +pg_SSLerrmessage(OSStatus errcode) +{ + char *err_buf; + const char *tmp; + CFStringRef err_msg; + + if (errcode == noErr || errcode == errSecSuccess) + return ssl_noerr; + + err_buf = malloc(SSL_ERR_LEN); + if (!err_buf) + return ssl_nomem; + + err_msg = SecCopyErrorMessageString(errcode, NULL); + if (err_msg) + { + tmp = CFStringGetCStringPtr(err_msg, kCFStringEncodingUTF8); + strlcpy(err_buf, tmp, SSL_ERR_LEN); + CFRelease(err_msg); + } + else + snprintf(err_buf, sizeof(err_buf), _("SSL error code %d"), errcode); + + return err_buf; +} + +static void +pg_SSLerrfree(char *err_buf) +{ + if (err_buf && err_buf != ssl_nomem && err_buf != ssl_noerr) + free(err_buf); +} + +/* + * pg_SSLsessionstate + * + * Returns 0 if the connection is open and -1 in case the connection is closed, + * or its status unknown. If msg is non-NULL the current state is copied with + * at most len - 1 characters ensuring a NUL terminated returned string. + */ +static int +pg_SSLsessionstate(PGconn *conn, char *msg, size_t len) +{ + SSLSessionState state; + OSStatus status; + const char *status_msg; + + /* + * If conn->ssl isn't defined we will report "Unknown" which it could be + * argued being correct or not, but since we don't know if there has ever + * been a connection at all it's not more correct to say "Closed" or + * "Aborted". + */ + if (conn->ssl) + status = SSLGetSessionState(conn->ssl, &state); + else + { + status = errSecInternalError; + state = -1; + } + + switch (state) + { + case kSSLConnected: + status_msg = "Connected"; + status = 0; + break; + case kSSLHandshake: + status_msg = "Handshake"; + status = 0; + break; + case kSSLIdle: + status_msg = "Idle"; + status = 0; + break; + case kSSLClosed: + status_msg = "Closed"; + status = -1; + break; + case kSSLAborted: + status_msg = "Aborted"; + status = -1; + break; + default: + status_msg = "Unknown"; + status = -1; + break; + } + + if (msg) + strlcpy(msg, status_msg, len); + + return (status == noErr ? 0 : -1); +} diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index b3492b033a..08c3005d14 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -80,6 +80,14 @@ typedef struct #endif #endif /* USE_OPENSSL */ +#ifdef USE_SECURETRANSPORT +#define Size pg_Size +#define uint64 pg_uint64 +#include +#undef Size +#undef uint64 +#endif + /* * POSTGRES backend dependent Constants. */ @@ -470,8 +478,18 @@ struct pg_conn void *engine; /* dummy field to keep struct the same if * OpenSSL version changes */ #endif -#endif /* USE_OPENSSL */ -#endif /* USE_SSL */ +#endif /* USE_OPENSSL */ + +#ifdef USE_SECURETRANSPORT + char *keychain; + + SSLContextRef ssl; /* SSL context reference */ + void *st_rootcert; + ssize_t ssl_buffered; + int ssl_key_bits; +#endif /* USE_SECURETRANSPORT */ + +#endif /* USE_SSL */ #ifdef ENABLE_GSS gss_ctx_id_t gctx; /* GSS context */ diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile index 4e9095529a..d62c6ba247 100644 --- a/src/test/ssl/Makefile +++ b/src/test/ssl/Makefile @@ -123,6 +123,20 @@ ssl/root+server.crl: ssl/root.crl ssl/server.crl ssl/root+client.crl: ssl/root.crl ssl/client.crl cat $^ > $@ +#### Keychains + +ifeq ($(with_securetransport),yes) + +KEYCHAINS := ssl/client.keychain + +# This target generates all Keychains +keychains: $(KEYCHAINS) + +PWD=$(shell pwd) +ssl/client.keychain: ssl/client.crt ssl/client.key + certtool i $(PWD)/ssl/client.crt c k=$(PWD)/ssl/client.keychain r=$(PWD)/ssl/client.key p= +endif + .PHONY: sslfiles-clean sslfiles-clean: rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt ssl/temp_ca_signed.crt diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm index 02f8028b2b..c645ab2bc4 100644 --- a/src/test/ssl/ServerSetup.pm +++ b/src/test/ssl/ServerSetup.pm @@ -159,7 +159,14 @@ sub switch_server_cert print $sslconf "ssl_ca_file='$cafile.crt'\n"; print $sslconf "ssl_cert_file='$certfile.crt'\n"; print $sslconf "ssl_key_file='$certfile.key'\n"; - print $sslconf "ssl_crl_file='root+client.crl'\n"; + if (check_pg_config("#define USE_SECURETRANSPORT 1")) + { + print $sslconf "ssl_crl_file=''\n"; + } + else + { + print $sslconf "ssl_crl_file='root+client.crl'\n"; + } close $sslconf; $node->reload; diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl index 28837a1391..a858344329 100644 --- a/src/test/ssl/t/001_ssltests.pl +++ b/src/test/ssl/t/001_ssltests.pl @@ -2,7 +2,7 @@ use strict; use warnings; use PostgresNode; use TestLib; -use Test::More tests => 40; +use Test::More; use ServerSetup; use File::Copy; @@ -21,6 +21,27 @@ my $common_connstr; copy("ssl/client.key", "ssl/client_tmp.key"); chmod 0600, "ssl/client_tmp.key"; +# macOS Secure Transport does not support loading CRL files, so skip CRL +# tests when built with Secure Transport support. +my $supports_crl_files = ! check_pg_config("#define USE_SECURETRANSPORT 1"); + +# Test for support of Secure Transport Keychain secure archives +# This check is currently the same as $supports_crl_files, but clarity when +# reading code is more important than avoiding the use of an extra variable +my $supports_keychains = check_pg_config("#define USE_SECURETRANSPORT 1"); + +#### Setup + +if ($supports_keychains) +{ + plan tests => 43; +} +else +{ + plan tests => 40; +} + + #### Part 0. Set up the server. note "setting up data directory"; @@ -91,18 +112,37 @@ test_connect_ok($common_connstr, note "testing sslcrl option with a non-revoked cert"; -# Invalid CRL filename is the same as no CRL, succeeds -test_connect_ok($common_connstr, - "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid"); - -# A CRL belonging to a different CA is not accepted, fails -test_connect_fails($common_connstr, -"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl"); - -# With the correct CRL, succeeds (this cert is not revoked) -test_connect_ok($common_connstr, -"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl" -); +if ($supports_crl_files) +{ + # Invalid CRL filename is the same as no CRL, succeeds + test_connect_ok($common_connstr, + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid"); + + # With the correct CRL, succeeds (this cert is not revoked) + test_connect_ok($common_connstr, + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl" + ); + + # A CRL belonging to a different CA is not accepted, fails + test_connect_fails($common_connstr, + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl"); +} +else +{ + # Invalid CRL filename is the same as no CRL, succeeds + test_connect_fails($common_connstr, + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid"); + + # With the correct CRL, succeeds (this cert is not revoked) + test_connect_fails($common_connstr, + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl" + ); + + # A CRL belonging to a different CA is not accepted, this should fail iff + # CRL files are supported + test_connect_fails($common_connstr, + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl"); +} # Check that connecting with verify-full fails, when the hostname doesn't # match the hostname in the server's certificate. @@ -176,9 +216,18 @@ $common_connstr = # Without the CRL, succeeds. With it, fails. test_connect_ok($common_connstr, "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca"); -test_connect_fails($common_connstr, -"sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl" -); +if ($supports_crl_files) +{ + test_connect_fails($common_connstr, + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl" + ); +} +else +{ + test_connect_fails($common_connstr, + "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl" + ); +} ### Part 2. Server-side tests. ### @@ -199,10 +248,20 @@ test_connect_ok($common_connstr, test_connect_fails($common_connstr, "user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key"); -# revoked client cert -test_connect_fails($common_connstr, -"user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key" -); +if ($supports_crl_files) +{ + # revoked client cert + test_connect_fails($common_connstr, + "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key" + ); +} +else +{ + # revoked client cert + test_connect_ok($common_connstr, + "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key" + ); +} # intermediate client_ca.crt is provided by client, and isn't in server's ssl_ca_file switch_server_cert($node, 'server-cn-only', 'root_ca'); @@ -213,5 +272,15 @@ test_connect_ok($common_connstr, "sslmode=require sslcert=ssl/client+client_ca.crt"); test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt"); +if ($supports_keychains) +{ + # empty keychain + test_connect_fails("user=ssltestuser keychain=invalid"); + + # correct client cert in keychain with and without proper label + test_connect_fails("user=ssltestuser keychain=ssl/client.keychain"); + test_connect_ok("user=ssltestuser sslcert=ssltestuser keychain=ssl/client.keychain"); +} + # clean up unlink "ssl/client_tmp.key"; diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl index 67c1409a6e..03a5533e29 100644 --- a/src/test/ssl/t/002_scram.pl +++ b/src/test/ssl/t/002_scram.pl @@ -15,6 +15,9 @@ my $SERVERHOSTADDR = '127.0.0.1'; my $supports_tls_server_end_point = check_pg_config("#define HAVE_X509_GET_SIGNATURE_NID 1"); +my $supports_tls_unique = + ! check_pg_config("#define USE_SECURETRANSPORT 1"); + # Allocation of base connection string shared among multiple tests. my $common_connstr; @@ -38,14 +41,27 @@ $ENV{PGPASSWORD} = "pass"; $common_connstr = "user=ssltestuser dbname=trustdb sslmode=require hostaddr=$SERVERHOSTADDR"; -# Default settings -test_connect_ok($common_connstr, '', - "SCRAM authentication with default channel binding"); - # Channel binding settings -test_connect_ok($common_connstr, - "scram_channel_binding=tls-unique", - "SCRAM authentication with tls-unique as channel binding"); +if ($supports_tls_unique) +{ + # Default settings + test_connect_ok($common_connstr, '', + "SCRAM authentication with default channel binding"); + + test_connect_ok($common_connstr, + "scram_channel_binding=tls-unique", + "SCRAM authentication with tls-unique as channel binding"); +} +else +{ + # Default settings + test_connect_fails($common_connstr, '', + "SCRAM authentication with default channel binding"); + + test_connect_fails($common_connstr, + "scram_channel_binding=tls-unique", + "SCRAM authentication with tls-unique as channel binding"); +} test_connect_ok($common_connstr, "scram_channel_binding=''", "SCRAM authentication without channel binding"); -- 2.14.1.145.gb3622a4ee