windows-schannel-2.patch
text/x-diff
Filename: windows-schannel-2.patch
Type: text/x-diff
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| configure | 43 | 0 |
| configure.in | 14 | 0 |
| src/backend/libpq/be-secure.c | 4 | 0 |
| src/backend/libpq/be-secure-openssl.c | 0 | 4 |
| src/backend/libpq/be-secure-winschannel.c | 1098 | 0 |
| src/backend/libpq/Makefile | 4 | 0 |
| src/include/libpq/libpq-be.h | 7 | 0 |
| src/include/pg_config.h.in | 3 | 0 |
| src/include/pg_config.h.win32 | 3 | 0 |
| src/include/pg_config_manual.h | 2 | 3 |
| src/interfaces/libpq/fe-secure.c | 294 | 0 |
| src/interfaces/libpq/fe-secure-openssl.c | 112 | 232 |
| src/interfaces/libpq/fe-secure-winschannel.c | 1353 | 0 |
| src/interfaces/libpq/libpq-int.h | 10 | 0 |
| src/interfaces/libpq/Makefile | 4 | 0 |
| src/interfaces/libpq/win32.mak | 11 | 1 |
| src/tools/msvc/config_default.pl | 1 | 0 |
| src/tools/msvc/Mkvcbuild.pm | 18 | 0 |
| src/tools/msvc/Solution.pm | 3 | 0 |
diff --git a/configure b/configure
index 0f435b5..d00290a 100755
--- a/configure
+++ b/configure
@@ -707,6 +707,7 @@ XML2_CONFIG
UUID_EXTRA_OBJS
with_uuid
with_selinux
+with_winschannel
with_openssl
krb_srvtab
with_python
@@ -823,6 +824,7 @@ with_pam
with_ldap
with_bonjour
with_openssl
+with_winschannel
with_selinux
with_readline
with_libedit_preferred
@@ -1509,6 +1511,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-winschannel build with Windows SChannel support
--with-selinux build with SELinux support
--without-readline do not use GNU Readline nor BSD Libedit for editing
--with-libedit-preferred
@@ -5514,6 +5517,46 @@ $as_echo "$with_openssl" >&6; }
#
+# Windows SChannel
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with native Windows SSL support" >&5
+$as_echo_n "checking whether to build with native Windows SSL support... " >&6; }
+
+
+
+# Check whether --with-winschannel was given.
+if test "${with_winschannel+set}" = set; then :
+ withval=$with_winschannel;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_WINDOWS_SCHANNEL 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-winschannel option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_winschannel=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_winschannel" >&5
+$as_echo "$with_winschannel" >&6; }
+
+
+if test "$with_openssl" = yes -a "$with_winschannel" = yes ; then
+ as_fn_error $? "
+*** Cannot select both OpenSSL and Windows SChannel." "$LINENO" 5
+fi
+
+#
# SELinux
#
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with SELinux support" >&5
diff --git a/configure.in b/configure.in
index f8a4507..132fb0a 100644
--- a/configure.in
+++ b/configure.in
@@ -662,6 +662,20 @@ AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
#
+# Windows SChannel
+#
+AC_MSG_CHECKING([whether to build with native Windows SSL support])
+PGAC_ARG_BOOL(with, winschannel, no, [build with Windows SChannel support],
+ [AC_DEFINE([USE_WINDOWS_SCHANNEL], 1, [Define to build with Windows SChannel support. (--with-winschannel)])])
+AC_MSG_RESULT([$with_winschannel])
+AC_SUBST(with_winschannel)
+
+if test "$with_openssl" = yes -a "$with_winschannel" = yes ; then
+ AC_MSG_ERROR([
+*** Cannot select both OpenSSL and Windows SChannel.])
+fi
+
+#
# SELinux
#
AC_MSG_CHECKING([whether to build with SELinux support])
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 8be0572..88d0b8b 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,8 @@ ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
endif
+ifeq ($(with_winschannel),yes)
+OBJS += be-secure-winschannel.o
+endif
+
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index e3a284b..651162e 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -922,10 +922,6 @@ aloop:
port->peer_cert_valid = true;
}
- ereport(DEBUG2,
- (errmsg("SSL connection from \"%s\"",
- port->peer_cn ? port->peer_cn : "(anonymous)")));
-
/* set up debugging/info callback */
SSL_CTX_set_info_callback(SSL_context, info_cb);
diff --git a/src/backend/libpq/be-secure-winschannel.c b/src/backend/libpq/be-secure-winschannel.c
new file mode 100644
index 0000000..306f265
--- /dev/null
+++ b/src/backend/libpq/be-secure-winschannel.c
@@ -0,0 +1,1098 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-winschannel.c
+ * SSL support using Windows SChannel APIs
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-winschannel.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "libpq/libpq-be.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+
+#include <sspi.h>
+#include <schnlsp.h>
+
+#include <wincrypt.h>
+
+/*
+ * Name of the Crypto API key container to use for storing the server
+ * certificate and private key.
+ */
+#define PG_KEY_CONTAINER_NAME L"PostgreSQL server key container"
+
+#define SCHANNEL_DEBUG LOG
+
+struct SchannelContext
+{
+ /* SChannel handle for the connection */
+ CtxtHandle ctxHandle;
+ bool ctxHandle_valid;
+ CredHandle credHandle;
+ bool credHandle_valid;
+
+ bool remote_shutdown;
+
+ /*
+ * Buffer for encrypting data to be written.
+ *
+ * When encryptedLen > 0, the buffer contains some encrypted data that
+ * should be written out to the client.
+ *
+ * When encryptedLen == 0, call encryptBuffer() to load the buffer with
+ * more data, and encrypt it.
+ */
+ char *encryptBuffer;
+ int encryptBufferSize;
+ int encryptBufferLen;
+
+ char *encryptedData;
+ size_t encryptedLen;
+
+ /*
+ * Number of plaintext bytes the buffer currently holds in encrypted form.
+ * This is less than the number of encrypted bytes, because the SSL
+ * protocol adds framing. (it could also be more, when compression is used)
+ */
+ int plaintextLen;
+
+ /*
+ * Buffer for decrypting read data.
+ *
+ * decryptBufferSize is the allocated size of decryptBuffer.
+ * decryptBufferLen is the number of ciphertext bytes loaded to the buffer.
+ *
+ * The buffer can be in one of two states. In the first state, it contains
+ * zero or more raw bytes, and more can be loaded into it. In the other
+ * state, it contains decrypted data that can be read out.
+ *
+ * When the buffer contains decrypted data, decryptedLen > 0, and
+ * decryptedData points to the data. The buffer can also contain some
+ * leftover raw data that has not been decrypted yet (extraData/extraLen).
+ *
+ * Before reading more raw data to the buffer, call
+ * prepareDecryptBufferForRead. It moves any leftover raw data to the
+ * beginning of the buffer.
+ */
+ char *decryptBuffer;
+ int decryptBufferSize;
+ int decryptBufferLen; /* ciphertext bytes loaded in decryptBuffer */
+
+ /* decrypted data not returned to the caller yet */
+ char *decryptedData;
+ int decryptedLen;
+
+ char *extraData;
+ int extraLen;
+};
+
+typedef struct SchannelContext SchannelContext;
+
+/*
+ * Somewhat arbitrarily, use a 10k buffer for both input and output.
+ *
+ * XXX: A buffer size somewhat larger than 8k probably makes sense at least
+ * for input, because the pqcomm.c uses an 8k buffer. (the extra is to contain
+ * SSL headers in the raw input). But I haven't done any performance testing.
+ */
+#define BUFSIZE 10000
+
+/* Functions for managing the encrypt/decrypt buffers */
+static ssize_t encryptBuffer(SchannelContext *ctx, void *ptr, int len);
+static void prepareDecryptBufferForRead(SchannelContext *ctx);
+static int decryptBuffer(SchannelContext *ctx);
+
+static int schannel_handshake(Port *port);
+
+/* Functions for dealing with certificates */
+static void importServerKey(void);
+static CERT_CONTEXT *getServerCert(void);
+static CERT_CONTEXT *getClientRootCert(void);
+static bool validateClientCert(CERT_CONTEXT *cert);
+
+/* Functions for converting error codes to strings */
+static const char *getSecurityStatus(SECURITY_STATUS dwerror);
+
+static char *slurpFile(const char *path, int *size);
+
+/*
+ * Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len)
+{
+ SchannelContext *ctx = port->windows_schannel;
+ int sendlen;
+
+ /*
+ * If the output buffer is empty, encrypt (some of) the input data to fill
+ * it.
+ */
+ if (ctx->encryptedLen == 0)
+ {
+ int plaintextLen;
+
+ plaintextLen = encryptBuffer(ctx, ptr, len);
+ if (plaintextLen < 0)
+ return -1;
+ ctx->plaintextLen = plaintextLen;
+ }
+
+ /*
+ * Write out the encrypted data in the buffer
+ */
+ sendlen = secure_raw_write(port,
+ ctx->encryptedData,
+ ctx->encryptedLen);
+ if (sendlen <= 0)
+ return sendlen;
+
+ ctx->encryptedData += sendlen;
+ ctx->encryptedLen -= sendlen;
+
+ if (ctx->encryptedLen == 0)
+ {
+ /* sent all the data in the buffer. */
+ return ctx->plaintextLen;
+ }
+ else
+ {
+ /* return 0 to indicate that the caller has to retry. */
+ return 0;
+ }
+}
+
+/*
+ * Encrypt more data. Helper function for be_tls_write().
+ */
+static ssize_t
+encryptBuffer(SchannelContext *ctx, void *ptr, int len)
+{
+ SecPkgContext_StreamSizes sizes;
+ SecBufferDesc sbufdesc;
+ SecBuffer sbufs[4];
+ SECURITY_STATUS rc;
+
+ /* we should've sent out all encrypted data from previous round first */
+ Assert(ctx->encryptedLen == 0);
+
+ rc = QueryContextAttributes(&ctx->ctxHandle,
+ SECPKG_ATTR_STREAM_SIZES, &sizes);
+ if (rc != SEC_E_OK)
+ {
+ elog(COMMERROR, "QueryContextAttributes failed: %s", getSecurityStatus(rc));
+ return -1;
+ }
+ if (sizes.cbHeader + sizes.cbTrailer + len > ctx->encryptBufferSize)
+ len = ctx->encryptBufferSize - (sizes.cbHeader + sizes.cbTrailer);
+
+ memcpy(&ctx->encryptBuffer[sizes.cbHeader], ptr, len);
+
+ sbufs[0].pvBuffer = ctx->encryptBuffer;
+ sbufs[0].cbBuffer = sizes.cbHeader;
+ sbufs[0].BufferType = SECBUFFER_STREAM_HEADER;
+
+ sbufs[1].pvBuffer = &ctx->encryptBuffer[sizes.cbHeader];
+ sbufs[1].cbBuffer = len;
+ sbufs[1].BufferType = SECBUFFER_DATA;
+
+ sbufs[2].pvBuffer = &ctx->encryptBuffer[sizes.cbHeader + len];
+ sbufs[2].cbBuffer = sizes.cbTrailer;
+ sbufs[2].BufferType = SECBUFFER_STREAM_TRAILER;
+
+ sbufs[3].pvBuffer = NULL;
+ sbufs[3].cbBuffer = 0;
+ sbufs[3].BufferType = SECBUFFER_EMPTY;
+
+ sbufdesc.ulVersion = SECBUFFER_VERSION;
+ sbufdesc.cBuffers = 4;
+ sbufdesc.pBuffers = sbufs;
+
+ rc = EncryptMessage(&ctx->ctxHandle, 0, &sbufdesc, 0);
+ if (rc != SEC_E_OK)
+ {
+ elog(ERROR, "EncryptMessage failed: %s", getSecurityStatus(rc));
+ return -1;
+ }
+
+ ctx->encryptedData = ctx->encryptBuffer;
+ ctx->encryptedLen = sbufs[0].cbBuffer + sbufs[1].cbBuffer + sbufs[2].cbBuffer;
+
+ return len;
+}
+
+/*
+ * Initialize global SSL context.
+ */
+void
+be_tls_init(void)
+{
+ /*
+ * Import the server's private key into the key container. (This only
+ * needs to be done once at server startup.)
+ */
+ if (IsUnderPostmaster)
+ importServerKey();
+}
+
+static int
+schannel_handshake(Port *port)
+{
+ SchannelContext *ctx = port->windows_schannel;
+ SecBufferDesc insbufdesc;
+ SecBuffer insbufs[2];
+ SecBufferDesc outsbufdesc;
+ SecBuffer outsbufs[1];
+ DWORD flags;
+ DWORD outflags;
+ SECURITY_STATUS rc;
+ ssize_t readlen;
+ bool handshake_complete = false;
+
+ do
+ {
+ /* read more raw data to buffer */
+ readlen = secure_raw_read(port,
+ &ctx->decryptBuffer[ctx->decryptBufferLen],
+ ctx->decryptBufferSize - ctx->decryptBufferLen);
+ if (readlen < 0)
+ {
+ ereport(COMMERROR,
+ (errcode_for_socket_access(),
+ errmsg("could not accept SSL connection: %m")));
+ be_tls_close(port);
+ return -1;
+ }
+
+ ctx->decryptBufferLen += readlen;
+
+ /*
+ * Pass the raw data read this far to AcceptSecurityContext.
+ * Per Microsoft documentation, AcceptSecurityContext requires two
+ * input buffers, one with SECBUFFER_TOKEN containing the input data,
+ * and one SECBUFFER_EMPTY.
+ */
+ insbufs[0].pvBuffer = ctx->decryptBuffer;
+ insbufs[0].cbBuffer = ctx->decryptBufferLen;
+ insbufs[0].BufferType = SECBUFFER_TOKEN;
+
+ insbufs[1].pvBuffer = NULL;
+ insbufs[1].cbBuffer = 0;
+ insbufs[1].BufferType = SECBUFFER_EMPTY;
+
+ insbufdesc.cBuffers = 2;
+ insbufdesc.pBuffers = insbufs;
+ insbufdesc.ulVersion = SECBUFFER_VERSION;
+
+ /*
+ * The ASC_REQ_MUTUAL_AUTH means that we request a client cert. The
+ * connection will still succeed if the client doesn't provide one.
+ */
+ flags = ASC_REQ_ALLOCATE_MEMORY |
+ ASC_REQ_CONFIDENTIALITY |
+ ASC_RET_EXTENDED_ERROR |
+ ASC_REQ_REPLAY_DETECT |
+ ASC_REQ_SEQUENCE_DETECT |
+ ASC_REQ_MUTUAL_AUTH |
+ ASC_REQ_STREAM;
+
+ outsbufs[0].pvBuffer = NULL;
+ outsbufs[0].BufferType = SECBUFFER_TOKEN;
+ outsbufs[0].cbBuffer = 0;
+
+ outsbufdesc.cBuffers = 1;
+ outsbufdesc.pBuffers = outsbufs;
+ outsbufdesc.ulVersion = SECBUFFER_VERSION;
+
+ rc = AcceptSecurityContext(&ctx->credHandle,
+ ctx->ctxHandle_valid ? &ctx->ctxHandle : NULL,
+ &insbufdesc,
+ flags,
+ 0,
+ ctx->ctxHandle_valid ? NULL : &ctx->ctxHandle,
+ &outsbufdesc,
+ &outflags,
+ NULL);
+ ctx->ctxHandle_valid = true;
+
+ /*
+ * If AcceptSecurityContext returns SEC_E_OK or SEC_I_CONTINUE_NEEDED,
+ * it has consumed (some of) the raw input. It may have also produced
+ * some output that we need to write back to the client.
+ */
+ if (rc == SEC_E_OK || rc == SEC_I_CONTINUE_NEEDED)
+ {
+ /*
+ * AcceptSecurityContext might not have processed all the input
+ * data. The number of unprocessed bytes is returned in input
+ * buffer 1, as an SECBUFFER_EXTRA buffer.
+ */
+ if (insbufs[1].BufferType == SECBUFFER_EXTRA)
+ {
+ size_t extraLen = insbufs[1].cbBuffer;
+
+ memmove(ctx->decryptBuffer, &ctx->decryptBuffer[ctx->decryptBufferLen - extraLen], extraLen);
+ ctx->decryptBufferLen = extraLen;
+ }
+ else
+ ctx->decryptBufferLen = 0;
+
+ if (outsbufs[0].pvBuffer)
+ {
+ ssize_t totalsent = 0;
+ ssize_t sendlen;
+
+ while(totalsent < outsbufs[0].cbBuffer)
+ {
+ sendlen = secure_raw_write(port,
+ outsbufs[0].pvBuffer,
+ outsbufs[0].cbBuffer);
+ /* Note: we assume that the socket is in blocking mode */
+ if (sendlen <= 0)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not send")));
+ return -1;
+ }
+
+ totalsent += sendlen;
+ }
+
+ FreeContextBuffer(outsbufs[0].pvBuffer);
+ outsbufs[0].pvBuffer = NULL;
+ }
+ }
+
+ switch(rc)
+ {
+ case SEC_E_OK:
+ /* SSL handshake is complete */
+ handshake_complete = true;
+ break;
+
+ case SEC_I_COMPLETE_AND_CONTINUE:
+ elog(COMMERROR, "SEC_I_COMPLETE_AND_CONTINUE not implemented", rc);
+ be_tls_close(port);
+ return -1;
+
+ case SEC_I_COMPLETE_NEEDED:
+ elog(COMMERROR, "SEC_I_COMPLETE_NEEDED not implemented", rc);
+ be_tls_close(port);
+ return -1;
+
+ case SEC_I_CONTINUE_NEEDED:
+ /* Need more input from the client to continue */
+ break;
+
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ elog(COMMERROR, "SEC_I_INCOMPLETE_CREDENTIALS not implemented");
+ be_tls_close(port);
+ return -1;
+
+ case SEC_E_INCOMPLETE_MESSAGE:
+ /*
+ * The input didn't contain a full SSL message. Loop back to
+ * read more data, leaving the input already read in the
+ * buffer, and retry.
+ */
+ break;
+
+ default:
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL connection"),
+ errdetail("AcceptSecurityContext failed: %s", getSecurityStatus(rc))));
+ be_tls_close(port);
+ return -1;
+ }
+ } while (!handshake_complete);
+
+ /* success! */
+ return 0;
+}
+
+/*
+ * Attempt to negotiate SSL connection.
+ */
+int
+be_tls_open_server(Port *port)
+{
+ SchannelContext *ctx = port->windows_schannel;
+ SECURITY_STATUS rc;
+ SCHANNEL_CRED credData;
+ CERT_CONTEXT *serverCert;
+ CERT_CONTEXT *remoteCert;
+
+ serverCert = getServerCert();
+
+ memset(&credData, 0, sizeof(credData));
+ credData.dwVersion = SCHANNEL_CRED_VERSION;
+ credData.dwFlags |= SCH_CRED_NO_SYSTEM_MAPPER;
+ credData.cCreds = 1;
+ credData.paCred = &serverCert;
+
+ ctx = (SchannelContext *) palloc0(sizeof(SchannelContext));
+
+ rc = AcquireCredentialsHandle(NULL,
+ UNISP_NAME,
+ SECPKG_CRED_INBOUND,
+ NULL,
+ &credData,
+ NULL,
+ NULL,
+ &ctx->credHandle,
+ NULL);
+ if (rc != SEC_E_OK)
+ {
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("could not initialize SSL"),
+ errdetail("AcquireCredentialsHandle failed: %s", getSecurityStatus(rc))));
+ be_tls_close(port);
+ return -1;
+ }
+ ctx->credHandle_valid = true;
+
+ ctx->encryptBufferSize = BUFSIZE;
+ ctx->encryptBuffer = palloc(ctx->encryptBufferSize);
+ ctx->decryptBufferSize = BUFSIZE;
+ ctx->decryptBuffer = palloc(ctx->encryptBufferSize);
+
+ port->windows_schannel = ctx;
+ port->ssl_in_use = true;
+
+ if (schannel_handshake(port) < 0)
+ return -1;
+
+ /* SSL handshake complete */
+ elog(SCHANNEL_DEBUG, "SSL handshake completed");
+
+ /* Get client certificate, if available */
+ rc = QueryContextAttributes(&ctx->ctxHandle,
+ SECPKG_ATTR_REMOTE_CERT_CONTEXT,
+ (PVOID) &remoteCert);
+ if (rc == SEC_E_OK)
+ {
+ /*
+ * Validate the client cert. If it passes, extract the Common Name,
+ * to be used later in cert authentication.
+ */
+ if (validateClientCert(remoteCert))
+ {
+ char *cn;
+ DWORD len;
+ DWORD len2;
+
+ port->peer_cert_valid = true;
+ elog(SCHANNEL_DEBUG, "client certificate validated");
+
+ /*
+ * Call CertGetNameString without passing a buffer, to get the
+ * required length.
+ */
+ len = CertGetNameString(remoteCert,
+ CERT_NAME_ATTR_TYPE,
+ 0,
+ szOID_COMMON_NAME,
+ NULL,
+ 0);
+ if (len > 1)
+ {
+ cn = palloc(len);
+ len2 = CertGetNameString(remoteCert,
+ CERT_NAME_ATTR_TYPE,
+ 0,
+ szOID_COMMON_NAME,
+ cn,
+ len);
+ if (len != len2)
+ {
+ /* shouldn't happen */
+ pfree(cn);
+ be_tls_close(port);
+ return -1;
+ }
+ port->peer_cn = cn;
+ elog(SCHANNEL_DEBUG, "client cert name: %s", cn);
+ }
+ }
+ else
+ elog(SCHANNEL_DEBUG, "client certificate validation failed");
+
+ CertFreeCertificateContext(remoteCert);
+ }
+
+ return 0;
+}
+
+/*
+ * Close SSL connection.
+ */
+void
+be_tls_close(Port *port)
+{
+ SchannelContext *ctx = port->windows_schannel;
+ DWORD token;
+ SecBufferDesc outsbufdesc;
+ SecBuffer outsbufs[1];
+ SECURITY_STATUS rc;
+
+ if (ctx->ctxHandle_valid)
+ {
+ /*
+ * "Shutting Down an Schannel Connection",
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa380138%28v=vs.85%29.aspx
+ */
+ token = SCHANNEL_SHUTDOWN;
+
+ outsbufdesc.ulVersion = SECBUFFER_VERSION;
+ outsbufdesc.cBuffers = 1;
+ outsbufdesc.pBuffers = outsbufs;
+
+ outsbufs[0].BufferType = SECBUFFER_TOKEN;
+ outsbufs[0].cbBuffer = sizeof( token );
+ outsbufs[0].pvBuffer = &token;
+
+ rc = ApplyControlToken(&ctx->ctxHandle, &outsbufdesc);
+
+ /*
+ * Use AcceptSecurityContext to finish the shutdown, just like the
+ * initiation handshake is done.
+ */
+ (void) schannel_handshake(port);
+
+ DeleteSecurityContext(&ctx->ctxHandle);
+ ctx->ctxHandle_valid = false;
+ }
+ if (ctx->credHandle_valid)
+ {
+ FreeCredentialsHandle(&ctx->credHandle);
+ ctx->credHandle_valid = false;
+ }
+
+ if (ctx->encryptBuffer)
+ pfree(ctx->encryptBuffer);
+ if (ctx->decryptBuffer)
+ pfree(ctx->decryptBuffer);
+
+ pfree(ctx);
+
+ if (port->peer_cn)
+ {
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+ port->peer_cert_valid = false;
+
+ port->windows_schannel = NULL;
+ port->ssl_in_use = false;
+}
+
+/*
+ * Read data from a secure connection.
+ */
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len)
+{
+ SchannelContext *ctx = port->windows_schannel;
+ int readlen;
+ int maxsize;
+ size_t totalreadlen = 0;
+ char *dst = ptr;
+
+ for (;;)
+ {
+ /* if we already have some decrypted data, copy it to the target buffer */
+ if (ctx->decryptedLen > 0)
+ {
+ maxsize = len - totalreadlen;
+ if (maxsize > ctx->decryptedLen)
+ maxsize = ctx->decryptedLen;
+ memcpy(dst, &ctx->decryptedData[totalreadlen], maxsize);
+ dst += maxsize;
+ totalreadlen += maxsize;
+ ctx->decryptedLen -= maxsize;
+ ctx->decryptedData += maxsize;
+ }
+
+ /* If we have some data to return, return it. */
+ if (totalreadlen > 0)
+ {
+ return totalreadlen;
+ }
+
+ /*
+ * there shouldn't be any decrypted data left in the buffer, we copied
+ * it all to the target.
+ */
+ Assert(ctx->decryptedLen == 0);
+
+ /*
+ * Need to decrypt more data, and for that, need to read more raw
+ * data.
+ */
+ prepareDecryptBufferForRead(ctx);
+ while (ctx->decryptedLen == 0)
+ {
+ /* read more data to buffer */
+ maxsize = ctx->decryptBufferSize - ctx->decryptBufferLen;
+ readlen = secure_raw_read(port,
+ &ctx->decryptBuffer[ctx->decryptBufferLen],
+ maxsize);
+ if (readlen < 0)
+ return readlen;
+ if (readlen == 0)
+ {
+ /* this implies we're in non-blocking mode */
+ return totalreadlen;
+ }
+
+ ctx->decryptBufferLen += readlen;
+
+ /* decrypt what we got */
+ if (decryptBuffer(ctx) == -1)
+ return -1;
+ if (ctx->remote_shutdown)
+ return totalreadlen;
+
+ /* If decryptBuffer needs more data, decrypteLen is still 0. */
+ }
+ }
+
+ return totalreadlen;
+}
+
+/*
+ * Prepare the buffer for reading in more cipher text.
+ */
+static void
+prepareDecryptBufferForRead(SchannelContext *ctx)
+{
+ /*
+ * should've consumed all the previous decrypted data in the buffer befor
+ * decrypting more.
+ */
+ Assert(ctx->decryptedLen == 0);
+
+ /*
+ * If there's any ciphertext left in the buffer from previous round, move
+ * it to beginning.
+ */
+ if (ctx->extraLen > 0)
+ memmove(ctx->decryptBuffer, ctx->extraData, ctx->extraLen);
+ ctx->decryptBufferLen = ctx->extraLen;
+ ctx->extraData = NULL;
+ ctx->extraLen = 0;
+ ctx->decryptedData = NULL;
+ ctx->decryptedLen = 0;
+}
+
+/*
+ * Try to decrypt the data currently in decryptBuffer. On success,
+ * decryptedData points to the decrypted data. DecryptMessage decrypts
+ * in-place, so decryptedData points to somewhere within the buffer!
+ *
+ * On success, returns 1. On error, returns -1. If more raw data is needed
+ * to decrypt, returns 0. If the client shut down the connection, also
+ * returns 0, and sets ctx->remote_shutdown flag.
+ */
+static int
+decryptBuffer(SchannelContext *ctx)
+{
+ /*
+ * Call DecryptBuffer until it returns some data, fails, or indicates that
+ * it needs more input
+ */
+ while(ctx->decryptedLen == 0)
+ {
+ SecBufferDesc sbufdesc;
+ SecBuffer sbufs[4];
+ SECURITY_STATUS rc;
+ int i;
+
+ Assert(ctx->decryptBufferLen > 0);
+ Assert(ctx->extraLen == 0);
+
+ sbufs[0].pvBuffer = ctx->decryptBuffer;
+ sbufs[0].cbBuffer = ctx->decryptBufferLen;
+ sbufs[0].BufferType = SECBUFFER_DATA;
+ sbufs[1].pvBuffer = NULL;
+ sbufs[1].cbBuffer = 0;
+ sbufs[1].BufferType = SECBUFFER_EMPTY;
+ sbufs[2].pvBuffer = NULL;
+ sbufs[2].cbBuffer = 0;
+ sbufs[2].BufferType = SECBUFFER_EMPTY;
+ sbufs[3].pvBuffer = NULL;
+ sbufs[3].cbBuffer = 0;
+ sbufs[3].BufferType = SECBUFFER_EMPTY;
+
+ sbufdesc.ulVersion = SECBUFFER_VERSION;
+ sbufdesc.cBuffers = 4;
+ sbufdesc.pBuffers = sbufs;
+
+ rc = DecryptMessage(&ctx->ctxHandle, &sbufdesc, 0, NULL);
+
+ if (rc == SEC_E_INCOMPLETE_MESSAGE)
+ {
+ /* We don't have the full message yet. Need more data. */
+ return 0;
+ }
+ if (rc == SEC_I_CONTEXT_EXPIRED)
+ {
+ elog(SCHANNEL_DEBUG, "DecryptMessage returned SEC_I_CONTEXT_EXPIRED");
+ ctx->remote_shutdown = true;
+ return 0;
+ }
+ if (rc == SEC_I_RENEGOTIATE)
+ {
+ /* TODO */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("SSL renegotiated not implemented")));
+ }
+ if (rc != SEC_E_OK)
+ {
+ /* FIXME: set errno */
+ elog(COMMERROR, "DecryptMessage failed: %s", getSecurityStatus(rc));
+ return -1;
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ if (sbufs[i].BufferType == SECBUFFER_EXTRA)
+ {
+ ctx->extraData = sbufs[i].pvBuffer;
+ ctx->extraLen = sbufs[i].cbBuffer;
+ }
+ if (sbufs[i].BufferType == SECBUFFER_DATA)
+ {
+ ctx->decryptedData = sbufs[i].pvBuffer;
+ ctx->decryptedLen = sbufs[i].cbBuffer;
+ }
+ }
+ if (ctx->decryptedLen == 0)
+ {
+ /*
+ * DecryptMessage didn't decrypt anything. Shift the extra data
+ * to beginning of buffer, and retry.
+ */
+ prepareDecryptBufferForRead(ctx);
+ }
+ }
+
+ return 1;
+}
+
+/**** Certificate handling functions ****/
+
+static char *
+slurpFile(const char *path, int *size)
+{
+ struct stat statbuf;
+ FILE *fp;
+ char *content;
+ int r;
+
+ if (stat(path, &statbuf) != 0)
+ {
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not stat certificate file \"%s\": %m",
+ path)));
+ }
+ fp = AllocateFile(path, "r");
+ if (!fp)
+ {
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read certificate file \"%s\": %m",
+ path)));
+ }
+ content = palloc(statbuf.st_size + 1);
+ r = fread(content, statbuf.st_size, 1, fp);
+ content[statbuf.st_size] = '\0';
+
+ if (r != 1 || ferror(fp) || FreeFile(fp))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read file \"%s\": %m",
+ path)));
+
+ if (size)
+ *size = statbuf.st_size;
+
+ return content;
+}
+
+/*
+ * Import the server's private key into the key container.
+ */
+static void
+importServerKey(void)
+{
+ char *content;
+ int filelen;
+ char buf[50000];
+ DWORD buflen = 50000;
+ char privKeyBlob[50000];
+ DWORD privKeyBlobLen = 50000;
+ HCRYPTPROV cryptProvHandle;
+ HCRYPTKEY keyHandle;
+
+ content = slurpFile(ssl_key_file, &filelen);
+
+ /* convert from PEM to DER */
+ buflen = 50000;
+ if (!CryptStringToBinary(content, filelen, CRYPT_STRING_BASE64HEADER,
+ buf, &buflen, NULL, NULL))
+ {
+ elog(ERROR, "CryptStringToBinary (key) failed: 0x%x", GetLastError());
+ }
+ pfree(content);
+
+ if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ PKCS_RSA_PRIVATE_KEY,
+ buf, buflen,
+ 0,
+ NULL,
+ privKeyBlob,
+ &privKeyBlobLen))
+ {
+ elog(ERROR, "CryptDecodeObjectEx (key) failed: 0x%x", GetLastError());
+ }
+
+ /* Open the key container */
+ if (!CryptAcquireContextW(&cryptProvHandle, PG_KEY_CONTAINER_NAME, MS_DEF_RSA_SCHANNEL_PROV_W, PROV_RSA_SCHANNEL, 0))
+ {
+ if (GetLastError() == NTE_BAD_KEYSET)
+ {
+ /*
+ * The key container could not be accessed. Most probable cause
+ * is that it doesn't exist, so try to create it.
+ */
+ if (!CryptAcquireContextW(&cryptProvHandle, PG_KEY_CONTAINER_NAME, MS_DEF_RSA_SCHANNEL_PROV_W, PROV_RSA_SCHANNEL, CRYPT_NEWKEYSET))
+ {
+ elog(ERROR, "CryptAcquireContext failed: 0x%x", GetLastError());
+ }
+ else
+ elog(LOG, "SChannel key container created");
+ }
+ else
+ elog(ERROR, "CryptAcquireContext failed: 0x%x", GetLastError());
+ }
+
+ if (!CryptImportKey(cryptProvHandle, privKeyBlob, privKeyBlobLen,
+ (HCRYPTKEY) NULL, 0, &keyHandle))
+ {
+ elog(ERROR, "CryptImportKey failed: 0x%x", GetLastError());
+ }
+
+ CryptDestroyKey(keyHandle);
+
+ if (!CryptReleaseContext(cryptProvHandle, 0))
+ elog(ERROR, "CryptReleaseContext failed: 0x%x", GetLastError());
+}
+
+static CERT_CONTEXT *
+getServerCert(void)
+{
+ CERT_CONTEXT *cert_cxt;
+ char *content;
+ int filelen;
+ char buf[50000];
+ DWORD buflen = 50000;
+ CRYPT_KEY_PROV_INFO keyProvInfo;
+
+ content = slurpFile(ssl_cert_file, &filelen);
+
+ /* convert from PEM to DER */
+ if (!CryptStringToBinary(content, filelen, CRYPT_STRING_BASE64HEADER,
+ buf, &buflen, NULL, NULL))
+ {
+ elog(ERROR, "CryptStringToBinary failed: 0x%x", GetLastError());
+ }
+
+ cert_cxt = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ buf,
+ buflen);
+ if (cert_cxt == NULL)
+ {
+ elog(ERROR, "CertCreateCertificateContext failed: 0x%x", GetLastError());
+ }
+ pfree(content);
+
+ /* Tell the Crypto API how to find the private key */
+ memset(&keyProvInfo, 0, sizeof(keyProvInfo));
+ keyProvInfo.pwszContainerName = PG_KEY_CONTAINER_NAME;
+ keyProvInfo.pwszProvName = MS_DEF_RSA_SCHANNEL_PROV_W;
+ keyProvInfo.dwProvType = PROV_RSA_SCHANNEL;
+ keyProvInfo.dwKeySpec = AT_KEYEXCHANGE;
+
+ if (!CertSetCertificateContextProperty(cert_cxt,
+ CERT_KEY_PROV_INFO_PROP_ID,
+ 0,
+ &keyProvInfo))
+ {
+ elog(ERROR, "CertSetCertificateContextProperty failed: %x", GetLastError());
+ }
+
+ return cert_cxt;
+}
+
+static bool
+validateClientCert(CERT_CONTEXT *cert)
+{
+ CERT_CONTEXT *rootCert;
+ DWORD flags;
+
+ /*
+ * Per MSDN documentation [1], we must check the following things:
+ *
+ * 1. The certificate chain is complete and the root is a certificate from
+ * a trusted certification authority (CA).
+ * 2. The current time is not beyond the begin and end dates for each of
+ * the certificates in the certificate chain.
+ * 3. None of the certificates in the certificate chain have been revoked.
+ * 4. The depth of the leaf certificate is not deeper than the maximum
+ * allowable depth specified in the certificate extension. This check is
+ * only necessary if there is a depth specified.
+ * 5. The usage of the certificate is correct, for example, a client
+ * certificate should not be used to authenticate a server.
+ *
+ * [1] "Manually Validating Schannel Credential",
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa378740%28v=vs.85%29.aspx
+ */
+
+ /*
+ * 1. Check the chain
+ * 2. Check expiration
+ *
+ * TODO: Currently, we only support client certs signed by the root client
+ * CA. Intermediary CA's are not supported.
+ */
+ rootCert = getClientRootCert();
+
+ /* TODO: CRL's are not actually supported */
+ flags = CERT_STORE_SIGNATURE_FLAG |
+ CERT_STORE_TIME_VALIDITY_FLAG;
+ if (!CertVerifySubjectCertificateContext(
+ cert,
+ rootCert,
+ &flags))
+ {
+ elog(LOG, "failed to verify client cert");
+ return false;
+ }
+ if (flags != 0)
+ {
+ if ((flags & CERT_STORE_SIGNATURE_FLAG) != 0)
+ elog(LOG, "signature check failed on client cert");
+ if ((flags & CERT_STORE_REVOCATION_FLAG) != 0)
+ elog(LOG, "client cert was revoked");
+ if ((flags & CERT_STORE_TIME_VALIDITY_FLAG) != 0)
+ elog(LOG, "client cert is not valid at this time");
+
+ return false;
+ }
+
+ /*
+ * We succeeded to get the issuer's certificate from the trusted root store,
+ * and the signature on the client cert passed, so we're good to go.
+ */
+
+ return true;
+}
+
+static CERT_CONTEXT *
+getClientRootCert(void)
+{
+ char *client_ca_file;
+ int client_ca_file_len;
+ char crtBuf[50000]; /* TODO: enlarge as needed */
+ DWORD crtBufLen = 50000;
+ CERT_CONTEXT *client_root_cxt;
+
+ client_ca_file = slurpFile(ssl_ca_file, &client_ca_file_len);
+ if (client_ca_file == NULL)
+ return NULL;
+
+ /* convert from PEM to DER */
+ if (!CryptStringToBinary(
+ client_ca_file,
+ client_ca_file_len,
+ CRYPT_STRING_BASE64HEADER,
+ crtBuf,
+ &crtBufLen,
+ NULL,
+ NULL))
+ {
+ elog(ERROR, "failed to convert client root cert from PEM to DER format");
+ }
+
+ client_root_cxt = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ crtBuf,
+ crtBufLen);
+ if (client_root_cxt == NULL)
+ elog(ERROR, "CertCreateCertificateContext failed: 0x%x", GetLastError());
+
+ return client_root_cxt;
+}
+
+/*** Printing error codes ***/
+
+#define ERRORCODE_STR(x) { x, #x }
+
+typedef struct
+{
+ uint32 rc;
+ const char *str;
+} errorcode_str;
+
+static const char *
+getErrorStr(uint32 rc, errorcode_str *map)
+{
+ static char buf[100];
+ int i;
+
+ for (i = 0; map[i].str != NULL; i++)
+ {
+ if (map[i].rc == rc)
+ return map[i].str;
+ }
+ snprintf(buf, sizeof(buf), "0x%X", rc);
+ return buf;
+}
+
+static const char *
+getSecurityStatus(SECURITY_STATUS rc)
+{
+ static errorcode_str sec_status_codes[] = {
+ ERRORCODE_STR(SEC_E_OK),
+ ERRORCODE_STR(SEC_E_INCOMPLETE_MESSAGE),
+ ERRORCODE_STR(SEC_E_INSUFFICIENT_MEMORY),
+ ERRORCODE_STR(SEC_E_INTERNAL_ERROR),
+ ERRORCODE_STR(SEC_E_INVALID_HANDLE),
+ ERRORCODE_STR(SEC_E_INVALID_TOKEN),
+ ERRORCODE_STR(SEC_E_LOGON_DENIED),
+ ERRORCODE_STR(SEC_E_NO_AUTHENTICATING_AUTHORITY),
+ ERRORCODE_STR(SEC_E_NO_CREDENTIALS),
+ ERRORCODE_STR(SEC_I_COMPLETE_AND_CONTINUE),
+ ERRORCODE_STR(SEC_I_COMPLETE_NEEDED),
+ ERRORCODE_STR(SEC_I_CONTINUE_NEEDED),
+ ERRORCODE_STR(SEC_E_ILLEGAL_MESSAGE),
+ { 0, NULL }
+ };
+ return getErrorStr(rc, sec_status_codes);
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 41ec1ad..fcd5ea2 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -103,6 +103,10 @@ secure_open_server(Port *port)
r = be_tls_open_server(port);
#endif
+ ereport(DEBUG2,
+ (errmsg("SSL connection from \"%s\"",
+ port->peer_cn ? port->peer_cn : "(anonymous)")));
+
return r;
}
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 34e52e4..7fcee98 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -53,6 +53,10 @@
#include <security.h>
#undef SECURITY_WIN32
+#ifdef USE_WINDOWS_SCHANNEL
+struct SchannelContext;
+#endif
+
#ifndef ENABLE_GSS
/*
* Define a fake structure compatible with GSSAPI on Unix.
@@ -197,6 +201,9 @@ typedef struct Port
X509 *peer;
unsigned long count;
#endif
+#ifdef USE_WINDOWS_SCHANNEL
+ struct SchannelContext *windows_schannel;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 5bdfa47..e4d8574 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -802,6 +802,9 @@
/* Define to select Win32-style shared memory. */
#undef USE_WIN32_SHARED_MEMORY
+/* Define to build with Windows SChannel support. (--with-winschannel) */
+#undef USE_WINDOWS_SCHANNEL
+
/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
significant byte first (like Motorola and SPARC, unlike Intel). */
#if defined AC_APPLE_UNIVERSAL_BUILD
diff --git a/src/include/pg_config.h.win32 b/src/include/pg_config.h.win32
index 00be15f..901de03 100644
--- a/src/include/pg_config.h.win32
+++ b/src/include/pg_config.h.win32
@@ -649,6 +649,9 @@
/* Define to select Win32-style semaphores. */
#define USE_WIN32_SEMAPHORES 1
+/* Define to build with Windows SChannel support. (--with-winschannel) */
+#undef USE_WINDOWS_SCHANNEL
+
/* Number of bits in a file offset, on hosts where this is settable. */
/* #undef _FILE_OFFSET_BITS */
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index d78f38e..54ed4b2 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -146,10 +146,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined (USE_OPENSSL) || defined(USE_WINDOWS_SCHANNEL)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index a90cb89..7968266 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -48,6 +48,10 @@ ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o
endif
+ifeq ($(with_winschannel),yes)
+OBJS += fe-secure-winschannel.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index f950fc3..cee7b2e 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -780,57 +780,21 @@ destroy_ssl_system(void)
static int
initialize_SSL(PGconn *conn)
{
- struct stat buf;
- char homedir[MAXPGPATH];
- char fnbuf[MAXPGPATH];
- char sebuf[256];
- bool have_homedir;
- bool have_cert;
EVP_PKEY *pkey = NULL;
-
- /*
- * 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) ||
- !(conn->sslcrl && strlen(conn->sslcrl) > 0))
- have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
- else /* won't need it */
- have_homedir = false;
-
- /* Read the client certificate file */
- if (conn->sslcert && strlen(conn->sslcert) > 0)
- strncpy(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')
- {
- /* no home directory, proceed without a client cert */
- have_cert = false;
- }
- else if (stat(fnbuf, &buf) != 0)
- {
- /*
- * If file is not present, just go on without a client cert; server
- * might or might not accept the connection. Any other error,
- * however, is grounds for complaint.
- */
- if (errno != ENOENT && errno != ENOTDIR)
- {
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not open certificate file \"%s\": %s\n"),
- fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
- return -1;
- }
- have_cert = false;
- }
- else
+ char *sslcertfile = NULL;
+ char *engine = NULL;
+ char *keyname = NULL;
+ char *sslkeyfile = NULL;
+ char *sslrootcert = NULL;
+ char *sslcrl = NULL;
+ int ret = -1;
+
+ if (!pqsecure_get_ssl_files(conn,
+ &sslcertfile, &sslkeyfile, &engine, &keyname,
+ &sslrootcert, &sslcrl))
+ return PGRES_POLLING_READING;
+
+ if (sslcertfile)
{
/*
* Cert file exists, so load it. Since OpenSSL doesn't provide the
@@ -855,216 +819,146 @@ initialize_SSL(PGconn *conn)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
- return -1;
+ goto fail;
}
#endif
- if (SSL_CTX_use_certificate_chain_file(SSL_context, fnbuf) != 1)
+ if (SSL_CTX_use_certificate_chain_file(SSL_context, sslcertfile) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read certificate file \"%s\": %s\n"),
- fnbuf, err);
+ sslcertfile, err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
- return -1;
+ goto fail;
}
- if (SSL_use_certificate_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
+ if (SSL_use_certificate_file(conn->ssl, sslcertfile, SSL_FILETYPE_PEM) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read certificate file \"%s\": %s\n"),
- fnbuf, err);
+ sslcertfile, err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
- return -1;
+ goto fail;
}
- /* need to load the associated private key, too */
- have_cert = true;
-
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
}
/*
- * Read the SSL key. If a key is specified, treat it as an engine:key
- * combination if there is colon present - we don't support files with
- * colon in the name. The exception is if the second character is a colon,
- * in which case it can be a Windows filename with drive specification.
+ * If an engine:key specification was given, load that.
*/
- if (have_cert && conn->sslkey && strlen(conn->sslkey) > 0)
+ if (engine)
{
#ifdef USE_SSL_ENGINE
- if (strchr(conn->sslkey, ':')
-#ifdef WIN32
- && conn->sslkey[1] != ':'
-#endif
- )
+ conn->engine = ENGINE_by_id(engine);
+ if (conn->engine == NULL)
{
- /* Colon, but not in second character, treat as engine:key */
- char *engine_str = strdup(conn->sslkey);
- char *engine_colon;
-
- if (engine_str == NULL)
- {
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("out of memory\n"));
- return -1;
- }
-
- /* cannot return NULL because we already checked before strdup */
- engine_colon = strchr(engine_str, ':');
-
- *engine_colon = '\0'; /* engine_str now has engine name */
- engine_colon++; /* engine_colon now has key name */
-
- conn->engine = ENGINE_by_id(engine_str);
- if (conn->engine == NULL)
- {
- char *err = SSLerrmessage();
+ char *err = SSLerrmessage();
- printfPQExpBuffer(&conn->errorMessage,
+ printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not load SSL engine \"%s\": %s\n"),
- engine_str, err);
- SSLerrfree(err);
- free(engine_str);
- return -1;
- }
+ engine, err);
+ SSLerrfree(err);
+ goto fail;
+ }
- if (ENGINE_init(conn->engine) == 0)
- {
- char *err = SSLerrmessage();
+ if (ENGINE_init(conn->engine) == 0)
+ {
+ char *err = SSLerrmessage();
- printfPQExpBuffer(&conn->errorMessage,
+ printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
- engine_str, err);
- SSLerrfree(err);
- ENGINE_free(conn->engine);
- conn->engine = NULL;
- free(engine_str);
- return -1;
- }
-
- pkey = ENGINE_load_private_key(conn->engine, engine_colon,
- NULL, NULL);
- if (pkey == NULL)
- {
- char *err = SSLerrmessage();
-
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
- engine_colon, engine_str, err);
- SSLerrfree(err);
- ENGINE_finish(conn->engine);
- ENGINE_free(conn->engine);
- conn->engine = NULL;
- free(engine_str);
- return -1;
- }
- if (SSL_use_PrivateKey(conn->ssl, pkey) != 1)
- {
- char *err = SSLerrmessage();
-
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"),
- engine_colon, engine_str, err);
- SSLerrfree(err);
- ENGINE_finish(conn->engine);
- ENGINE_free(conn->engine);
- conn->engine = NULL;
- free(engine_str);
- return -1;
- }
-
- free(engine_str);
-
- fnbuf[0] = '\0'; /* indicate we're not going to load from a
- * file */
- }
- else
-#endif /* USE_SSL_ENGINE */
- {
- /* PGSSLKEY is not an engine, treat it as a filename */
- strncpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ engine, err);
+ SSLerrfree(err);
+ ENGINE_free(conn->engine);
+ conn->engine = NULL;
+ goto fail;
}
- }
- else if (have_homedir)
- {
- /* No PGSSLKEY specified, load default file */
- snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
- }
- else
- fnbuf[0] = '\0';
- if (have_cert && fnbuf[0] != '\0')
- {
- /* read the client key from file */
-
- if (stat(fnbuf, &buf) != 0)
+ pkey = ENGINE_load_private_key(conn->engine, keyname, NULL, NULL);
+ if (pkey == NULL)
{
+ char *err = SSLerrmessage();
+
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("certificate present, but not private key file \"%s\"\n"),
- fnbuf);
- return -1;
+ libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
+ keyname, engine, err);
+ SSLerrfree(err);
+ ENGINE_finish(conn->engine);
+ ENGINE_free(conn->engine);
+ conn->engine = NULL;
+ goto fail;
}
-#ifndef WIN32
- if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+ if (SSL_use_PrivateKey(conn->ssl, pkey) != 1)
{
+ char *err = SSLerrmessage();
+
printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
- fnbuf);
- return -1;
+ libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"),
+ keyname, engine, err);
+ SSLerrfree(err);
+ ENGINE_finish(conn->engine);
+ ENGINE_free(conn->engine);
+ conn->engine = NULL;
+ goto fail;
}
-#endif
+#else
+ /*
+ * should not happen; pqsecure_get_ssl_files doesn't return an
+ * engine spec if not compiled with USE_SSL_ENGINE.
+ */
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("engine not supported\n"));
+ goto fail;
+#endif /* USE_SSL_ENGINE */
+ }
- if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_PEM) != 1)
+ /* Read the client private key file */
+ if (sslkeyfile)
+ {
+ if (SSL_use_PrivateKey_file(conn->ssl, sslkeyfile, SSL_FILETYPE_PEM) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not load private key file \"%s\": %s\n"),
- fnbuf, err);
+ sslkeyfile, err);
SSLerrfree(err);
- return -1;
+ goto fail;
}
}
- /* verify that the cert and key go together */
- if (have_cert &&
+ /* Verify that the cert and key go together */
+ if (sslcertfile &&
SSL_check_private_key(conn->ssl) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("certificate does not match private key file \"%s\": %s\n"),
- fnbuf, err);
+ sslkeyfile, err);
SSLerrfree(err);
- return -1;
+ goto fail;
}
/*
- * If the root cert file exists, load it so we can perform certificate
- * verification. If sslmode is "verify-full" we will also do further
- * verification after the connection has been completed.
+ * Load root cert, if exists. (pqsecure_get_ssl_files already complained
+ * if no root cert was configured but sslmode requires verification
+ * of server certificates.)
*/
- if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
- strncpy(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' &&
- stat(fnbuf, &buf) == 0)
+ if (sslrootcert)
{
X509_STORE *cvstore;
@@ -1075,35 +969,28 @@ initialize_SSL(PGconn *conn)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not acquire mutex: %s\n"), strerror(rc));
- return -1;
+ goto fail;
}
#endif
- if (SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL) != 1)
+ if (SSL_CTX_load_verify_locations(SSL_context, sslrootcert, NULL) != 1)
{
char *err = SSLerrmessage();
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not read root certificate file \"%s\": %s\n"),
- fnbuf, err);
+ sslrootcert, err);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
- return -1;
+ goto fail;
}
if ((cvstore = SSL_CTX_get_cert_store(SSL_context)) != NULL)
{
- if (conn->sslcrl && strlen(conn->sslcrl) > 0)
- strncpy(fnbuf, conn->sslcrl, sizeof(fnbuf));
- else if (have_homedir)
- snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
- else
- fnbuf[0] = '\0';
-
/* Set the flags to check against the complete CRL chain */
- if (fnbuf[0] != '\0' &&
- X509_STORE_load_locations(cvstore, fnbuf, NULL) == 1)
+ if (sslcrl &&
+ X509_STORE_load_locations(cvstore, sslcrl, NULL) == 1)
{
/* OpenSSL 0.96 does not support X509_V_FLAG_CRL_CHECK */
#ifdef X509_V_FLAG_CRL_CHECK
@@ -1114,12 +1001,12 @@ initialize_SSL(PGconn *conn)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSL library does not support CRL certificates (file \"%s\")\n"),
- fnbuf);
+ sslcrl);
SSLerrfree(err);
#ifdef ENABLE_THREAD_SAFETY
pthread_mutex_unlock(&ssl_config_mutex);
#endif
- return -1;
+ goto fail;
#endif
}
/* if not found, silently ignore; we do not require CRL */
@@ -1130,31 +1017,6 @@ initialize_SSL(PGconn *conn)
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, verify_cb);
}
- else
- {
- /*
- * 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 -1;
- }
- }
/*
* If the OpenSSL version used supports it (from 1.0.0 on) and the user
@@ -1167,9 +1029,27 @@ initialize_SSL(PGconn *conn)
}
#endif
- return 0;
+ /* success */
+ ret = 0;
+
+fail:
+ if (sslcertfile)
+ free(sslcertfile);
+ if (engine)
+ free(engine);
+ if (keyname)
+ free(keyname);
+ if (sslkeyfile)
+ free(sslkeyfile);
+ if (sslrootcert)
+ free(sslrootcert);
+ if (sslcrl)
+ free(sslcrl);
+
+ return ret;
}
+
/*
* Attempt to negotiate SSL connection.
*/
diff --git a/src/interfaces/libpq/fe-secure-winschannel.c b/src/interfaces/libpq/fe-secure-winschannel.c
new file mode 100644
index 0000000..4af5936
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-winschannel.c
@@ -0,0 +1,1353 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-winschannel.c
+ * Windows Schannel SSL implementation
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-winschannel.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres_fe.h"
+
+#include <ctype.h>
+#include <fcntl.h>
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+
+#include "win32.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <sspi.h>
+#include <schnlsp.h>
+
+#define SCHANNEL_DEBUG
+
+typedef enum
+{
+ ISC_INIT, /* before first call to InitializeSecurityContext */
+ ISC_NEED_READ, /* need to pass more input to InitializeSecurityContext */
+ ISC_NEED_WRITE, /* need to write data to client */
+ ISC_FINAL_WRITE, /* handshake complete after sending last output to client */
+ ISC_COMPLETED /* handshake complete */
+} HandshakeState;
+
+struct SchannelContext
+{
+ /* SChannel handle for the connection */
+ CtxtHandle ctxHandle;
+ bool ctxHandle_valid;
+ CredHandle credHandle;
+ bool credHandle_valid;
+
+ /* client certificate to send to server */
+ CERT_CONTEXT *clientCert;
+ /* Root certificate for validating server certificates */
+ CERT_CONTEXT *rootCert;
+
+ HandshakeState handshakeState;
+ bool remote_shutdown;
+
+ /*
+ * Buffer for encrypting data to be written.
+ *
+ * When encryptedLen > 0, the buffer contains some encrypted data that
+ * should be written out to the client.
+ *
+ * When encryptedLen == 0, call encryptBuffer() to load the buffer with
+ * more data, and encrypt it.
+ */
+ char *encryptBuffer;
+ int encryptBufferSize;
+ int encryptBufferLen;
+
+ char *encryptedData;
+ size_t encryptedLen;
+
+ /*
+ * Number of plaintext bytes the buffer currently holds in encrypted form.
+ * This is less than the number of encrypted bytes, because the SSL
+ * protocol adds framing. (it could also be more, when compression is used)
+ */
+ int plaintextLen;
+
+ /*
+ * Buffer for decrypting read data.
+ *
+ * decryptBufferSize is the allocated size of decryptBuffer.
+ * decryptBufferLen is the number of ciphertext bytes loaded to the buffer.
+ *
+ * The buffer can be in one of two states. In the first state, it contains
+ * zero or more raw bytes, and more can be loaded into it. In the other
+ * state, it contains decrypted data that can be read out.
+ *
+ * When the buffer contains decrypted data, decryptedLen > 0, and
+ * decryptedData points to the data. The buffer can also contain some
+ * leftover raw data that has not been decrypted yet (extraData/extraLen).
+ *
+ * Before reading more raw data to the buffer, call
+ * prepareDecryptBufferForRead. It moves any leftover raw data to the
+ * beginning of the buffer.
+ */
+ char *decryptBuffer;
+ int decryptBufferSize;
+ int decryptBufferLen; /* ciphertext bytes loaded in decryptBuffer */
+
+ /* decrypted data not returned to the caller yet */
+ char *decryptedData;
+ int decryptedLen;
+
+ char *extraData;
+ int extraLen;
+};
+
+typedef struct SchannelContext SchannelContext;
+
+/*
+ * Somewhat arbitrarily, use a 10k buffer for both input and output.
+ */
+#define BUFSIZE 10000
+
+/* Functions for managing the encrypt/decrypt buffers */
+static ssize_t encryptBuffer(PGconn *conn, const void *ptr, int len);
+static void prepareDecryptBufferForRead(SchannelContext *ctx);
+static int decryptBuffer(PGconn *conn);
+
+/* Functions for dealing with certificates */
+static bool loadCerts(PGconn *conn);
+static bool validateServerCert(PGconn *conn);
+
+/* Functions for converting error codes to strings */
+static const char *getSSLPolicyStatusCode(DWORD dwerror);
+static const char *getSecurityStatus(SECURITY_STATUS dwerror);
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+}
+
+
+/*
+ * Initialize SSL system, in particular creating the SSL_context object
+ * that will be shared by all SSL-using connections in this process.
+ *
+ * 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)
+{
+ /* nothing to do here ATM */
+ return 0;
+}
+
+
+/*
+ * Begin or continue negotiating a secure session.
+ */
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SchannelContext *ctx = conn->windows_schannel;
+ SecBufferDesc outsbufdesc;
+ SecBuffer outsbufs[1];
+ SecBufferDesc insbufdesc;
+ SecBuffer insbufs[2];
+ SECURITY_STATUS rc;
+ DWORD flags;
+ DWORD outflags;
+ int n;
+
+#ifdef SCHANNEL_DEBUG
+ fprintf(stderr, "pgtls_open_client called: %d\n", ctx ? ctx->handshakeState : -1);
+ fflush(stderr);
+#endif
+
+ /* On first call, allocate the context struct and buffers */
+ if (ctx == NULL)
+ {
+ ctx = (SchannelContext *) malloc(sizeof(SchannelContext));
+ if (ctx == NULL)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+ memset(ctx, 0, sizeof(SchannelContext));
+ conn->windows_schannel = ctx;
+
+ ctx->encryptBufferSize = BUFSIZE;
+ ctx->encryptBuffer = malloc(ctx->encryptBufferSize);
+ if (ctx->encryptBuffer == NULL)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ ctx->decryptBufferSize = BUFSIZE;
+ ctx->decryptBuffer = malloc(ctx->encryptBufferSize);
+ if (ctx->decryptBuffer == NULL)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+
+ ctx->handshakeState = ISC_INIT;
+
+ conn->windows_schannel = ctx;
+ conn->ssl_in_use = true;
+ }
+
+ if (!ctx->credHandle_valid)
+ {
+ SCHANNEL_CRED credData;
+
+ if (!loadCerts(conn))
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ memset(&credData, 0, sizeof(credData));
+ credData.dwVersion = SCHANNEL_CRED_VERSION;
+ credData.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK |
+ SCH_CRED_NO_DEFAULT_CREDS |
+ SCH_CRED_MANUAL_CRED_VALIDATION;
+ if (ctx->clientCert)
+ {
+ credData.cCreds = 1;
+ credData.paCred = &ctx->clientCert;
+ }
+ else
+ credData.cCreds = 0;
+
+ /* FIXME: check server cert */
+
+#ifdef SCHANNEL_DEBUG
+ fprintf(stderr, "calling AcquireCredentialsHandle\n");
+ fflush(stderr);
+#endif
+ rc = AcquireCredentialsHandle(NULL,
+ UNISP_NAME,
+ SECPKG_CRED_OUTBOUND,
+ NULL,
+ &credData,
+ NULL,
+ NULL,
+ &ctx->credHandle,
+ NULL);
+ if (rc != SEC_E_OK)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not acquire credentials handle: %s\n"),
+ getSecurityStatus(rc));
+ return PGRES_POLLING_FAILED;
+ }
+ ctx->credHandle_valid = true;
+
+#ifdef SCHANNEL_DEBUG
+ fprintf(stderr, "AcquireCredentialsHandle succeeded\n");
+ fflush(stderr);
+#endif
+ }
+
+ /* If we have any pending outgoing data, flush that first */
+ if (ctx->handshakeState == ISC_NEED_WRITE ||
+ ctx->handshakeState == ISC_FINAL_WRITE)
+ {
+#ifdef SCHANNEL_DEBUG
+ fprintf(stderr, "Sending %d handshake bytes\n",
+ ctx->encryptedLen);
+ fflush(stderr);
+#endif
+ n = pqsecure_raw_write(conn, ctx->encryptBuffer, ctx->encryptedLen);
+ if (n < 0)
+ {
+ int result_errno = SOCK_ERRNO;
+ if (result_errno == EINTR
+#ifdef EAGAIN
+ || result_errno == EAGAIN
+#endif
+#ifdef EWOULDBLOCK
+ || result_errno == EWOULDBLOCK)
+#endif
+ {
+ return PGRES_POLLING_WRITING;
+ }
+ else
+ {
+ pgtls_close(conn);
+ /* pqsecure_raw_write already set error message */
+ return PGRES_POLLING_FAILED;
+ }
+ }
+ ctx->encryptedData += n;
+ ctx->encryptedLen -= n;
+ if (ctx->encryptedLen > 0)
+ return PGRES_POLLING_WRITING;
+ else
+ {
+ if (ctx->handshakeState == ISC_NEED_WRITE)
+ {
+ /*
+ * Successfully sent the data to client that
+ * InitializeSecurityContext asked us to sent. Now wait for
+ * a reply.
+ */
+ ctx->handshakeState = ISC_NEED_READ;
+ }
+ else
+ {
+ ctx->handshakeState = ISC_COMPLETED;
+ return PGRES_POLLING_OK;
+ }
+ }
+ }
+
+ /* call InitializeSecurityContext */
+
+ flags = ISC_REQ_ALLOCATE_MEMORY |
+ ISC_REQ_CONFIDENTIALITY |
+ ISC_RET_EXTENDED_ERROR |
+ ISC_REQ_REPLAY_DETECT |
+ ISC_REQ_SEQUENCE_DETECT |
+ ISC_REQ_STREAM |
+ ISC_REQ_MANUAL_CRED_VALIDATION;
+
+ outsbufs[0].pvBuffer = NULL;
+ outsbufs[0].BufferType = SECBUFFER_TOKEN;
+ outsbufs[0].cbBuffer = 0;
+
+ outsbufdesc.cBuffers = 1;
+ outsbufdesc.pBuffers = outsbufs;
+ outsbufdesc.ulVersion = SECBUFFER_VERSION;
+
+ /* Read some data if needed */
+ if (ctx->handshakeState == ISC_NEED_READ)
+ {
+ int maxsize;
+ int readlen;
+
+ /* read more data to buffer */
+ maxsize = ctx->decryptBufferSize - ctx->decryptBufferLen;
+#ifdef SCHANNEL_DEBUG
+ fprintf(stderr, "reading maxsize %d buflen %d\n", maxsize, ctx->decryptBufferLen);
+ fflush(stderr);
+#endif
+ readlen = pqsecure_raw_read(conn,
+ &ctx->decryptBuffer[ctx->decryptBufferLen],
+ maxsize);
+ fprintf(stderr, "received %d bytes in handshake\n", readlen);
+ fflush(stderr);
+ if (readlen < 0)
+ {
+ int result_errno = SOCK_ERRNO;
+ if (result_errno == EINTR
+#ifdef EAGAIN
+ || result_errno == EAGAIN
+#endif
+#ifdef EWOULDBLOCK
+ || result_errno == EWOULDBLOCK)
+#endif
+ {
+ return PGRES_POLLING_READING;
+ }
+
+ pgtls_close(conn);
+ /* pqsecure_raw_read set error message */
+ return PGRES_POLLING_FAILED;
+ }
+
+ ctx->decryptBufferLen += readlen;
+ }
+
+ /*
+ * On first call, don't pass input buffer. The function will return
+ * a context. On subsequent calls, pass any input data in a buffer,
+ * and pass the handle we got on first call.
+ */
+ if (ctx->handshakeState == ISC_INIT)
+ {
+ rc = InitializeSecurityContext(&ctx->credHandle,
+ NULL,
+ NULL,
+ flags,
+ 0,
+ 0,
+ NULL,
+ 0,
+ &ctx->ctxHandle,
+ &outsbufdesc,
+ &outflags,
+ NULL);
+ }
+ else
+ {
+ insbufs[0].pvBuffer = ctx->decryptBuffer;
+ insbufs[0].cbBuffer = ctx->decryptBufferLen;
+ insbufs[0].BufferType = SECBUFFER_TOKEN;
+
+ insbufs[1].pvBuffer = NULL;
+ insbufs[1].cbBuffer = 0;
+ insbufs[1].BufferType = SECBUFFER_EMPTY;
+
+ insbufdesc.cBuffers = 2;
+ insbufdesc.pBuffers = insbufs;
+ insbufdesc.ulVersion = SECBUFFER_VERSION;
+
+ rc = InitializeSecurityContext(&ctx->credHandle,
+ &ctx->ctxHandle,
+ NULL,
+ flags,
+ 0,
+ 0,
+ &insbufdesc,
+ 0,
+ NULL,
+ &outsbufdesc,
+ &outflags,
+ NULL);
+ /*
+ * The following return codes are considered success, in the sense
+ * that a context was created and must eventually be destroyed to
+ * avoid leaking memory.
+ */
+ if (rc == SEC_I_COMPLETE_AND_CONTINUE ||
+ rc == SEC_I_COMPLETE_NEEDED ||
+ rc == SEC_I_CONTINUE_NEEDED ||
+ rc == SEC_I_INCOMPLETE_CREDENTIALS ||
+ rc == SEC_E_INCOMPLETE_MESSAGE ||
+ rc == SEC_E_OK)
+ {
+ ctx->ctxHandle_valid = true;
+ }
+ }
+#ifdef SCHANNEL_DEBUG
+ fprintf(stderr, "InitializeSecurityContext returned %x\n", (unsigned int) rc);
+ fflush(stderr);
+#endif
+
+ if (rc == SEC_E_OK || rc == SEC_I_CONTINUE_NEEDED)
+ {
+ size_t outLen;
+
+ /*
+ * InitializeSecurityContext might not have consumed all of the input
+ * data.
+ */
+ if (ctx->handshakeState == ISC_NEED_READ)
+ {
+ if (insbufs[1].BufferType == SECBUFFER_EXTRA)
+ {
+ int extraLen = insbufs[1].cbBuffer;
+
+ memmove(ctx->decryptBuffer,
+ &ctx->decryptBuffer[ctx->decryptBufferLen - extraLen],
+ extraLen);
+ ctx->decryptBufferLen = extraLen;
+ }
+ else
+ ctx->decryptBufferLen = 0;
+ }
+
+ /*
+ * Must send the token that InitializeSecurityContext put in the
+ * output buffer (if any).
+ */
+ outLen = outsbufs[0].cbBuffer;
+ if (outLen > 0)
+ {
+ /* enlarge the output buffer if required. */
+ if (outLen > ctx->encryptBufferSize)
+ {
+ char *newbuf;
+ newbuf = realloc(ctx->encryptBuffer, outsbufs[0].cbBuffer);
+ if (newbuf == NULL)
+ {
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ return PGRES_POLLING_FAILED;
+ }
+ ctx->encryptBuffer = newbuf;
+ ctx->encryptBufferSize = outLen;
+ }
+ memcpy(ctx->encryptBuffer, outsbufs[0].pvBuffer, outLen);
+ ctx->encryptedData = ctx->encryptBuffer;
+ ctx->encryptedLen = outLen;
+
+ FreeContextBuffer(outsbufs[0].pvBuffer);
+ }
+ }
+
+ switch(rc)
+ {
+ case SEC_E_OK:
+ /*
+ * SSL handshake is complete. But we might still have some
+ * data pending in the output buffer.
+ */
+ if (!validateServerCert(conn))
+ {
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+ }
+
+ if (ctx->encryptedLen > 0)
+ {
+ ctx->handshakeState = ISC_FINAL_WRITE;
+ return PGRES_POLLING_WRITING;
+ }
+
+ return PGRES_POLLING_OK;
+
+ case SEC_I_COMPLETE_AND_CONTINUE:
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ "SEC_I_COMPLETE_AND_CONTINUE not implemented\n", rc);
+ return PGRES_POLLING_FAILED;
+
+ case SEC_I_COMPLETE_NEEDED:
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ "SEC_I_COMPLETE_NEEDED not implemented\n", rc);
+ return PGRES_POLLING_FAILED;
+
+ case SEC_I_CONTINUE_NEEDED:
+ /*
+ * Write the output data that InitializeSecurityContext returned,
+ * and wait for reply.
+ */
+ ctx->handshakeState = ISC_NEED_WRITE;
+ return PGRES_POLLING_WRITING;
+
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ "SEC_I_INCOMPLETE_CREDENTIALS not implemented\n", rc);
+ return PGRES_POLLING_FAILED;
+
+ case SEC_E_INCOMPLETE_MESSAGE:
+ /* read more input data and retry */
+ ctx->handshakeState = ISC_NEED_READ;
+ return PGRES_POLLING_READING;
+
+ default:
+ pgtls_close(conn);
+ printfPQExpBuffer(&conn->errorMessage,
+ "InitializeSecurityContext failed: %s\n",
+ getSecurityStatus(rc));
+ return PGRES_POLLING_FAILED;
+ }
+ /* not reached */
+
+fail:
+ pgtls_close(conn);
+ return PGRES_POLLING_FAILED;
+}
+
+/*
+ * Close SSL connection.
+ */
+void
+pgtls_close(PGconn *conn)
+{
+ SchannelContext *ctx = conn->windows_schannel;
+
+ /* TODO: should do a proper SSL shutdown. */
+
+ if (ctx)
+ {
+ if (ctx->encryptBuffer)
+ free(ctx->encryptBuffer);
+ if (ctx->decryptBuffer)
+ free(ctx->decryptBuffer);
+
+ if (ctx->credHandle_valid)
+ FreeCredentialsHandle(&ctx->credHandle);
+
+ if (ctx->ctxHandle_valid)
+ DeleteSecurityContext(&ctx->ctxHandle);
+
+ free(ctx);
+ conn->windows_schannel = NULL;
+ }
+ conn->ssl_in_use = false;
+}
+
+/*
+ * 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 determine whether to continue/retry after error.
+ */
+ssize_t
+pgtls_read(PGconn *conn, char *buffer, size_t len)
+{
+ SchannelContext *ctx = conn->windows_schannel;
+ int readlen;
+ int maxsize;
+ size_t totalreadlen = 0;
+ char *dst = buffer;
+
+ for (;;)
+ {
+ /* if we already have some decrypted data, copy it to the target buffer */
+ if (ctx->decryptedLen > 0)
+ {
+ maxsize = len - totalreadlen;
+ if (maxsize > ctx->decryptedLen)
+ maxsize = ctx->decryptedLen;
+ memcpy(dst, &ctx->decryptedData[totalreadlen], maxsize);
+ dst += maxsize;
+ totalreadlen += maxsize;
+ ctx->decryptedLen -= maxsize;
+ ctx->decryptedData += maxsize;
+ }
+
+ /* If we have some data to return, return it. */
+ if (totalreadlen > 0)
+ {
+ return totalreadlen;
+ }
+
+ /*
+ * there shouldn't be any decrypted data left in the buffer, we copied
+ * it all to the target.
+ */
+ Assert(ctx->decryptedLen == 0);
+
+ /*
+ * Need to decrypt more data, and for that, need to read more raw
+ * data.
+ */
+ prepareDecryptBufferForRead(ctx);
+ while (ctx->decryptedLen == 0)
+ {
+ /* read more data to buffer */
+ maxsize = ctx->decryptBufferSize - ctx->decryptBufferLen;
+ readlen = pqsecure_raw_read(conn,
+ &ctx->decryptBuffer[ctx->decryptBufferLen],
+ maxsize);
+ if (readlen < 0)
+ return readlen;
+ if (readlen == 0)
+ {
+ /* this implies we're in non-blocking mode */
+ return totalreadlen;
+ }
+
+ ctx->decryptBufferLen += readlen;
+
+ /* decrypt what we got */
+ if (decryptBuffer(conn) == -1)
+ return -1;
+ if (ctx->remote_shutdown)
+ return totalreadlen;
+
+ /* If decryptBuffer needs more data, decrypteLen is still 0. */
+ }
+ }
+
+ return totalreadlen;
+}
+
+
+/*
+ * Prepare the buffer for reading in more cipher text.
+ */
+static void
+prepareDecryptBufferForRead(SchannelContext *ctx)
+{
+ /*
+ * should've consumed all the previous decrypted data in the buffer befor
+ * decrypting more.
+ */
+ Assert(ctx->decryptedLen == 0);
+
+ /*
+ * If there's any ciphertext left in the buffer from previous round, move
+ * it to beginning.
+ */
+ if (ctx->extraLen > 0)
+ memmove(ctx->decryptBuffer, ctx->extraData, ctx->extraLen);
+ ctx->decryptBufferLen = ctx->extraLen;
+ ctx->extraData = NULL;
+ ctx->extraLen = 0;
+ ctx->decryptedData = NULL;
+ ctx->decryptedLen = 0;
+}
+
+/*
+ * Try to decrypt the data currently in decryptBuffer. On success,
+ * decryptedData points to the decrypted data. DecryptMessage decrypts
+ * in-place, so decryptedData points to somewhere within the buffer!
+ *
+ * On success, returns 1. On error, returns -1. If more raw data is needed
+ * to decrypt, returns 0. If the client shut down the connection, also
+ * returns 0, and sets ctx->remote_shutdown flag.
+ */
+static int
+decryptBuffer(PGconn *conn)
+{
+ SchannelContext *ctx = conn->windows_schannel;
+ /*
+ * Call DecryptBuffer until it returns some data, fails, or indicates that
+ * it needs more input
+ */
+ while(ctx->decryptedLen == 0)
+ {
+ SecBufferDesc sbufdesc;
+ SecBuffer sbufs[4];
+ SECURITY_STATUS rc;
+ int i;
+
+ Assert(ctx->decryptBufferLen > 0);
+ Assert(ctx->extraLen == 0);
+
+ sbufs[0].pvBuffer = ctx->decryptBuffer;
+ sbufs[0].cbBuffer = ctx->decryptBufferLen;
+ sbufs[0].BufferType = SECBUFFER_DATA;
+ sbufs[1].pvBuffer = NULL;
+ sbufs[1].cbBuffer = 0;
+ sbufs[1].BufferType = SECBUFFER_EMPTY;
+ sbufs[2].pvBuffer = NULL;
+ sbufs[2].cbBuffer = 0;
+ sbufs[2].BufferType = SECBUFFER_EMPTY;
+ sbufs[3].pvBuffer = NULL;
+ sbufs[3].cbBuffer = 0;
+ sbufs[3].BufferType = SECBUFFER_EMPTY;
+
+ sbufdesc.ulVersion = SECBUFFER_VERSION;
+ sbufdesc.cBuffers = 4;
+ sbufdesc.pBuffers = sbufs;
+
+ rc = DecryptMessage(&ctx->ctxHandle, &sbufdesc, 0, NULL);
+
+ if (rc == SEC_E_INCOMPLETE_MESSAGE)
+ {
+ /* We don't have the full message yet. Need more data. */
+ return 0;
+ }
+ if (rc == SEC_I_CONTEXT_EXPIRED)
+ {
+ ctx->remote_shutdown = true;
+ return 0;
+ }
+ if (rc == SEC_I_RENEGOTIATE)
+ {
+ /* TODO */
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL renegotiation not implemented\n\n"));
+ return -1;
+ }
+ if (rc != SEC_E_OK)
+ {
+ /* FIXME: set errno */
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not decrypt received data: %s\n"),
+ getSecurityStatus(rc));
+ return -1;
+ }
+
+ for (i = 0; i < 4; i++)
+ {
+ if (sbufs[i].BufferType == SECBUFFER_EXTRA)
+ {
+ ctx->extraData = sbufs[i].pvBuffer;
+ ctx->extraLen = sbufs[i].cbBuffer;
+ }
+ if (sbufs[i].BufferType == SECBUFFER_DATA)
+ {
+ ctx->decryptedData = sbufs[i].pvBuffer;
+ ctx->decryptedLen = sbufs[i].cbBuffer;
+ }
+ }
+ if (ctx->decryptedLen == 0)
+ {
+ /*
+ * DecryptMessage didn't decrypt anything. Shift the extra data
+ * to beginning of buffer, and retry.
+ */
+ prepareDecryptBufferForRead(ctx);
+ }
+ }
+
+ return 1;
+}
+
+/*
+ * 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 determine whether to continue/retry after error.
+ */
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ SchannelContext *ctx = conn->windows_schannel;
+ int sendlen;
+
+ /*
+ * If the output buffer is empty, encrypt (some of) the input data to fill
+ * it.
+ */
+ if (ctx->encryptedLen == 0)
+ {
+ int plaintextLen;
+
+ plaintextLen = encryptBuffer(conn, ptr, len);
+ if (plaintextLen < 0)
+ return -1;
+ ctx->plaintextLen = plaintextLen;
+ }
+
+ /*
+ * Write out the encrypted data in the buffer
+ */
+ sendlen = pqsecure_raw_write(conn,
+ ctx->encryptedData,
+ ctx->encryptedLen);
+ if (sendlen <= 0)
+ return sendlen;
+
+ ctx->encryptedData += sendlen;
+ ctx->encryptedLen -= sendlen;
+
+ if (ctx->encryptedLen == 0)
+ {
+ /* sent all the data in the buffer. */
+ return ctx->plaintextLen;
+ }
+ else
+ {
+ /* return 0 to indicate that the caller has to retry. */
+ return 0;
+ }
+}
+
+
+/*
+ * Encrypt more data. Helper function for pgtls_write().
+ */
+static ssize_t
+encryptBuffer(PGconn *conn, const void *ptr, int len)
+{
+ SchannelContext *ctx = conn->windows_schannel;
+ SecPkgContext_StreamSizes sizes;
+ SecBufferDesc sbufdesc;
+ SecBuffer sbufs[4];
+ SECURITY_STATUS rc;
+
+ /* we should've sent out all encrypted data from previous round first */
+ Assert(ctx->encryptedLen == 0);
+
+ rc = QueryContextAttributes(&ctx->ctxHandle,
+ SECPKG_ATTR_STREAM_SIZES, &sizes);
+ if (rc != SEC_E_OK)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("QueryContextAttributes failed: %x"),
+ rc);
+ return -1;
+ }
+ if (sizes.cbHeader + sizes.cbTrailer + len > ctx->encryptBufferSize)
+ len = ctx->encryptBufferSize - (sizes.cbHeader + sizes.cbTrailer);
+
+ memcpy(&ctx->encryptBuffer[sizes.cbHeader], ptr, len);
+
+ sbufs[0].pvBuffer = ctx->encryptBuffer;
+ sbufs[0].cbBuffer = sizes.cbHeader;
+ sbufs[0].BufferType = SECBUFFER_STREAM_HEADER;
+
+ sbufs[1].pvBuffer = &ctx->encryptBuffer[sizes.cbHeader];
+ sbufs[1].cbBuffer = len;
+ sbufs[1].BufferType = SECBUFFER_DATA;
+
+ sbufs[2].pvBuffer = &ctx->encryptBuffer[sizes.cbHeader + len];
+ sbufs[2].cbBuffer = sizes.cbTrailer;
+ sbufs[2].BufferType = SECBUFFER_STREAM_TRAILER;
+
+ sbufs[3].pvBuffer = NULL;
+ sbufs[3].cbBuffer = 0;
+ sbufs[3].BufferType = SECBUFFER_EMPTY;
+
+ sbufdesc.ulVersion = SECBUFFER_VERSION;
+ sbufdesc.cBuffers = 4;
+ sbufdesc.pBuffers = sbufs;
+
+ rc = EncryptMessage(&ctx->ctxHandle, 0, &sbufdesc, 0);
+ if (rc != SEC_E_OK)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("EncryptMessage failed: %x"),
+ rc);
+ return -1;
+ }
+
+ ctx->encryptedData = ctx->encryptBuffer;
+ ctx->encryptedLen = sbufs[0].cbBuffer + sbufs[1].cbBuffer + sbufs[2].cbBuffer;
+
+ return len;
+}
+
+/**** Certificate handling functions ****/
+
+static bool
+validateServerCert(PGconn *conn)
+{
+ SchannelContext *ctx = conn->windows_schannel;
+ CERT_CONTEXT *remoteCert;
+ SECURITY_STATUS rc;
+ DWORD flags;
+
+ /* Get server certificate */
+ rc = QueryContextAttributes(&ctx->ctxHandle,
+ SECPKG_ATTR_REMOTE_CERT_CONTEXT,
+ (PVOID) &remoteCert);
+ if (rc != SEC_E_OK)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not get server certificate: %s\n"),
+ getSecurityStatus(rc));
+ return false;
+ }
+
+ /*
+ * Per MSDN documentation [1], we must check the following things:
+ *
+ * 1. The certificate chain is complete and the root is a certificate from
+ * a trusted certification authority (CA).
+ * 2. The current time is not beyond the begin and end dates for each of
+ * the certificates in the certificate chain.
+ * 3. None of the certificates in the certificate chain have been revoked.
+ * 4. The depth of the leaf certificate is not deeper than the maximum
+ * allowable depth specified in the certificate extension. This check is
+ * only necessary if there is a depth specified.
+ * 5. The usage of the certificate is correct, for example, a client
+ * certificate should not be used to authenticate a server.
+ *
+ * [1] "Manually Validating Schannel Credential",
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa378740%28v=vs.85%29.aspx
+ */
+
+ /*
+ * 1. Check the chain
+ * 2. Check expiration
+ *
+ * TODO: Currently, we only support client certs signed by the root client
+ * CA. Intermediary CA's are not supported.
+ */
+
+ /* TODO: CRL's are not actually supported */
+ flags = CERT_STORE_SIGNATURE_FLAG |
+ CERT_STORE_TIME_VALIDITY_FLAG;
+ if (!CertVerifySubjectCertificateContext(
+ remoteCert,
+ ctx->rootCert,
+ &flags))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not verify server certificate\n"));
+ return false;
+ }
+ if (flags != 0)
+ {
+ if ((flags & CERT_STORE_SIGNATURE_FLAG) != 0)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("signature check failed on client cert\n"));
+ else if ((flags & CERT_STORE_REVOCATION_FLAG) != 0)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("server certificate was revoked\n"));
+ else if ((flags & CERT_STORE_TIME_VALIDITY_FLAG) != 0)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("server certificate is not valid at this time\n"));
+ else
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("server certificate is not valid\n"));
+
+ return false;
+ }
+
+ /*
+ * We succeeded to get the issuer's certificate from the trusted root store,
+ * and the signature on the client cert passed, so we're good to go.
+ */
+
+ return true;
+}
+
+
+/*
+ * Loads the certificate into a Certificate Context.
+ *
+ * on error, sets an error message in conn and returns NULL.
+ */
+static CERT_CONTEXT *
+loadCert(PGconn *conn, char *path)
+{
+ CERT_CONTEXT *cert_cxt;
+ char crtBuf[50000]; /* TODO: enlarge as needed */
+ DWORD crtBufLen = 50000;
+ struct stat statbuf;
+ FILE *fp;
+ char *content;
+ int r;
+ int filelen;
+
+ /* Read file into memory */
+ if (stat(path, &statbuf) != 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not stat certificate file \"%s\""),
+ path);
+ return NULL;
+ }
+ filelen = statbuf.st_size;
+
+ fp = fopen(path, "rb");
+ if (!fp)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not open certificate file \"%s\""),
+ path);
+ return NULL;
+ }
+ content = malloc(filelen);
+ r = fread(content, filelen, 1, fp);
+ if (r != 1 || ferror(fp) || fclose(fp))
+ {
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not read certificate file \"%s\""),
+ path);
+ return NULL;
+ }
+
+ /* convert from PEM to DER */
+ if (!CryptStringToBinary(
+ content,
+ filelen,
+ CRYPT_STRING_BASE64HEADER,
+ crtBuf,
+ &crtBufLen,
+ NULL,
+ NULL))
+ {
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not convert certificate file \"%s\" from PEM format"),
+ path);
+ return NULL;
+ }
+ if (crtBufLen > sizeof(crtBuf))
+ {
+ /* TODO: enlarge buffer */
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ return NULL;
+ }
+
+ cert_cxt = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ crtBuf,
+ crtBufLen);
+ if (cert_cxt == NULL)
+ {
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not create certificate context"));
+ return NULL;
+ }
+
+ return cert_cxt;
+
+}
+
+static bool
+loadPrivateKey(PGconn *conn, CERT_CONTEXT *cert, char *path)
+{
+ char keyBuf[50000]; /* TODO: enlarge as needed */
+ DWORD keyBufLen = 50000;
+ CERT_KEY_PROV_INFO key;
+ HCRYPTPROV cryptProvHandle;
+ HCRYPTKEY keyHandle;
+ PUBLICKEYSTRUC *blob;
+ struct stat statbuf;
+ FILE *fp;
+ char *content;
+ int r;
+ int filelen;
+ char privKeyBlob[50000];
+ int privKeyBlobLen = 50000;
+
+ /* Read file into memory */
+ if (stat(path, &statbuf) != 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not read private key file \"%s\""),
+ path);
+ return false;
+ }
+ filelen = statbuf.st_size;
+
+ fp = fopen(path, "rb");
+ if (!fp)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not read private key file \"%s\""),
+ path);
+ return false;
+ }
+ content = malloc(filelen);
+ r = fread(content, filelen, 1, fp);
+ if (r != 1 || ferror(fp) || fclose(fp))
+ {
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not read private key file \"%s\""),
+ path);
+ return false;
+ }
+
+ /* convert from PEM to DER */
+ if (!CryptStringToBinary(
+ content,
+ filelen,
+ CRYPT_STRING_BASE64HEADER,
+ keyBuf,
+ &keyBufLen,
+ NULL,
+ NULL))
+ {
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not convert private key file \"%s\" from PEM format"),
+ path);
+ return false;
+ }
+ if (keyBufLen > sizeof(keyBuf))
+ {
+ /* TODO: enlarge buffer */
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ return false;
+ }
+
+ /*
+ * Load the key.
+ */
+ if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ PKCS_RSA_PRIVATE_KEY,
+ keyBuf,
+ keyBufLen,
+ 0,
+ NULL,
+ privKeyBlob,
+ &privKeyBlobLen))
+ {
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not parse private key file \"%s\""),
+ path);
+ return false;
+ }
+ blob = (PUBLICKEYSTRUC *) privKeyBlob;
+
+ if (!CryptAcquireContext(&cryptProvHandle, "mycontainer", MS_ENHANCED_PROV, PROV_RSA_FULL, 0))
+ {
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CryptAcquireContext failed"));
+ return false;
+ }
+
+ if (!CryptImportKey(cryptProvHandle, privKeyBlob, privKeyBlobLen,
+ (HCRYPTKEY) NULL, 0, &keyHandle))
+ {
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CryptAcquireContext failed"));
+ return false;
+ }
+
+ memset(&keyCtx, 0, sizeof(keyCtx));
+ keyCtx.cbSize = sizeof(keyCtx);
+ keyCtx.hCryptProv = cryptProvHandle;
+ keyCtx.dwKeySpec = AT_KEYEXCHANGE;
+
+ if (!CertSetCertificateContextProperty(cert,
+ CERT_KEY_CONTEXT_PROP_ID,
+ 0,
+ &keyCtx))
+ {
+ free(content);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("CertSetCertificateContextProperty failed"));
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+loadCerts(PGconn *conn)
+{
+ SchannelContext *ctx = conn->windows_schannel;
+ char *sslcertfile = NULL;
+ char *engine = NULL;
+ char *keyname = NULL;
+ char *sslkeyfile = NULL;
+ char *sslrootcert = NULL;
+ char *sslcrl = NULL;
+ bool ret = false;
+
+ if (!pqsecure_get_ssl_files(conn, &sslcertfile, &engine, &keyname, &sslkeyfile,
+ &sslrootcert, &sslcrl))
+ return false;
+
+#ifdef SCHANNEL_DEBUG
+ fprintf(stderr, "files: %s %s %s\n",
+ sslcertfile ? sslcertfile : "<no client cert>",
+ sslkeyfile ? sslkeyfile : "<no client key>",
+ sslrootcert ? sslrootcert : "<no root cert>",
+ sslcrl ? sslcrl : "<no crl>");
+ fflush(stderr);
+#endif
+
+ if (engine || keyname)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("engines not supported"));
+ goto fail;
+ }
+
+ if (sslcertfile)
+ {
+ ctx->clientCert = loadCert(conn, sslcertfile);
+ if (ctx->clientCert == NULL)
+ goto fail;
+
+ /*
+ * Cool, got the client cert. Also load the private key associated
+ * with it.
+ */
+ if (!loadPrivateKey(conn, ctx->clientCert, sslkeyfile))
+ goto fail;
+ }
+
+ /* Load root certificate for validating server certificates */
+ if (sslrootcert)
+ {
+ ctx->rootCert = loadCert(conn, sslrootcert);
+ if (ctx->rootCert == NULL)
+ goto fail;
+ }
+
+ ret = true;
+
+fail:
+ if (sslcertfile)
+ free(sslcertfile);
+ if (engine)
+ free(engine);
+ if (keyname)
+ free(keyname);
+ if (sslkeyfile)
+ free(sslkeyfile);
+ if (sslrootcert)
+ free(sslrootcert);
+ if (sslcrl)
+ free(sslcrl);
+
+#ifdef SCHANNEL_DEBUG
+ fprintf(stderr, "certs loaded: %d\n", ret);
+ fflush(stderr);
+#endif
+
+ return ret;
+}
+
+
+/*
+ * TODO: need a new API for this.
+ */
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * return non-zero, so that psql reports that SSL is used. Of course, it's
+ * totally bogus for anyone who actually tries to get the OpenSSL
+ * SSL struct.
+ */
+ return (void *) 1;
+}
+
+/*** Printing error codes ***/
+
+#define ERRORCODE_STR(x) { x, #x }
+
+typedef struct
+{
+ uint32 rc;
+ const char *str;
+} errorcode_str;
+
+static const char *
+getErrorStr(uint32 rc, errorcode_str *map)
+{
+ static char buf[100];
+ int i;
+
+ for (i = 0; map[i].str != NULL; i++)
+ {
+ if (map[i].rc == rc)
+ return map[i].str;
+ }
+ snprintf(buf, sizeof(buf), "0x%X", rc);
+ return buf;
+}
+
+static const char *
+getSecurityStatus(SECURITY_STATUS rc)
+{
+ static errorcode_str sec_status_codes[] = {
+ ERRORCODE_STR(SEC_E_OK),
+ ERRORCODE_STR(SEC_E_INCOMPLETE_MESSAGE),
+ ERRORCODE_STR(SEC_E_INSUFFICIENT_MEMORY),
+ ERRORCODE_STR(SEC_E_INTERNAL_ERROR),
+ ERRORCODE_STR(SEC_E_INVALID_HANDLE),
+ ERRORCODE_STR(SEC_E_INVALID_TOKEN),
+ ERRORCODE_STR(SEC_E_LOGON_DENIED),
+ ERRORCODE_STR(SEC_E_NO_AUTHENTICATING_AUTHORITY),
+ ERRORCODE_STR(SEC_E_NO_CREDENTIALS),
+ ERRORCODE_STR(SEC_I_COMPLETE_AND_CONTINUE),
+ ERRORCODE_STR(SEC_I_COMPLETE_NEEDED),
+ ERRORCODE_STR(SEC_I_CONTINUE_NEEDED),
+
+ ERRORCODE_STR(SEC_E_ALGORITHM_MISMATCH),
+ ERRORCODE_STR(SEC_E_UNSUPPORTED_FUNCTION),
+ ERRORCODE_STR(SEC_E_UNTRUSTED_ROOT),
+ { 0, NULL }
+ };
+ return getErrorStr(rc, sec_status_codes);
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 66778b2..e19fc61 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -475,3 +475,297 @@ pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
}
#endif /* ENABLE_THREAD_SAFETY && !WIN32 */
+
+/*
+ * Determine the filenames of SSL related files.
+ *
+ * The filenames returned are checked to exist; the caller should throw an
+ * error if one of the returned files can not be opened. This function
+ * performs some preliminariry sanity checks, e.g. if a certificate file
+ * exists, we must also find the corresponding private key file.
+ *
+ * *sslcert is set to the path of the client's certificate, and *sslkey to
+ * the path to the corresponding private key. If compiled with
+ * USE_SSL_ENGINE, a pathname containing a colon is interpreted as a
+ * a two-part "engin:key" string, for specifying a hardware key.
+ *
+ * *sslrootcert and *sslcrl are set to the paths to CA certificate and
+ * CRL files, used to validate the server certificate.
+ *
+ * The returned strings are malloc'd, and the caller is responsible for
+ * freeing them. On error, returns false and sets the libpq error message.
+ */
+bool
+pqsecure_get_ssl_files(PGconn *conn, char **sslcert, char **sslkey,
+ char **engine, char **keyname,
+ char **sslrootcert, char **sslcrl)
+{
+ struct stat buf;
+ char homedir[MAXPGPATH];
+ char fnbuf[MAXPGPATH];
+ char sebuf[256];
+ bool have_homedir;
+
+ *sslcert = NULL;
+ *sslkey = NULL;
+ *engine = NULL;
+ *keyname = NULL;
+ *sslrootcert = NULL;
+ *sslcrl = NULL;
+
+ /*
+ * 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) ||
+ !(conn->sslcrl && strlen(conn->sslcrl) > 0))
+ have_homedir = pqGetHomeDirectory(homedir, sizeof(homedir));
+ else /* won't need it */
+ have_homedir = false;
+
+ /* Find the client certificate file */
+ if (conn->sslcert && strlen(conn->sslcert) > 0)
+ strncpy(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')
+ {
+ /* no home directory, proceed without a client cert */
+ }
+ else if (stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * If file is not present, just go on without a client cert; server
+ * might or might not accept the connection. Any other error,
+ * however, is grounds for complaint.
+ */
+ if (errno != ENOENT && errno != ENOTDIR)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not open certificate file \"%s\": %s\n"),
+ fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
+ goto fail;
+ }
+ }
+ else
+ {
+ *sslcert = strdup(fnbuf);
+ if (*sslcert == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto fail;
+ }
+ }
+
+ /*
+ * Read the SSL key. If a key is specified, treat it as an engine:key
+ * combination if there is colon present - we don't support files with
+ * colon in the name. The exception is if the second character is a colon,
+ * in which case it can be a Windows filename with drive specification.
+ */
+ if (*sslcert && conn->sslkey && strlen(conn->sslkey) > 0)
+ {
+#ifdef USE_SSL_ENGINE
+ if (strchr(conn->sslkey, ':')
+#ifdef WIN32
+ && conn->sslkey[1] != ':'
+#endif
+ )
+ {
+ /* Colon, but not in second character, treat as engine:key */
+ char *engine_str = strdup(conn->sslkey);
+ char *engine_colon;
+
+ if (engine_str == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto fail;
+ }
+
+ /* cannot return NULL because we already checked before strdup */
+ engine_colon = strchr(engine_str, ':');
+
+ *engine_colon = '\0'; /* engine_str now has engine name */
+ engine_colon++; /* engine_colon now has key name */
+
+ *engine = strdup(engine_str);
+ if (*engine)
+ *keyname = strdup(engine_colon);
+
+ free(engine_str);
+
+ if (*engine == NULL || *keyname == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto fail;
+ }
+
+ fnbuf[0] = '\0'; /* indicate we're not going to load from a
+ * file */
+ }
+ else
+#endif /* USE_SSL_ENGINE */
+ {
+ /* PGSSLKEY is not an engine, treat it as a filename */
+ strncpy(fnbuf, conn->sslkey, sizeof(fnbuf));
+ }
+ }
+ else if (have_homedir)
+ {
+ /* No PGSSLKEY specified, load default file */
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, USER_KEY_FILE);
+ }
+ else
+ fnbuf[0] = '\0';
+
+ if (*sslcert && fnbuf[0] != '\0')
+ {
+ /* read the client key from file */
+
+ if (stat(fnbuf, &buf) != 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("certificate present, but not private key file \"%s\"\n"),
+ fnbuf);
+ goto fail;
+ }
+#ifndef WIN32
+ if (!S_ISREG(buf.st_mode) || buf.st_mode & (S_IRWXG | S_IRWXO))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("private key file \"%s\" has group or world access; permissions should be u=rw (0600) or less\n"),
+ fnbuf);
+ goto fail;
+ }
+#endif
+
+ *sslkey = strdup(fnbuf);
+ if (*sslkey == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto fail;
+ }
+ }
+
+ /*
+ * If the root cert file exists, load it so we can perform certificate
+ * verification. If sslmode is "verify-full" we will also do further
+ * verification after the connection has been completed.
+ */
+ if (conn->sslrootcert && strlen(conn->sslrootcert) > 0)
+ strncpy(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' &&
+ stat(fnbuf, &buf) == 0)
+ {
+ *sslrootcert = strdup(fnbuf);
+ if (*sslrootcert == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto fail;
+ }
+ }
+ else
+ {
+ /*
+ * 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);
+ goto fail;
+ }
+ }
+
+ /* If we have a root certificate, also try to load a CRL */
+ if (*sslrootcert)
+ {
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ strncpy(fnbuf, conn->sslcrl, sizeof(fnbuf));
+ else if (have_homedir)
+ snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOT_CRL_FILE);
+ else
+ fnbuf[0] = '\0';
+
+ if (fnbuf[0] != '\0' &&
+ stat(fnbuf, &buf) != 0)
+ {
+ /*
+ * If not found, silently ignore; we do not require CRL.
+ * Any other error, however, is grounds for complaint.
+ */
+ if (errno != ENOENT && errno != ENOTDIR)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not open certificate file \"%s\": %s\n"),
+ fnbuf, pqStrerror(errno, sebuf, sizeof(sebuf)));
+ goto fail;
+ }
+ }
+ else
+ {
+ *sslcrl = strdup(fnbuf);
+ if (*sslcrl == NULL)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory\n"));
+ goto fail;
+ }
+ }
+ }
+
+ /* all done! */
+ return true;
+
+fail:
+ if (*sslcert)
+ free(*sslcert);
+ if (*engine)
+ free(*engine);
+ if (*keyname)
+ free(*keyname);
+ if (*sslkey)
+ free(*sslkey);
+ if (*sslrootcert)
+ free(*sslrootcert);
+ if (*sslcrl)
+ free(*sslcrl);
+
+ *sslcert = NULL;
+ *engine = NULL;
+ *keyname = NULL;
+ *sslkey = NULL;
+ *sslrootcert = NULL;
+ *sslcrl = NULL;
+
+ return false;
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 6032904..35b1c9e 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -82,6 +82,10 @@ typedef struct
#endif
#endif /* USE_OPENSSL */
+#ifdef USE_WINDOWS_SCHANNEL
+struct SchannelContext;
+#endif
+
/*
* POSTGRES backend dependent Constants.
*/
@@ -440,6 +444,10 @@ struct pg_conn
#endif /* USE_OPENSSL */
#endif /* USE_SSL */
+#ifdef USE_WINDOWS_SCHANNEL
+ struct SchannelContext *windows_schannel;
+#endif /* USE_WINDOWS_SCHANNEL */
+
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
gss_name_t gtarg_nam; /* GSS target name */
@@ -627,6 +635,8 @@ extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
+extern bool pqsecure_get_ssl_files(PGconn *, char **sslcert, char **sslkey, char **engine, char **keyname, char **sslrootcert, char **sslcrl);
+
#if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
extern int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending);
extern void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending,
diff --git a/src/interfaces/libpq/win32.mak b/src/interfaces/libpq/win32.mak
index 39a0bc9..5793fce 100644
--- a/src/interfaces/libpq/win32.mak
+++ b/src/interfaces/libpq/win32.mak
@@ -127,6 +127,9 @@ CLEAN :
!IFDEF USE_OPENSSL
-@erase "$(INTDIR)\fe-secure-openssl.obj"
!ENDIF
+!IFDEF USE_WINDOWS_SCHANNEL
+ -@erase "$(INTDIR)\fe-secure-winschannel.obj"
+!ENDIF
LIB32=link.exe -lib
@@ -170,7 +173,9 @@ LIB32_OBJS= \
!IFDEF USE_OPENSSL
LIB32_OBJS=$(LIB32_OBJS) "$(INTDIR)\fe-secure-openssl.obj"
!ENDIF
-
+!IFDEF USE_WINDOWS_SCHANNEL
+ LIB32_OBJS=$(LIB32_OBJS) "$(INTDIR)\fe-secure-winschannel.obj"
+!ENDIF
config: ..\..\include\pg_config.h ..\..\include\pg_config_ext.h pg_config_paths.h ..\..\include\pg_config_os.h
@@ -200,6 +205,11 @@ CPP_PROJ=$(CPP_PROJ) /D USE_OPENSSL
SSL_LIBS=ssleay32.lib libeay32.lib gdi32.lib
!ENDIF
+!IFDEF USE_WINDOWS_SCHANNEL
+CPP_PROJ=$(CPP_PROJ) /D USE_WINDOWS_SCHANNEL
+SSL_LIBS=crypt32.lib
+!ENDIF
+
!IFDEF USE_KFW
CPP_PROJ=$(CPP_PROJ) /D KRB5
KFW_LIBS=krb5_32.lib comerr32.lib gssapi32.lib
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index e6fb3ec..fa2ac8c 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -115,6 +115,10 @@ sub mkvcbuild
$postgres->AddDefine('BUILDING_DLL');
$postgres->AddLibrary('secur32.lib');
$postgres->AddLibrary('ws2_32.lib');
+ if ($solution->{options}->{winschannel})
+ {
+ $postgres->AddLibrary('crypt32.lib');
+ }
$postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c
@@ -123,6 +127,11 @@ sub mkvcbuild
{
$postgres->RemoveFile('src\backend\libpq\be-secure-openssl.c');
}
+ # and same for winschannel
+ if (!$solution->{options}->{winschannel})
+ {
+ $postgres->RemoveFile('src\backend\libpq\be-secure-winschannel.c');
+ }
my $snowball = $solution->AddProject('dict_snowball', 'dll', '',
'src\backend\snowball');
@@ -276,6 +285,10 @@ sub mkvcbuild
$libpq->AddDefine('UNSAFE_STAT_OK');
$libpq->AddIncludeDir('src\port');
$libpq->AddLibrary('secur32.lib');
+ if ($solution->{options}->{winschannel})
+ {
+ $libpq->AddLibrary('crypt32.lib');
+ }
$libpq->AddLibrary('ws2_32.lib');
$libpq->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
$libpq->UseDef('src\interfaces\libpq\libpqdll.def');
@@ -288,6 +301,11 @@ sub mkvcbuild
{
$libpq->RemoveFile('src\interfaces\libpq\fe-secure-openssl.c');
}
+ # and same for winschannel
+ if (!$solution->{options}->{winschannel})
+ {
+ $libpq->RemoveFile('src\interfaces\libpq\fe-secure-winschannel.c');
+ }
my $libpqwalreceiver =
$solution->AddProject('libpqwalreceiver', 'dll', '',
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 39e41f6..7034eea 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -183,6 +183,8 @@ sub GenerateFiles
print O "#define USE_LDAP 1\n" if ($self->{options}->{ldap});
print O "#define HAVE_LIBZ 1\n" if ($self->{options}->{zlib});
print O "#define USE_OPENSSL 1\n" if ($self->{options}->{openssl});
+ print O "#define USE_WINDOWS_SCHANNEL 1\n"
+ if ($self->{options}->{winschannel});
print O "#define ENABLE_NLS 1\n" if ($self->{options}->{nls});
print O "#define BLCKSZ ", 1024 * $self->{options}->{blocksize}, "\n";
@@ -629,6 +631,7 @@ sub GetFakeConfigure
$cfg .= ' --without-zlib' unless ($self->{options}->{zlib});
$cfg .= ' --with-extra-version' if ($self->{options}->{extraver});
$cfg .= ' --with-openssl' if ($self->{options}->{openssl});
+ $cfg .= ' --with-winschannel' if ($self->{options}->{winschannel});
$cfg .= ' --with-ossp-uuid' if ($self->{options}->{uuid});
$cfg .= ' --with-libxml' if ($self->{options}->{xml});
$cfg .= ' --with-libxslt' if ($self->{options}->{xslt});
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index e4d4810..ab4001a 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ winschannel => 1, # --with-winschannel
uuid => undef, # --with-ossp-uuid
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>