diff --git a/configure b/configure
index 013dc0b..740bae3 100755
--- a/configure
+++ b/configure
@@ -5773,6 +5773,7 @@ for ac_header in \
 	OpenAL/al.h \
 	OpenCL/opencl.h \
 	QuickTime/ImageCompression.h \
+	Security/Security.h \
 	alias.h \
 	alsa/asoundlib.h \
 	arpa/inet.h \
diff --git a/configure.ac b/configure.ac
index 277bd8e..7e9bbb4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -380,6 +380,7 @@ AC_CHECK_HEADERS(\
 	OpenAL/al.h \
 	OpenCL/opencl.h \
 	QuickTime/ImageCompression.h \
+	Security/Security.h \
 	alias.h \
 	alsa/asoundlib.h \
 	arpa/inet.h \
diff --git a/dlls/secur32/Makefile.in b/dlls/secur32/Makefile.in
index 4824cdb..446157f 100644
--- a/dlls/secur32/Makefile.in
+++ b/dlls/secur32/Makefile.in
@@ -3,6 +3,7 @@ IMPORTLIB = secur32
 IMPORTS   = netapi32 advapi32
 DELAYIMPORTS = crypt32
 EXTRAINCL = @GNUTLSINCL@
+EXTRALIBS = @SECURITYLIB@
 
 C_SRCS = \
 	base64_codec.c \
diff --git a/dlls/secur32/schannel.c b/dlls/secur32/schannel.c
index 7c385e7..e0632cd 100644
--- a/dlls/secur32/schannel.c
+++ b/dlls/secur32/schannel.c
@@ -25,7 +25,15 @@
 #include <stdarg.h>
 #include <errno.h>
 #include <limits.h>
-#ifdef SONAME_LIBGNUTLS
+#ifdef HAVE_SECURITY_SECURITY_H
+#include <Security/Security.h>
+#define GetCurrentThread GetCurrentThread_Mac
+#define LoadResource LoadResource_Mac
+#include <CoreServices/CoreServices.h>
+#undef GetCurrentThread
+#undef LoadResource
+#undef DPRINTF
+#elif defined(SONAME_LIBGNUTLS)
 #include <gnutls/gnutls.h>
 #endif
 
@@ -40,7 +48,926 @@
 
 WINE_DEFAULT_DEBUG_CHANNEL(secur32);
 
-#ifdef SONAME_LIBGNUTLS
+typedef void *schan_imp_transport;
+static int schan_pull(schan_imp_transport transport, void *buff, size_t *buff_len);
+static int schan_push(schan_imp_transport transport, const void *buff, size_t *buff_len);
+
+
+#ifdef HAVE_SECURITY_SECURITY_H
+
+struct mac_session {
+    SSLContextRef context;
+    schan_imp_transport transport;
+};
+typedef struct mac_session *schan_imp_session;
+typedef void *schan_imp_certificate;
+
+static BOOL schan_imp_create_session(schan_imp_session *s, BOOL is_server)
+{
+    OSStatus status;
+
+    TRACE("(%p, %d)\n", s, is_server);
+
+    *s = HeapAlloc(GetProcessHeap(), 0, sizeof(**s));
+    if (!*s)
+        return FALSE;
+
+    status = SSLNewContext(is_server, &(*s)->context);
+    if (status != noErr)
+    {
+        ERR("Failed to create session context: %ld\n", (long)status);
+        goto fail;
+    }
+
+    status = SSLSetConnection((*s)->context, *s);
+    if (status != noErr)
+    {
+        ERR("Failed to set session connection: %ld\n", (long)status);
+        goto fail;
+    }
+
+    status = SSLSetEnableCertVerify((*s)->context, FALSE);
+    if (status != noErr)
+    {
+        ERR("Failed to disable certificate verification: %ld\n", (long)status);
+        goto fail;
+    }
+
+    status = SSLSetProtocolVersionEnabled((*s)->context, kSSLProtocol2, FALSE);
+    if (status != noErr)
+    {
+        ERR("Failed to disable SSL version 2: %ld\n", (long)status);
+        goto fail;
+    }
+
+    TRACE("    -> %p/%p\n", *s, (*s)->context);
+
+    return TRUE;
+
+fail:
+    HeapFree(GetProcessHeap(), 0, *s);
+    return FALSE;
+}
+
+static void schan_imp_dispose_session(schan_imp_session s)
+{
+    OSStatus status;
+
+    TRACE("(%p/%p)\n", s, s->context);
+
+    status = SSLDisposeContext(s->context);
+    if (status != noErr)
+        ERR("Failed to dispose of session context: %ld\n", (long)status);
+    HeapFree(GetProcessHeap(), 0, s);
+}
+
+static BOOL schan_imp_allocate_certificate_credentials(schan_imp_certificate *c)
+{
+    /* The certificate is never really used for anything. */
+    *c = NULL;
+    return TRUE;
+}
+
+static void schan_imp_free_certificate_credentials(schan_imp_certificate c)
+{
+}
+
+static BOOL schan_imp_set_session_certificate(schan_imp_session s,
+    schan_imp_certificate c)
+{
+    /* TODO: We'd use SSLSetCertificate, except the certificate is a dummy. */
+    return TRUE;
+}
+
+/* schan_imp_pull_adaptor
+ *      Callback registered with SSLSetIOFuncs as the read function for a
+ *      session.  Reads data from the session connection.  Conforms to the
+ *      SSLReadFunc type.
+ *
+ *  transport - The session connection
+ *  buff - The buffer into which to store the read data.  Must be at least
+ *         *buff_len bytes in length.
+ *  *buff_len - On input, the desired length to read.  On successful return,
+ *              the number of bytes actually read.
+ *
+ *  Returns:
+ *      noErr on complete success meaning the requested length was successfully
+ *          read.
+ *      errSSLWouldBlock when the requested length could not be read without
+ *          blocking.  *buff_len indicates how much was actually read.  The
+ *          caller should try again if/when they want to read more.
+ *      errSSLClosedGraceful when the connection has closed and there's no
+ *          more data to be read.
+ *      other error code for failure.
+ */
+static OSStatus schan_imp_pull_adaptor(SSLConnectionRef transport, void *buff,
+                                       size_t *buff_len)
+{
+    schan_imp_session s = (schan_imp_session)transport;
+    size_t requested = *buff_len;
+    int status;
+    OSStatus ret;
+
+    TRACE("(%p/%p, %p, %p/%lu)\n", s, s->transport, buff, buff_len,
+          (unsigned long)*buff_len);
+
+    status = schan_pull(s->transport, buff, buff_len);
+    if (status == 0)
+    {
+        if (*buff_len == 0)
+        {
+            TRACE("Connection closed\n");
+            ret = errSSLClosedGraceful;
+        }
+        else if (*buff_len < requested)
+        {
+            TRACE("Pulled %lu bytes before would block\n", (unsigned long)*buff_len);
+            ret = errSSLWouldBlock;
+        }
+        else
+        {
+            TRACE("Pulled %lu bytes\n", (unsigned long)*buff_len);
+            ret = noErr;
+        }
+    }
+    else if (status == EAGAIN)
+    {
+        TRACE("Would block before being able to pull anything\n");
+        ret = errSSLWouldBlock;
+    }
+    else
+    {
+        FIXME("Unknown status code from schan_pull: %d\n", status);
+        ret = ioErr;
+    }
+
+    return ret;
+}
+
+/* schan_imp_push_adaptor
+ *      Callback registered with SSLSetIOFuncs as the write function for a
+ *      session.  Writes data to the session connection.  Conforms to the
+ *      SSLWriteFunc type.
+ *
+ *  transport - The session connection
+ *  buff - The buffer of data to write.  Must be at least *buff_len bytes in length.
+ *  *buff_len - On input, the desired length to write.  On successful return,
+ *              the number of bytes actually written.
+ *
+ *  Returns:
+ *      noErr on complete or partial success; *buff_len indicates how much data
+ *          was actually written, which may be less than requrested.
+ *      errSSLWouldBlock when no data could be written without blocking.  The
+ *          caller should try again.
+ *      other error code for failure.
+ */
+static OSStatus schan_imp_push_adaptor(SSLConnectionRef transport, const void *buff,
+                                       size_t *buff_len)
+{
+    schan_imp_session s = (schan_imp_session)transport;
+    int status;
+    OSStatus ret;
+
+    TRACE("(%p/%p, %p, %p/%lu)\n", s, s->transport, buff, buff_len,
+          (unsigned long)*buff_len);
+
+    status = schan_push(s->transport, buff, buff_len);
+    if (status == 0)
+    {
+        TRACE("Pushed %lu bytes\n", (unsigned long)*buff_len);
+        ret = noErr;
+    }
+    else if (status == EAGAIN)
+    {
+        TRACE("Would block before being able to push anything\n");
+        ret = errSSLWouldBlock;
+    }
+    else
+    {
+        FIXME("Unknown status code from schan_push: %d\n", status);
+        ret = ioErr;
+    }
+
+    return ret;
+}
+
+static BOOL schan_imp_set_session_io_funcs(schan_imp_session s)
+{
+    OSStatus status;
+
+    TRACE("(%p/%p)\n", s, s->context);
+
+    status = SSLSetIOFuncs(s->context, schan_imp_pull_adaptor, schan_imp_push_adaptor);
+    if (status != noErr)
+        ERR("Failed to set session I/O funcs: %ld\n", (long)status);
+    return (status == noErr);
+}
+
+static void schan_imp_set_session_transport(schan_imp_session s,
+                                            schan_imp_transport t)
+{
+    TRACE("(%p/%p, %p)\n", s, s->context, t);
+
+    s->transport = t;
+}
+
+static SECURITY_STATUS schan_imp_handshake(schan_imp_session s)
+{
+    OSStatus status;
+
+    TRACE("(%p/%p)\n", s, s->context);
+
+    status = SSLHandshake(s->context);
+    if (status == noErr)
+    {
+        TRACE("Handshake completed\n");
+        return SEC_E_OK;
+    }
+    else if (status == errSSLWouldBlock)
+    {
+        TRACE("Continue...\n");
+        return SEC_I_CONTINUE_NEEDED;
+    }
+    else if (errSecErrnoBase <= status && status <= errSecErrnoLimit)
+    {
+        ERR("Handshake failed: %s\n", strerror(status));
+        return SEC_E_INTERNAL_ERROR;
+    }
+    else
+    {
+        ERR("Handshake failed: %ld\n", (long)status);
+        cssmPerror("SSLHandshake", status);
+        return SEC_E_INTERNAL_ERROR;
+    }
+
+    /* Never reached */
+    return SEC_E_OK;
+}
+
+static unsigned int schan_imp_get_session_cipher_block_size(schan_imp_session s)
+{
+    SSLCipherSuite cipherSuite;
+    OSStatus status;
+
+    TRACE("(%p/%p)\n", s, s->context);
+
+    status = SSLGetNegotiatedCipher(s->context, &cipherSuite);
+    if (status != noErr)
+    {
+        ERR("Failed to get session cipher suite: %ld\n", status);
+        return 1;
+    }
+
+    TRACE("cipher suite %x\n", (unsigned int)cipherSuite);
+
+    switch (cipherSuite)
+    {
+    /* AES_128_CBC */
+    case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_RSA_WITH_AES_128_CBC_SHA:
+        return 16;
+
+    /* AES_256_CBC */
+    case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+    case TLS_RSA_WITH_AES_256_CBC_SHA:
+        return 16;
+
+    /* ARCFOUR_128 */
+    case SSL_DH_anon_WITH_RC4_128_MD5:
+    case SSL_RSA_WITH_RC4_128_MD5:
+    case SSL_RSA_WITH_RC4_128_SHA:
+        return 1;
+
+    /* ARCFOUR_40 */
+    case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5:
+    case SSL_RSA_EXPORT_WITH_RC4_40_MD5:
+        return 1;
+
+    /* DES_CBC */
+    case SSL_DH_anon_WITH_DES_CBC_SHA:
+    case SSL_DH_DSS_WITH_DES_CBC_SHA:
+    case SSL_DH_RSA_WITH_DES_CBC_SHA:
+    case SSL_DHE_DSS_WITH_DES_CBC_SHA:
+    case SSL_DHE_RSA_WITH_DES_CBC_SHA:
+    case SSL_RSA_WITH_DES_CBC_MD5:
+    case SSL_RSA_WITH_DES_CBC_SHA:
+        return 8;
+
+    /* DES40_CBC */
+    case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA:
+        return 8;
+
+    /* NULL */
+    case SSL_FORTEZZA_DMS_WITH_NULL_SHA:
+    case SSL_NULL_WITH_NULL_NULL:
+    case SSL_RSA_WITH_NULL_MD5:
+    case SSL_RSA_WITH_NULL_SHA:
+        return 1;
+
+    /* RC2_CBC */
+    case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5:
+    case SSL_RSA_WITH_RC2_CBC_MD5:
+        return 8;
+
+    /* IDEA_CBC */
+    case SSL_RSA_WITH_IDEA_CBC_MD5:
+    case SSL_RSA_WITH_IDEA_CBC_SHA:
+        return 8;
+
+    /* 3DES_EDE_CBC */
+    case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_RSA_WITH_3DES_EDE_CBC_MD5:
+    case SSL_RSA_WITH_3DES_EDE_CBC_SHA:
+        return 8;
+
+    default:
+        FIXME("Unknown cipher suite %#x, returning 1\n", (unsigned)cipherSuite);
+        return 1;
+    }
+}
+
+static DWORD schan_imp_get_session_protocol(schan_imp_session s)
+{
+    SSLProtocol protocol;
+    OSStatus status;
+
+    TRACE("(%p/%p)\n", s, s->context);
+
+    status = SSLGetNegotiatedProtocolVersion(s->context, &protocol);
+    if (status != noErr)
+    {
+        ERR("Failed to get session protocol: %ld\n", status);
+        return 0;
+    }
+
+    TRACE("protocol %d\n", protocol);
+
+    switch (protocol)
+    {
+    case kSSLProtocol2: return SP_PROT_SSL2_CLIENT;
+    case kSSLProtocol3: return SP_PROT_SSL3_CLIENT;
+    case kTLSProtocol1: return SP_PROT_TLS1_CLIENT;
+    default:
+        FIXME("unknown protocol %d\n", protocol);
+        return 0;
+    }
+}
+
+static ALG_ID schan_imp_get_cipher_algid(SSLCipherSuite cipherSuite)
+{
+    TRACE("(%x)\n", (unsigned int)cipherSuite);
+
+    switch (cipherSuite)
+    {
+    /* AES_128_CBC */
+    case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_RSA_WITH_AES_128_CBC_SHA:
+        return CALG_AES;
+
+    /* AES_256_CBC */
+    case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+    case TLS_RSA_WITH_AES_256_CBC_SHA:
+        return CALG_AES;
+
+    /* ARCFOUR_128 */
+    case SSL_DH_anon_WITH_RC4_128_MD5:
+    case SSL_RSA_WITH_RC4_128_MD5:
+    case SSL_RSA_WITH_RC4_128_SHA:
+        return CALG_RC4;
+
+    /* ARCFOUR_40 */
+    case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5:
+    case SSL_RSA_EXPORT_WITH_RC4_40_MD5:
+        return CALG_RC4;
+
+    /* DES_CBC */
+    case SSL_DH_anon_WITH_DES_CBC_SHA:
+    case SSL_DH_DSS_WITH_DES_CBC_SHA:
+    case SSL_DH_RSA_WITH_DES_CBC_SHA:
+    case SSL_DHE_DSS_WITH_DES_CBC_SHA:
+    case SSL_DHE_RSA_WITH_DES_CBC_SHA:
+    case SSL_RSA_WITH_DES_CBC_MD5:
+    case SSL_RSA_WITH_DES_CBC_SHA:
+        return CALG_DES;
+
+    /* DES40_CBC */
+    case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA:
+        return CALG_DES;
+
+    /* NULL */
+    case SSL_FORTEZZA_DMS_WITH_NULL_SHA:
+    case SSL_NULL_WITH_NULL_NULL:
+    case SSL_RSA_WITH_NULL_MD5:
+    case SSL_RSA_WITH_NULL_SHA:
+        return 0;
+
+    /* RC2_CBC */
+    case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5:
+    case SSL_RSA_WITH_RC2_CBC_MD5:
+        return CALG_RC2;
+
+    /* IDEA_CBC */
+    case SSL_RSA_WITH_IDEA_CBC_MD5:
+    case SSL_RSA_WITH_IDEA_CBC_SHA:
+        return (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_IDEA);
+
+    /* 3DES_EDE_CBC */
+    case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_RSA_WITH_3DES_EDE_CBC_MD5:
+    case SSL_RSA_WITH_3DES_EDE_CBC_SHA:
+        return CALG_3DES;
+
+    default:
+        FIXME("Unknown cipher suite %#x, returning 0\n", (unsigned)cipherSuite);
+        return 0;
+    }
+}
+
+static unsigned int schan_imp_get_cipher_key_size(SSLCipherSuite cipherSuite)
+{
+    TRACE("(%x)\n", (unsigned int)cipherSuite);
+
+    switch (cipherSuite)
+    {
+    /* AES_128_CBC */
+    case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_RSA_WITH_AES_128_CBC_SHA:
+        return 16;
+
+    /* AES_256_CBC */
+    case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+    case TLS_RSA_WITH_AES_256_CBC_SHA:
+        return 32;
+
+    /* ARCFOUR_128 */
+    case SSL_DH_anon_WITH_RC4_128_MD5:
+    case SSL_RSA_WITH_RC4_128_MD5:
+    case SSL_RSA_WITH_RC4_128_SHA:
+        return 16;
+
+    /* ARCFOUR_40 */
+    case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5:
+    case SSL_RSA_EXPORT_WITH_RC4_40_MD5:
+        return 5;
+
+    /* DES_CBC */
+    case SSL_DH_anon_WITH_DES_CBC_SHA:
+    case SSL_DH_DSS_WITH_DES_CBC_SHA:
+    case SSL_DH_RSA_WITH_DES_CBC_SHA:
+    case SSL_DHE_DSS_WITH_DES_CBC_SHA:
+    case SSL_DHE_RSA_WITH_DES_CBC_SHA:
+    case SSL_RSA_WITH_DES_CBC_MD5:
+    case SSL_RSA_WITH_DES_CBC_SHA:
+        return 8;
+
+    /* DES40_CBC */
+    case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA:
+        return 5;
+
+    /* NULL */
+    case SSL_FORTEZZA_DMS_WITH_NULL_SHA:
+    case SSL_NULL_WITH_NULL_NULL:
+    case SSL_RSA_WITH_NULL_MD5:
+    case SSL_RSA_WITH_NULL_SHA:
+        return 0;
+
+    /* RC2_CBC */
+    case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5:
+    case SSL_RSA_WITH_RC2_CBC_MD5:
+        return 5;
+
+    /* IDEA_CBC */
+    case SSL_RSA_WITH_IDEA_CBC_MD5:
+    case SSL_RSA_WITH_IDEA_CBC_SHA:
+        return 16;
+
+    /* 3DES_EDE_CBC */
+    case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_RSA_WITH_3DES_EDE_CBC_MD5:
+    case SSL_RSA_WITH_3DES_EDE_CBC_SHA:
+        return 24;
+
+    default:
+        FIXME("Unknown cipher suite %#x, returning 0\n", (unsigned)cipherSuite);
+        return 0;
+    }
+}
+
+static ALG_ID schan_imp_get_mac_algid(SSLCipherSuite cipherSuite)
+{
+    TRACE("(%x)\n", (unsigned int)cipherSuite);
+
+    switch (cipherSuite)
+    {
+    /* SHA */
+    case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_anon_WITH_DES_CBC_SHA:
+    case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_DSS_WITH_DES_CBC_SHA:
+    case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_RSA_WITH_DES_CBC_SHA:
+    case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_DSS_WITH_DES_CBC_SHA:
+    case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_RSA_WITH_DES_CBC_SHA:
+    case SSL_FORTEZZA_DMS_WITH_NULL_SHA:
+    case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_RSA_WITH_DES_CBC_SHA:
+    case SSL_RSA_WITH_IDEA_CBC_SHA:
+    case SSL_RSA_WITH_NULL_SHA:
+    case SSL_RSA_WITH_RC4_128_SHA:
+    case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+    case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+    case TLS_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_RSA_WITH_AES_256_CBC_SHA:
+        return CALG_SHA;
+
+    /* MD5 */
+    case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5:
+    case SSL_DH_anon_WITH_RC4_128_MD5:
+    case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5:
+    case SSL_RSA_EXPORT_WITH_RC4_40_MD5:
+    case SSL_RSA_WITH_3DES_EDE_CBC_MD5:
+    case SSL_RSA_WITH_DES_CBC_MD5:
+    case SSL_RSA_WITH_IDEA_CBC_MD5:
+    case SSL_RSA_WITH_NULL_MD5:
+    case SSL_RSA_WITH_RC2_CBC_MD5:
+    case SSL_RSA_WITH_RC4_128_MD5:
+        return CALG_MD5;
+
+    /* NULL */
+    case SSL_NULL_WITH_NULL_NULL:
+        return 0;
+
+    default:
+        FIXME("Unknown cipher suite %#x, returning 0\n", (unsigned)cipherSuite);
+        return 0;
+    }
+}
+
+static unsigned int schan_imp_get_mac_key_size(SSLCipherSuite cipherSuite)
+{
+    TRACE("(%x)\n", (unsigned int)cipherSuite);
+
+    switch (cipherSuite)
+    {
+    /* SHA */
+    case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_anon_WITH_DES_CBC_SHA:
+    case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_DSS_WITH_DES_CBC_SHA:
+    case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_RSA_WITH_DES_CBC_SHA:
+    case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_DSS_WITH_DES_CBC_SHA:
+    case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_RSA_WITH_DES_CBC_SHA:
+    case SSL_FORTEZZA_DMS_WITH_NULL_SHA:
+    case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_RSA_WITH_DES_CBC_SHA:
+    case SSL_RSA_WITH_IDEA_CBC_SHA:
+    case SSL_RSA_WITH_NULL_SHA:
+    case SSL_RSA_WITH_RC4_128_SHA:
+    case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+    case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+    case TLS_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_RSA_WITH_AES_256_CBC_SHA:
+        return 20;
+
+    /* MD5 */
+    case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5:
+    case SSL_DH_anon_WITH_RC4_128_MD5:
+    case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5:
+    case SSL_RSA_EXPORT_WITH_RC4_40_MD5:
+    case SSL_RSA_WITH_3DES_EDE_CBC_MD5:
+    case SSL_RSA_WITH_DES_CBC_MD5:
+    case SSL_RSA_WITH_IDEA_CBC_MD5:
+    case SSL_RSA_WITH_NULL_MD5:
+    case SSL_RSA_WITH_RC2_CBC_MD5:
+    case SSL_RSA_WITH_RC4_128_MD5:
+        return 16;
+
+    /* NULL */
+    case SSL_NULL_WITH_NULL_NULL:
+        return 0;
+
+    default:
+        FIXME("Unknown cipher suite %#x, returning 0\n", (unsigned)cipherSuite);
+        return 0;
+    }
+}
+
+static ALG_ID schan_impt_get_kx_algid(SSLCipherSuite cipherSuite)
+{
+    TRACE("(%x)\n", (unsigned int)cipherSuite);
+
+    switch (cipherSuite)
+    {
+    /* RSA */
+    case SSL_RSA_WITH_3DES_EDE_CBC_MD5:
+    case SSL_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_RSA_WITH_DES_CBC_MD5:
+    case SSL_RSA_WITH_DES_CBC_SHA:
+    case SSL_RSA_WITH_IDEA_CBC_MD5:
+    case SSL_RSA_WITH_IDEA_CBC_SHA:
+    case SSL_RSA_WITH_NULL_MD5:
+    case SSL_RSA_WITH_NULL_SHA:
+    case SSL_RSA_WITH_RC2_CBC_MD5:
+    case SSL_RSA_WITH_RC4_128_MD5:
+    case SSL_RSA_WITH_RC4_128_SHA:
+    case TLS_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_RSA_WITH_AES_256_CBC_SHA:
+        return CALG_RSA_KEYX;
+
+    /* DHE_RSA */
+    case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_RSA_WITH_DES_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+        return CALG_DH_EPHEM;
+
+    /* DHE_DSS */
+    case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DHE_DSS_WITH_DES_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+        return CALG_DH_EPHEM;
+
+    /* Misc. */
+    case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5:
+    case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_anon_WITH_DES_CBC_SHA:
+    case SSL_DH_anon_WITH_RC4_128_MD5:
+    case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_DSS_WITH_DES_CBC_SHA:
+    case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+    case SSL_DH_RSA_WITH_DES_CBC_SHA:
+    case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_FORTEZZA_DMS_WITH_NULL_SHA:
+    case SSL_NULL_WITH_NULL_NULL:
+    case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA:
+    case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5:
+    case SSL_RSA_EXPORT_WITH_RC4_40_MD5:
+    case TLS_DH_anon_WITH_AES_128_CBC_SHA:
+    case TLS_DH_anon_WITH_AES_256_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+    case TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+    case TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+        FIXME("Don't know key exchange algorithm for cipher suite %#x, returning 0\n", (unsigned)cipherSuite);
+        return 0;
+
+    default:
+        FIXME("Unknown cipher suite %#x, returning 0\n", (unsigned)cipherSuite);
+        return 0;
+    }
+}
+
+static BOOL schan_imp_get_session_connection_info(schan_imp_session s,
+                                                  SecPkgContext_ConnectionInfo *info)
+{
+    SSLCipherSuite cipherSuite;
+    OSStatus status;
+
+    TRACE("(%p/%p, %p)\n", s, s->context, info);
+
+    status = SSLGetNegotiatedCipher(s->context, &cipherSuite);
+    if (status != noErr)
+    {
+        ERR("Failed to get session cipher suite: %ld\n", status);
+        return FALSE;
+    }
+
+    info->dwProtocol = schan_imp_get_session_protocol(s);
+    info->aiCipher = schan_imp_get_cipher_algid(cipherSuite);
+    info->dwCipherStrength = schan_imp_get_cipher_key_size(cipherSuite);
+    info->aiHash = schan_imp_get_mac_algid(cipherSuite);
+    info->dwHashStrength = schan_imp_get_mac_key_size(cipherSuite);
+    info->aiExch = schan_impt_get_kx_algid(cipherSuite);
+    /* FIXME: info->dwExchStrength? */
+    info->dwExchStrength = 0;
+
+    return TRUE;
+}
+
+static size_t schan_imp_get_session_mac_key_size(schan_imp_session s)
+{
+    SSLCipherSuite cipherSuite;
+    OSStatus status;
+
+    TRACE("(%p/%p)\n", s, s->context);
+
+    status = SSLGetNegotiatedCipher(s->context, &cipherSuite);
+    if (status != noErr)
+    {
+        ERR("Failed to get session cipher suite: %ld\n", status);
+        return 0;
+    }
+
+    return schan_imp_get_mac_key_size(cipherSuite);
+}
+
+static SECURITY_STATUS schan_imp_get_session_peer_certificate(schan_imp_session s,
+                                                              PCCERT_CONTEXT *cert)
+{
+    SECURITY_STATUS ret = SEC_E_INTERNAL_ERROR;
+    CFArrayRef certs;
+    OSStatus status;
+
+    TRACE("(%p/%p, %p)\n", s, s->context, cert);
+
+    status = SSLCopyPeerCertificates(s->context, &certs);
+    if (status == noErr && certs)
+    {
+        SecCertificateRef mac_cert;
+        CFDataRef data;
+        if (CFArrayGetCount(certs) &&
+            (mac_cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, 0)) &&
+            (SecKeychainItemExport(mac_cert, kSecFormatX509Cert, 0, NULL, &data) == noErr))
+        {
+            *cert = CertCreateCertificateContext(X509_ASN_ENCODING,
+                    CFDataGetBytePtr(data), CFDataGetLength(data));
+            if (*cert)
+                ret = SEC_E_OK;
+            else
+            {
+                ret = GetLastError();
+                WARN("CertCreateCertificateContext failed: %x\n", ret);
+            }
+            CFRelease(data);
+        }
+        else
+            WARN("Couldn't extract certificate data\n");
+        CFRelease(certs);
+    }
+    else
+        WARN("SSLCopyPeerCertificates failed: %ld\n", (long)status);
+
+    return ret;
+}
+
+static SECURITY_STATUS schan_imp_send(schan_imp_session s, const void *buffer,
+                                      size_t *length)
+{
+    OSStatus status;
+
+    TRACE("(%p/%p, %p, %p/%lu)\n", s, s->context, buffer, length, (unsigned long)*length);
+
+    status = SSLWrite(s->context, buffer, *length, length);
+    if (status == noErr)
+        TRACE("Wrote %lu bytes\n", (unsigned long)*length);
+    else if (status == errSSLWouldBlock)
+    {
+        if (!*length)
+        {
+            TRACE("Would block before being able to write anything\n");
+            return SEC_I_CONTINUE_NEEDED;
+        }
+        else
+            TRACE("Wrote %lu bytes before would block\n", (unsigned long)*length);
+    }
+    else
+    {
+        WARN("SSLWrite failed: %ld\n", (long)status);
+        return SEC_E_INTERNAL_ERROR;
+    }
+
+    return SEC_E_OK;
+}
+
+static SECURITY_STATUS schan_imp_recv(schan_imp_session s, void *buffer,
+                                      size_t *length)
+{
+    OSStatus status;
+
+    TRACE("(%p/%p, %p, %p/%lu)\n", s, s->context, buffer, length, (unsigned long)*length);
+
+    status = SSLRead(s->context, buffer, *length, length);
+    if (status == noErr)
+        TRACE("Read %lu bytes\n", (unsigned long)*length);
+    else if (status == errSSLWouldBlock)
+    {
+        if (!*length)
+        {
+            TRACE("Would block before being able to read anything\n");
+            return SEC_I_CONTINUE_NEEDED;
+        }
+        else
+            TRACE("Read %lu bytes before would block\n", (unsigned long)*length);
+    }
+    else
+    {
+        WARN("SSLRead failed: %ld\n", (long)status);
+        return SEC_E_INTERNAL_ERROR;
+    }
+
+    return SEC_E_OK;
+}
+
+static BOOL schan_imp_is_inited_flag;
+
+static BOOL schan_imp_init(void)
+{
+    TRACE("()\n");
+    schan_imp_is_inited_flag = TRUE;
+    return TRUE;
+}
+
+static BOOL schan_imp_is_inited(void)
+{
+    return schan_imp_is_inited_flag;
+}
+
+static void schan_imp_deinit(void)
+{
+    TRACE("()\n");
+    schan_imp_is_inited_flag = FALSE;
+}
+
+#elif defined(SONAME_LIBGNUTLS)
 
 static void *libgnutls_handle;
 #define MAKE_FUNCPTR(f) static typeof(f) * p##f
@@ -73,6 +1000,401 @@ MAKE_FUNCPTR(gnutls_transport_set_pull_function);
 MAKE_FUNCPTR(gnutls_transport_set_push_function);
 #undef MAKE_FUNCPTR
 
+typedef gnutls_session_t schan_imp_session;
+typedef gnutls_certificate_credentials schan_imp_certificate;
+
+static BOOL schan_imp_create_session(schan_imp_session *s, BOOL is_server)
+{
+    int err = pgnutls_init(s, is_server ? GNUTLS_SERVER : GNUTLS_CLIENT);
+    if (err != GNUTLS_E_SUCCESS)
+    {
+        pgnutls_perror(err);
+        return FALSE;
+    }
+
+    /* FIXME: We should be using the information from the credentials here. */
+    FIXME("Using hardcoded \"NORMAL\" priority\n");
+    err = pgnutls_set_default_priority(*s);
+    if (err != GNUTLS_E_SUCCESS)
+    {
+        pgnutls_perror(err);
+        pgnutls_deinit(*s);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static void schan_imp_dispose_session(schan_imp_session s)
+{
+    pgnutls_deinit(s);
+}
+
+static BOOL schan_imp_allocate_certificate_credentials(schan_imp_certificate *c)
+{
+    int ret = pgnutls_certificate_allocate_credentials(c);
+    if (ret != GNUTLS_E_SUCCESS)
+        pgnutls_perror(ret);
+    return (ret == GNUTLS_E_SUCCESS);
+}
+
+static void schan_imp_free_certificate_credentials(schan_imp_certificate c)
+{
+    pgnutls_certificate_free_credentials(c);
+}
+
+static BOOL schan_imp_set_session_certificate(schan_imp_session s,
+    schan_imp_certificate c)
+{
+    int err = pgnutls_credentials_set(s, GNUTLS_CRD_CERTIFICATE, c);
+    if (err != GNUTLS_E_SUCCESS)
+        pgnutls_perror(err);
+    return (err == GNUTLS_E_SUCCESS);
+}
+
+static ssize_t schan_imp_pull_adaptor(gnutls_transport_ptr_t transport,
+                                      void *buff, size_t buff_len)
+{
+    struct schan_transport *t = transport;
+
+    int ret = schan_pull(transport, buff, &buff_len);
+    if (ret)
+    {
+        pgnutls_transport_set_errno(t->ctx->session, ret);
+        return -1;
+    }
+
+    return buff_len;
+}
+
+static ssize_t schan_imp_push_adaptor(gnutls_transport_ptr_t transport,
+                                      const void *buff, size_t buff_len)
+{
+    struct schan_transport *t = transport;
+
+    int ret = schan_push(transport, buff, &buff_len);
+    if (ret)
+    {
+        pgnutls_transport_set_errno(t->ctx->session, ret);
+        return -1;
+    }
+
+    return buff_len;
+}
+
+static BOOL schan_imp_set_session_io_funcs(schan_imp_session s)
+{
+    pgnutls_transport_set_pull_function(s, schan_imp_pull_adaptor);
+    pgnutls_transport_set_push_function(s, schan_imp_push_adaptor);
+    return TRUE;
+}
+
+static void schan_imp_set_session_transport(schan_imp_session s,
+                                            schan_imp_transport t)
+{
+    pgnutls_transport_set_ptr(s, t);
+}
+
+static SECURITY_STATUS schan_imp_handshake(schan_imp_session s)
+{
+    int err = pgnutls_handshake(ctx->session);
+    switch(err)
+    {
+        case GNUTLS_E_SUCCESS:
+            TRACE("Handshake completed\n");
+            return SEC_E_OK;
+
+        case GNUTLS_E_AGAIN:
+            TRACE("Continue...\n");
+            return SEC_I_CONTINUE_NEEDED;
+
+        case GNUTLS_E_WARNING_ALERT_RECEIVED:
+        case GNUTLS_E_FATAL_ALERT_RECEIVED:
+        {
+            gnutls_alert_description_t alert = pgnutls_alert_get(ctx->session);
+            const char *alert_name = pgnutls_alert_get_name(alert);
+            WARN("ALERT: %d %s\n", alert, alert_name);
+            return SEC_E_INTERNAL_ERROR;
+        }
+
+        default:
+            pgnutls_perror(err);
+            return SEC_E_INTERNAL_ERROR;
+    }
+
+    /* Never reached */
+    return SEC_E_OK;
+}
+
+static unsigned int schannel_get_cipher_block_size(gnutls_cipher_algorithm_t cipher)
+{
+    const struct
+    {
+        gnutls_cipher_algorithm_t cipher;
+        unsigned int block_size;
+    }
+    algorithms[] =
+    {
+        {GNUTLS_CIPHER_3DES_CBC, 8},
+        {GNUTLS_CIPHER_AES_128_CBC, 16},
+        {GNUTLS_CIPHER_AES_256_CBC, 16},
+        {GNUTLS_CIPHER_ARCFOUR_128, 1},
+        {GNUTLS_CIPHER_ARCFOUR_40, 1},
+        {GNUTLS_CIPHER_DES_CBC, 8},
+        {GNUTLS_CIPHER_NULL, 1},
+        {GNUTLS_CIPHER_RC2_40_CBC, 8},
+    };
+    unsigned int i;
+
+    for (i = 0; i < sizeof(algorithms) / sizeof(*algorithms); ++i)
+    {
+        if (algorithms[i].cipher == cipher)
+            return algorithms[i].block_size;
+    }
+
+    FIXME("Unknown cipher %#x, returning 1\n", cipher);
+
+    return 1;
+}
+
+static unsigned int schan_imp_get_session_cipher_block_size(schan_imp_session s)
+{
+    gnutls_cipher_algorithm_t cipher = pgnutls_cipher_get(s);
+    return schannel_get_cipher_block_size(cipher);
+}
+
+static DWORD schannel_get_protocol(gnutls_protocol_t proto)
+{
+    /* FIXME: currently schannel only implements client connections, but
+     * there's no reason it couldn't be used for servers as well.  The
+     * context doesn't tell us which it is, so assume client for now.
+     */
+    switch (proto)
+    {
+    case GNUTLS_SSL3: return SP_PROT_SSL3_CLIENT;
+    case GNUTLS_TLS1_0: return SP_PROT_TLS1_CLIENT;
+    default:
+        FIXME("unknown protocol %d\n", proto);
+        return 0;
+    }
+}
+
+static ALG_ID schannel_get_cipher_algid(gnutls_cipher_algorithm_t cipher)
+{
+    switch (cipher)
+    {
+    case GNUTLS_CIPHER_UNKNOWN:
+    case GNUTLS_CIPHER_NULL: return 0;
+    case GNUTLS_CIPHER_ARCFOUR_40:
+    case GNUTLS_CIPHER_ARCFOUR_128: return CALG_RC4;
+    case GNUTLS_CIPHER_DES_CBC:
+    case GNUTLS_CIPHER_3DES_CBC: return CALG_DES;
+    case GNUTLS_CIPHER_AES_128_CBC:
+    case GNUTLS_CIPHER_AES_256_CBC: return CALG_AES;
+    case GNUTLS_CIPHER_RC2_40_CBC: return CALG_RC2;
+    default:
+        FIXME("unknown algorithm %d\n", cipher);
+        return 0;
+    }
+}
+
+static ALG_ID schannel_get_mac_algid(gnutls_mac_algorithm_t mac)
+{
+    switch (mac)
+    {
+    case GNUTLS_MAC_UNKNOWN:
+    case GNUTLS_MAC_NULL: return 0;
+    case GNUTLS_MAC_MD5: return CALG_MD5;
+    case GNUTLS_MAC_SHA1:
+    case GNUTLS_MAC_SHA256:
+    case GNUTLS_MAC_SHA384:
+    case GNUTLS_MAC_SHA512: return CALG_SHA;
+    default:
+        FIXME("unknown algorithm %d\n", mac);
+        return 0;
+    }
+}
+
+static ALG_ID schannel_get_kx_algid(gnutls_kx_algorithm_t kx)
+{
+    switch (kx)
+    {
+        case GNUTLS_KX_RSA: return CALG_RSA_KEYX;
+        case GNUTLS_KX_DHE_DSS:
+        case GNUTLS_KX_DHE_RSA: return CALG_DH_EPHEM;
+    default:
+        FIXME("unknown algorithm %d\n", kx);
+        return 0;
+    }
+}
+
+static BOOL schan_imp_get_session_connection_info(schan_imp_session s,
+                                                  SecPkgContext_ConnectionInfo *info)
+{
+    gnutls_protocol_t proto = pgnutls_protocol_get_version(s);
+    gnutls_cipher_algorithm_t alg = pgnutls_cipher_get(s);
+    gnutls_mac_algorithm_t mac = pgnutls_mac_get(s);
+    gnutls_kx_algorithm_t kx = pgnutls_kx_get(s);
+
+    info->dwProtocol = schannel_get_protocol(proto);
+    info->aiCipher = schannel_get_cipher_algid(alg);
+    info->dwCipherStrength = pgnutls_cipher_get_key_size(alg);
+    info->aiHash = schannel_get_mac_algid(mac);
+    info->dwHashStrength = pgnutls_mac_get_key_size(mac);
+    info->aiExch = schannel_get_kx_algid(kx);
+    /* FIXME: info->dwExchStrength? */
+    info->dwExchStrength = 0;
+    return TRUE;
+}
+
+static size_t schan_imp_get_session_mac_key_size(schan_imp_session s)
+{
+    gnutls_mac_algorithm_t mac = pgnutls_mac_get(s);
+    return pgnutls_mac_get_key_size(mac);
+}
+
+static SECURITY_STATUS schan_imp_get_session_peer_certificate(schan_imp_session s,
+                                                              PCCERT_CONTEXT *cert)
+{
+    unsigned int list_size;
+    const gnutls_datum_t *datum;
+
+    datum = pgnutls_certificate_get_peers(s, &list_size);
+    if (datum)
+    {
+        *cert = CertCreateCertificateContext(X509_ASN_ENCODING,
+                datum->data, datum->size);
+        if (!*cert)
+            return GetLastError();
+        else
+            return SEC_E_OK;
+    }
+    else
+        return SEC_E_INTERNAL_ERROR;
+}
+
+static SECURITY_STATUS schan_imp_send(schan_imp_session s, const void *buffer,
+                                      size_t *length)
+{
+    ssize_t ret = pgnutls_record_send(s, buffer, *length);
+    if (ret >= 0)
+        *length = ret;
+    else if (ret == GNUTLS_E_AGAIN)
+        return SEC_I_CONTINUE_NEEDED;
+    else
+    {
+        pgnutls_perror(ret);
+        return SEC_E_INTERNAL_ERROR;
+    }
+
+    return SEC_E_OK;
+}
+
+static SECURITY_STATUS schan_imp_recv(schan_imp_session s, void *buffer,
+                                      size_t *length)
+{
+    ssize_t ret = pgnutls_record_recv(s, buffer, *length);
+    if (ret >= 0)
+        *length = ret;
+    else if (ret == GNUTLS_E_AGAIN)
+        return SEC_I_CONTINUE_NEEDED;
+    else
+    {
+        pgnutls_perror(ret);
+        return SEC_E_INTERNAL_ERROR;
+    }
+
+    return SEC_E_OK;
+}
+
+static void schan_gnutls_log(int level, const char *msg)
+{
+    TRACE("<%d> %s", level, msg);
+}
+
+static BOOL schan_imp_init(void)
+{
+    libgnutls_handle = wine_dlopen(SONAME_LIBGNUTLS, RTLD_NOW, NULL, 0);
+    if (!libgnutls_handle)
+    {
+        WARN("Failed to load libgnutls.\n");
+        return FALSE;
+    }
+
+#define LOAD_FUNCPTR(f) \
+    if (!(p##f = wine_dlsym(libgnutls_handle, #f, NULL, 0))) \
+    { \
+        ERR("Failed to load %s\n", #f); \
+        goto fail; \
+    }
+
+    LOAD_FUNCPTR(gnutls_alert_get)
+    LOAD_FUNCPTR(gnutls_alert_get_name)
+    LOAD_FUNCPTR(gnutls_certificate_allocate_credentials)
+    LOAD_FUNCPTR(gnutls_certificate_free_credentials)
+    LOAD_FUNCPTR(gnutls_certificate_get_peers)
+    LOAD_FUNCPTR(gnutls_cipher_get)
+    LOAD_FUNCPTR(gnutls_cipher_get_key_size)
+    LOAD_FUNCPTR(gnutls_credentials_set)
+    LOAD_FUNCPTR(gnutls_deinit)
+    LOAD_FUNCPTR(gnutls_global_deinit)
+    LOAD_FUNCPTR(gnutls_global_init)
+    LOAD_FUNCPTR(gnutls_global_set_log_function)
+    LOAD_FUNCPTR(gnutls_global_set_log_level)
+    LOAD_FUNCPTR(gnutls_handshake)
+    LOAD_FUNCPTR(gnutls_init)
+    LOAD_FUNCPTR(gnutls_kx_get)
+    LOAD_FUNCPTR(gnutls_mac_get)
+    LOAD_FUNCPTR(gnutls_mac_get_key_size)
+    LOAD_FUNCPTR(gnutls_perror)
+    LOAD_FUNCPTR(gnutls_protocol_get_version)
+    LOAD_FUNCPTR(gnutls_set_default_priority)
+    LOAD_FUNCPTR(gnutls_record_recv);
+    LOAD_FUNCPTR(gnutls_record_send);
+    LOAD_FUNCPTR(gnutls_transport_set_errno)
+    LOAD_FUNCPTR(gnutls_transport_set_ptr)
+    LOAD_FUNCPTR(gnutls_transport_set_pull_function)
+    LOAD_FUNCPTR(gnutls_transport_set_push_function)
+#undef LOAD_FUNCPTR
+
+    ret = pgnutls_global_init();
+    if (ret != GNUTLS_E_SUCCESS)
+    {
+        pgnutls_perror(ret);
+        goto fail;
+    }
+
+    if (TRACE_ON(secur32))
+    {
+        pgnutls_global_set_log_level(4);
+        pgnutls_global_set_log_function(schan_gnutls_log);
+    }
+
+    return TRUE;
+
+fail:
+    wine_dlclose(libgnutls_handle, NULL, 0);
+    libgnutls_handle = NULL;
+    return FALSE;
+}
+
+static BOOL schan_imp_is_inited(void)
+{
+    return (libgnutls_handle != NULL);
+}
+
+static void schan_imp_deinit(void)
+{
+    pgnutls_global_deinit();
+    wine_dlclose(libgnutls_handle, NULL, 0);
+    libgnutls_handle = NULL;
+}
+
+#endif
+
+
+#if defined(HAVE_SECURITY_SECURITY_H) || defined(SONAME_LIBGNUTLS)
+
 #define SCHAN_INVALID_HANDLE ~0UL
 
 enum schan_handle_type
@@ -91,12 +1413,12 @@ struct schan_handle
 struct schan_credentials
 {
     ULONG credential_use;
-    gnutls_certificate_credentials credentials;
+    schan_imp_certificate credentials;
 };
 
 struct schan_context
 {
-    gnutls_session_t session;
+    schan_imp_session session;
     ULONG req_ctx_attr;
 };
 
@@ -364,7 +1686,6 @@ static SECURITY_STATUS schan_AcquireClientCredentials(const SCHANNEL_CRED *schan
     if (st == SEC_E_OK)
     {
         ULONG_PTR handle;
-        int ret;
 
         creds = HeapAlloc(GetProcessHeap(), 0, sizeof(*creds));
         if (!creds) return SEC_E_INSUFFICIENT_MEMORY;
@@ -373,10 +1694,8 @@ static SECURITY_STATUS schan_AcquireClientCredentials(const SCHANNEL_CRED *schan
         if (handle == SCHAN_INVALID_HANDLE) goto fail;
 
         creds->credential_use = SECPKG_CRED_OUTBOUND;
-        ret = pgnutls_certificate_allocate_credentials(&creds->credentials);
-        if (ret != GNUTLS_E_SUCCESS)
+        if (!schan_imp_allocate_certificate_credentials(&creds->credentials))
         {
-            pgnutls_perror(ret);
             schan_free_handle(handle, SCHAN_HANDLE_CRED);
             goto fail;
         }
@@ -483,7 +1802,7 @@ static SECURITY_STATUS SEC_ENTRY schan_FreeCredentialsHandle(
     if (!creds) return SEC_E_INVALID_HANDLE;
 
     if (creds->credential_use == SECPKG_CRED_OUTBOUND)
-        pgnutls_certificate_free_credentials(creds->credentials);
+        schan_imp_free_certificate_credentials(creds->credentials);
     HeapFree(GetProcessHeap(), 0, creds);
 
     return SEC_E_OK;
@@ -587,58 +1906,93 @@ static char *schan_get_buffer(const struct schan_transport *t, struct schan_buff
     return (char *)buffer->pvBuffer + s->offset;
 }
 
-static ssize_t schan_pull(gnutls_transport_ptr_t transport, void *buff, size_t buff_len)
+/* schan_pull
+ *      Read data from the transport input buffer.
+ *
+ * transport - The session transport object.
+ * buff - The buffer into which to store the read data.  Must be at least
+ *        *buff_len bytes in length.
+ * buff_len - On input, *buff_len is the desired length to read.  On successful
+ *            return, *buff_len is the number of bytes actually read.
+ *
+ * Returns:
+ *  0 on success, in which case:
+ *      *buff_len == 0 indicates end of file.
+ *      *buff_len > 0 indicates that some data was read.  May be less than
+ *          what was requested, in which case the caller should call again if/
+ *          when they want more.
+ *  EAGAIN when no data could be read without blocking
+ *  another errno-style error value on failure
+ *
+ */
+static int schan_pull(schan_imp_transport transport, void *buff, size_t *buff_len)
 {
     struct schan_transport *t = transport;
     char *b;
+    size_t local_len = *buff_len;
+
+    TRACE("Pull %zu bytes\n", local_len);
 
-    TRACE("Pull %zu bytes\n", buff_len);
+    *buff_len = 0;
 
-    b = schan_get_buffer(t, &t->in, &buff_len);
+    b = schan_get_buffer(t, &t->in, &local_len);
     if (!b)
-    {
-        pgnutls_transport_set_errno(t->ctx->session, EAGAIN);
-        return -1;
-    }
+        return EAGAIN;
 
-    if (t->in.limit != 0 && t->in.offset + buff_len >= t->in.limit)
+    if (t->in.limit != 0 && t->in.offset + local_len >= t->in.limit)
     {
-        buff_len = t->in.limit - t->in.offset;
-        if (buff_len == 0)
-        {
-            pgnutls_transport_set_errno(t->ctx->session, EAGAIN);
-            return -1;
-        }
+        local_len = t->in.limit - t->in.offset;
+        if (local_len == 0)
+            return EAGAIN;
     }
 
-    memcpy(buff, b, buff_len);
-    t->in.offset += buff_len;
+    memcpy(buff, b, local_len);
+    t->in.offset += local_len;
 
-    TRACE("Read %zu bytes\n", buff_len);
+    TRACE("Read %zu bytes\n", local_len);
 
-    return buff_len;
+    *buff_len = local_len;
+    return 0;
 }
 
-static ssize_t schan_push(gnutls_transport_ptr_t transport, const void *buff, size_t buff_len)
+/* schan_push
+ *      Write data to the transport output buffer.
+ *
+ * transport - The session transport object.
+ * buff - The buffer of data to write.  Must be at least *buff_len bytes in length.
+ * buff_len - On input, *buff_len is the desired length to write.  On successful
+ *            return, *buff_len is the number of bytes actually written.
+ *
+ * Returns:
+ *  0 on success
+ *      *buff_len will be > 0 indicating how much data was written.  May be less
+ *          than what was requested, in which case the caller should call again
+            if/when they want to write more.
+ *  EAGAIN when no data could be written without blocking
+ *  another errno-style error value on failure
+ *
+ */
+static int schan_push(schan_imp_transport transport, const void *buff, size_t *buff_len)
 {
     struct schan_transport *t = transport;
     char *b;
+    size_t local_len = *buff_len;
+
+    TRACE("Push %zu bytes\n", local_len);
 
-    TRACE("Push %zu bytes\n", buff_len);
+    *buff_len = 0;
 
-    b = schan_get_buffer(t, &t->out, &buff_len);
+    b = schan_get_buffer(t, &t->out, &local_len);
     if (!b)
-    {
-        pgnutls_transport_set_errno(t->ctx->session, EAGAIN);
-        return -1;
-    }
+        return EAGAIN;
 
-    memcpy(b, buff, buff_len);
-    t->out.offset += buff_len;
+    memcpy(b, buff, local_len);
+    t->out.offset += local_len;
 
-    TRACE("Wrote %zu bytes\n", buff_len);
+    TRACE("Wrote %zu bytes\n", local_len);
 
-    return buff_len;
+    *buff_len = local_len;
+    return 0;
 }
 
 static int schan_init_sec_ctx_get_next_buffer(const struct schan_transport *t, struct schan_buffers *s)
@@ -691,7 +2045,7 @@ static SECURITY_STATUS SEC_ENTRY schan_InitializeSecurityContextW(
     struct schan_buffers *out_buffers;
     struct schan_credentials *cred;
     struct schan_transport transport;
-    int err;
+    SECURITY_STATUS ret;
 
     TRACE("%p %p %s 0x%08x %d %d %p %d %p %p %p %p\n", phCredential, phContext,
      debugstr_w(pszTargetName), fContextReq, Reserved1, TargetDataRep, pInput,
@@ -725,38 +2079,22 @@ static SECURITY_STATUS SEC_ENTRY schan_InitializeSecurityContextW(
             return SEC_E_INTERNAL_ERROR;
         }
 
-        err = pgnutls_init(&ctx->session, GNUTLS_CLIENT);
-        if (err != GNUTLS_E_SUCCESS)
+        if (!schan_imp_create_session(&ctx->session, FALSE))
         {
-            pgnutls_perror(err);
             schan_free_handle(handle, SCHAN_HANDLE_CTX);
             HeapFree(GetProcessHeap(), 0, ctx);
             return SEC_E_INTERNAL_ERROR;
         }
 
-        /* FIXME: We should be using the information from the credentials here. */
-        FIXME("Using hardcoded \"NORMAL\" priority\n");
-        err = pgnutls_set_default_priority(ctx->session);
-        if (err != GNUTLS_E_SUCCESS)
-        {
-            pgnutls_perror(err);
-            pgnutls_deinit(ctx->session);
-            schan_free_handle(handle, SCHAN_HANDLE_CTX);
-            HeapFree(GetProcessHeap(), 0, ctx);
-        }
-
-        err = pgnutls_credentials_set(ctx->session, GNUTLS_CRD_CERTIFICATE, cred->credentials);
-        if (err != GNUTLS_E_SUCCESS)
+        if (!schan_imp_set_session_certificate(ctx->session, cred->credentials) ||
+            !schan_imp_set_session_io_funcs(ctx->session))
         {
-            pgnutls_perror(err);
-            pgnutls_deinit(ctx->session);
+            schan_imp_dispose_session(ctx->session);
             schan_free_handle(handle, SCHAN_HANDLE_CTX);
             HeapFree(GetProcessHeap(), 0, ctx);
+            return SEC_E_INTERNAL_ERROR;
         }
 
-        pgnutls_transport_set_pull_function(ctx->session, schan_pull);
-        pgnutls_transport_set_push_function(ctx->session, schan_push);
-
         phNewContext->dwLower = handle;
         phNewContext->dwUpper = 0;
     }
@@ -770,10 +2108,10 @@ static SECURITY_STATUS SEC_ENTRY schan_InitializeSecurityContextW(
     transport.ctx = ctx;
     init_schan_buffers(&transport.in, pInput, schan_init_sec_ctx_get_next_buffer);
     init_schan_buffers(&transport.out, pOutput, schan_init_sec_ctx_get_next_buffer);
-    pgnutls_transport_set_ptr(ctx->session, &transport);
+    schan_imp_set_session_transport(ctx->session, &transport);
 
     /* Perform the TLS handshake */
-    err = pgnutls_handshake(ctx->session);
+    ret = schan_imp_handshake(ctx->session);
 
     if(transport.in.offset && transport.in.offset != pInput->pBuffers[0].cbBuffer) {
         if(pInput->cBuffers<2 || pInput->pBuffers[1].BufferType!=SECBUFFER_EMPTY)
@@ -794,29 +2132,7 @@ static SECURITY_STATUS SEC_ENTRY schan_InitializeSecurityContextW(
     if (ctx->req_ctx_attr & ISC_REQ_ALLOCATE_MEMORY)
         *pfContextAttr |= ISC_RET_ALLOCATED_MEMORY;
 
-    switch(err)
-    {
-        case GNUTLS_E_SUCCESS:
-            TRACE("Handshake completed\n");
-            return SEC_E_OK;
-
-        case GNUTLS_E_AGAIN:
-            TRACE("Continue...\n");
-            return SEC_I_CONTINUE_NEEDED;
-
-        case GNUTLS_E_WARNING_ALERT_RECEIVED:
-        case GNUTLS_E_FATAL_ALERT_RECEIVED:
-        {
-            gnutls_alert_description_t alert = pgnutls_alert_get(ctx->session);
-            const char *alert_name = pgnutls_alert_get_name(alert);
-            WARN("ALERT: %d %s\n", alert, alert_name);
-            return SEC_E_INTERNAL_ERROR;
-        }
-
-        default:
-            pgnutls_perror(err);
-            return SEC_E_INTERNAL_ERROR;
-    }
+    return ret;
 }
 
 /***********************************************************************
@@ -851,102 +2167,6 @@ static SECURITY_STATUS SEC_ENTRY schan_InitializeSecurityContextA(
     return ret;
 }
 
-static unsigned int schannel_get_cipher_block_size(gnutls_cipher_algorithm_t cipher)
-{
-    const struct
-    {
-        gnutls_cipher_algorithm_t cipher;
-        unsigned int block_size;
-    }
-    algorithms[] =
-    {
-        {GNUTLS_CIPHER_3DES_CBC, 8},
-        {GNUTLS_CIPHER_AES_128_CBC, 16},
-        {GNUTLS_CIPHER_AES_256_CBC, 16},
-        {GNUTLS_CIPHER_ARCFOUR_128, 1},
-        {GNUTLS_CIPHER_ARCFOUR_40, 1},
-        {GNUTLS_CIPHER_DES_CBC, 8},
-        {GNUTLS_CIPHER_NULL, 1},
-        {GNUTLS_CIPHER_RC2_40_CBC, 8},
-    };
-    unsigned int i;
-
-    for (i = 0; i < sizeof(algorithms) / sizeof(*algorithms); ++i)
-    {
-        if (algorithms[i].cipher == cipher)
-            return algorithms[i].block_size;
-    }
-
-    FIXME("Unknown cipher %#x, returning 1\n", cipher);
-
-    return 1;
-}
-
-static DWORD schannel_get_protocol(gnutls_protocol_t proto)
-{
-    /* FIXME: currently schannel only implements client connections, but
-     * there's no reason it couldn't be used for servers as well.  The
-     * context doesn't tell us which it is, so assume client for now.
-     */
-    switch (proto)
-    {
-    case GNUTLS_SSL3: return SP_PROT_SSL3_CLIENT;
-    case GNUTLS_TLS1_0: return SP_PROT_TLS1_CLIENT;
-    default:
-        FIXME("unknown protocol %d\n", proto);
-        return 0;
-    }
-}
-
-static ALG_ID schannel_get_cipher_algid(gnutls_cipher_algorithm_t cipher)
-{
-    switch (cipher)
-    {
-    case GNUTLS_CIPHER_UNKNOWN:
-    case GNUTLS_CIPHER_NULL: return 0;
-    case GNUTLS_CIPHER_ARCFOUR_40:
-    case GNUTLS_CIPHER_ARCFOUR_128: return CALG_RC4;
-    case GNUTLS_CIPHER_DES_CBC:
-    case GNUTLS_CIPHER_3DES_CBC: return CALG_DES;
-    case GNUTLS_CIPHER_AES_128_CBC:
-    case GNUTLS_CIPHER_AES_256_CBC: return CALG_AES;
-    case GNUTLS_CIPHER_RC2_40_CBC: return CALG_RC2;
-    default:
-        FIXME("unknown algorithm %d\n", cipher);
-        return 0;
-    }
-}
-
-static ALG_ID schannel_get_mac_algid(gnutls_mac_algorithm_t mac)
-{
-    switch (mac)
-    {
-    case GNUTLS_MAC_UNKNOWN:
-    case GNUTLS_MAC_NULL: return 0;
-    case GNUTLS_MAC_MD5: return CALG_MD5;
-    case GNUTLS_MAC_SHA1:
-    case GNUTLS_MAC_SHA256:
-    case GNUTLS_MAC_SHA384:
-    case GNUTLS_MAC_SHA512: return CALG_SHA;
-    default:
-        FIXME("unknown algorithm %d\n", mac);
-        return 0;
-    }
-}
-
-static ALG_ID schannel_get_kx_algid(gnutls_kx_algorithm_t kx)
-{
-    switch (kx)
-    {
-        case GNUTLS_KX_RSA: return CALG_RSA_KEYX;
-        case GNUTLS_KX_DHE_DSS:
-        case GNUTLS_KX_DHE_RSA: return CALG_DH_EPHEM;
-    default:
-        FIXME("unknown algorithm %d\n", kx);
-        return 0;
-    }
-}
-
 static SECURITY_STATUS SEC_ENTRY schan_QueryContextAttributesW(
         PCtxtHandle context_handle, ULONG attribute, PVOID buffer)
 {
@@ -963,10 +2183,8 @@ static SECURITY_STATUS SEC_ENTRY schan_QueryContextAttributesW(
         case SECPKG_ATTR_STREAM_SIZES:
         {
             SecPkgContext_StreamSizes *stream_sizes = buffer;
-            gnutls_mac_algorithm_t mac = pgnutls_mac_get(ctx->session);
-            size_t mac_size = pgnutls_mac_get_key_size(mac);
-            gnutls_cipher_algorithm_t cipher = pgnutls_cipher_get(ctx->session);
-            unsigned int block_size = schannel_get_cipher_block_size(cipher);
+            size_t mac_size = schan_imp_get_session_mac_key_size(ctx->session);
+            unsigned int block_size = schan_imp_get_session_cipher_block_size(ctx->session);
 
             TRACE("Using %zu mac bytes, block size %u\n", mac_size, block_size);
 
@@ -980,40 +2198,14 @@ static SECURITY_STATUS SEC_ENTRY schan_QueryContextAttributesW(
         }
         case SECPKG_ATTR_REMOTE_CERT_CONTEXT:
         {
-            unsigned int list_size;
-            const gnutls_datum_t *datum;
-
-            datum = pgnutls_certificate_get_peers(ctx->session, &list_size);
-            if (datum)
-            {
-                PCCERT_CONTEXT *cert = buffer;
-
-                *cert = CertCreateCertificateContext(X509_ASN_ENCODING,
-                        datum->data, datum->size);
-                if (!*cert)
-                    return GetLastError();
-                else
-                    return SEC_E_OK;
-            }
-            else
-                return SEC_E_INTERNAL_ERROR;
+            PCCERT_CONTEXT *cert = buffer;
+            return schan_imp_get_session_peer_certificate(ctx->session, cert);
         }
         case SECPKG_ATTR_CONNECTION_INFO:
         {
             SecPkgContext_ConnectionInfo *info = buffer;
-            gnutls_protocol_t proto = pgnutls_protocol_get_version(ctx->session);
-            gnutls_cipher_algorithm_t alg = pgnutls_cipher_get(ctx->session);
-            gnutls_mac_algorithm_t mac = pgnutls_mac_get(ctx->session);
-            gnutls_kx_algorithm_t kx = pgnutls_kx_get(ctx->session);
-
-            info->dwProtocol = schannel_get_protocol(proto);
-            info->aiCipher = schannel_get_cipher_algid(alg);
-            info->dwCipherStrength = pgnutls_cipher_get_key_size(alg);
-            info->aiHash = schannel_get_mac_algid(mac);
-            info->dwHashStrength = pgnutls_mac_get_key_size(mac);
-            info->aiExch = schannel_get_kx_algid(kx);
-            /* FIXME: info->dwExchStrength? */
-            info->dwExchStrength = 0;
+            if (!schan_imp_get_session_connection_info(ctx->session, info))
+                return SEC_E_INTERNAL_ERROR;
             return SEC_E_OK;
         }
 
@@ -1099,7 +2291,6 @@ static SECURITY_STATUS SEC_ENTRY schan_EncryptMessage(PCtxtHandle context_handle
     SIZE_T data_size;
     char *data;
     ssize_t sent = 0;
-    ssize_t ret;
     int idx;
 
     TRACE("context_handle %p, quality %d, message %p, message_seq_no %d\n",
@@ -1128,23 +2319,22 @@ static SECURITY_STATUS SEC_ENTRY schan_EncryptMessage(PCtxtHandle context_handle
         init_schan_buffers(&transport.out, message, schan_encrypt_message_get_next_buffer);
     else
         init_schan_buffers(&transport.out, message, schan_encrypt_message_get_next_buffer_token);
-    pgnutls_transport_set_ptr(ctx->session, &transport);
+    schan_imp_set_session_transport(ctx->session, &transport);
 
     while (sent < data_size)
     {
-        ret = pgnutls_record_send(ctx->session, data + sent, data_size - sent);
-        if (ret < 0)
+        size_t length = data_size - sent;
+        SECURITY_STATUS status = schan_imp_send(ctx->session, data + sent, &length);
+        if (status == SEC_I_CONTINUE_NEEDED)
+            break;
+        else if (status != SEC_E_OK)
         {
-            if (ret != GNUTLS_E_AGAIN)
-            {
-                pgnutls_perror(ret);
-                HeapFree(GetProcessHeap(), 0, data);
-                ERR("Returning SEC_E_INTERNAL_ERROR\n");
-                return SEC_E_INTERNAL_ERROR;
-            }
-            else break;
+            HeapFree(GetProcessHeap(), 0, data);
+            ERR("Returning %lu\n", (unsigned long)status);
+            return status;
         }
-        sent += ret;
+
+        sent += length;
     }
 
     TRACE("Sent %zd bytes\n", sent);
@@ -1230,7 +2420,6 @@ static SECURITY_STATUS SEC_ENTRY schan_DecryptMessage(PCtxtHandle context_handle
     char *data;
     unsigned expected_size;
     ssize_t received = 0;
-    ssize_t ret;
     int idx;
     unsigned char *buf_ptr;
 
@@ -1272,36 +2461,32 @@ static SECURITY_STATUS SEC_ENTRY schan_DecryptMessage(PCtxtHandle context_handle
     init_schan_buffers(&transport.in, message, schan_decrypt_message_get_next_buffer);
     transport.in.limit = expected_size;
     init_schan_buffers(&transport.out, NULL, NULL);
-    pgnutls_transport_set_ptr(ctx->session, (gnutls_transport_ptr_t)&transport);
+    schan_imp_set_session_transport(ctx->session, (schan_imp_transport)&transport);
 
     while (received < data_size)
     {
-        ret = pgnutls_record_recv(ctx->session, data + received, data_size - received);
-        if (ret < 0)
+        size_t length = data_size - received;
+        SECURITY_STATUS status = schan_imp_recv(ctx->session, data + received, &length);
+        if (status == SEC_I_CONTINUE_NEEDED)
         {
-            if (ret == GNUTLS_E_AGAIN)
-            {
-                if (!received)
-                {
-                    pgnutls_perror(ret);
-                    HeapFree(GetProcessHeap(), 0, data);
-                    TRACE("Returning SEC_E_INCOMPLETE_MESSAGE\n");
-                    return SEC_E_INCOMPLETE_MESSAGE;
-                }
-                break;
-            }
-            else
+            if (!received)
             {
-                pgnutls_perror(ret);
                 HeapFree(GetProcessHeap(), 0, data);
-                ERR("Returning SEC_E_INTERNAL_ERROR\n");
-                return SEC_E_INTERNAL_ERROR;
+                TRACE("Returning SEC_E_INCOMPLETE_MESSAGE\n");
+                return SEC_E_INCOMPLETE_MESSAGE;
             }
+            break;
+        }
+        else if (status != SEC_E_OK)
+        {
+            HeapFree(GetProcessHeap(), 0, data);
+            ERR("Returning %lu\n", (unsigned long)status);
+            return status;
         }
-        else if (!ret)
+        else if (!length)
             break;
 
-        received += ret;
+        received += length;
     }
 
     TRACE("Received %zd bytes\n", received);
@@ -1336,17 +2521,12 @@ static SECURITY_STATUS SEC_ENTRY schan_DeleteSecurityContext(PCtxtHandle context
     ctx = schan_free_handle(context_handle->dwLower, SCHAN_HANDLE_CTX);
     if (!ctx) return SEC_E_INVALID_HANDLE;
 
-    pgnutls_deinit(ctx->session);
+    schan_imp_dispose_session(ctx->session);
     HeapFree(GetProcessHeap(), 0, ctx);
 
     return SEC_E_OK;
 }
 
-static void schan_gnutls_log(int level, const char *msg)
-{
-    TRACE("<%d> %s", level, msg);
-}
-
 static const SecurityFunctionTableA schanTableA = {
     1,
     NULL, /* EnumerateSecurityPackagesA */
@@ -1438,63 +2618,9 @@ void SECUR32_initSchannelSP(void)
             (SEC_WCHAR *)schannelComment },
     };
     SecureProvider *provider;
-    int ret;
 
-    libgnutls_handle = wine_dlopen(SONAME_LIBGNUTLS, RTLD_NOW, NULL, 0);
-    if (!libgnutls_handle)
-    {
-        WARN("Failed to load libgnutls.\n");
+    if (!schan_imp_init())
         return;
-    }
-
-#define LOAD_FUNCPTR(f) \
-    if (!(p##f = wine_dlsym(libgnutls_handle, #f, NULL, 0))) \
-    { \
-        ERR("Failed to load %s\n", #f); \
-        goto fail; \
-    }
-
-    LOAD_FUNCPTR(gnutls_alert_get)
-    LOAD_FUNCPTR(gnutls_alert_get_name)
-    LOAD_FUNCPTR(gnutls_certificate_allocate_credentials)
-    LOAD_FUNCPTR(gnutls_certificate_free_credentials)
-    LOAD_FUNCPTR(gnutls_certificate_get_peers)
-    LOAD_FUNCPTR(gnutls_cipher_get)
-    LOAD_FUNCPTR(gnutls_cipher_get_key_size)
-    LOAD_FUNCPTR(gnutls_credentials_set)
-    LOAD_FUNCPTR(gnutls_deinit)
-    LOAD_FUNCPTR(gnutls_global_deinit)
-    LOAD_FUNCPTR(gnutls_global_init)
-    LOAD_FUNCPTR(gnutls_global_set_log_function)
-    LOAD_FUNCPTR(gnutls_global_set_log_level)
-    LOAD_FUNCPTR(gnutls_handshake)
-    LOAD_FUNCPTR(gnutls_init)
-    LOAD_FUNCPTR(gnutls_kx_get)
-    LOAD_FUNCPTR(gnutls_mac_get)
-    LOAD_FUNCPTR(gnutls_mac_get_key_size)
-    LOAD_FUNCPTR(gnutls_perror)
-    LOAD_FUNCPTR(gnutls_protocol_get_version)
-    LOAD_FUNCPTR(gnutls_set_default_priority)
-    LOAD_FUNCPTR(gnutls_record_recv);
-    LOAD_FUNCPTR(gnutls_record_send);
-    LOAD_FUNCPTR(gnutls_transport_set_errno)
-    LOAD_FUNCPTR(gnutls_transport_set_ptr)
-    LOAD_FUNCPTR(gnutls_transport_set_pull_function)
-    LOAD_FUNCPTR(gnutls_transport_set_push_function)
-#undef LOAD_FUNCPTR
-
-    ret = pgnutls_global_init();
-    if (ret != GNUTLS_E_SUCCESS)
-    {
-        pgnutls_perror(ret);
-        goto fail;
-    }
-
-    if (TRACE_ON(secur32))
-    {
-        pgnutls_global_set_log_level(4);
-        pgnutls_global_set_log_function(schan_gnutls_log);
-    }
 
     schan_handle_table = HeapAlloc(GetProcessHeap(), 0, 64 * sizeof(*schan_handle_table));
     if (!schan_handle_table)
@@ -1518,8 +2644,7 @@ void SECUR32_initSchannelSP(void)
 fail:
     HeapFree(GetProcessHeap(), 0, schan_handle_table);
     schan_handle_table = NULL;
-    wine_dlclose(libgnutls_handle, NULL, 0);
-    libgnutls_handle = NULL;
+    schan_imp_deinit();
     return;
 }
 
@@ -1527,16 +2652,16 @@ void SECUR32_deinitSchannelSP(void)
 {
     SIZE_T i = schan_handle_count;
 
-    if (!libgnutls_handle) return;
+    if (!schan_imp_is_inited()) return;
 
     /* deinitialized sessions first because a pointer to the credentials
-     * are stored for the session by calling gnutls_credentials_set. */
+     * may be stored for the session. */
     while (i--)
     {
         if (schan_handle_table[i].type == SCHAN_HANDLE_CTX)
         {
             struct schan_context *ctx = schan_free_handle(i, SCHAN_HANDLE_CTX);
-            pgnutls_deinit(ctx->session);
+            schan_imp_dispose_session(ctx->session);
             HeapFree(GetProcessHeap(), 0, ctx);
         }
     }
@@ -1547,22 +2672,21 @@ void SECUR32_deinitSchannelSP(void)
         {
             struct schan_credentials *cred;
             cred = schan_free_handle(i, SCHAN_HANDLE_CRED);
-            pgnutls_certificate_free_credentials(cred->credentials);
+            schan_imp_free_certificate_credentials(cred->credentials);
             HeapFree(GetProcessHeap(), 0, cred);
         }
     }
     HeapFree(GetProcessHeap(), 0, schan_handle_table);
-    pgnutls_global_deinit();
-    wine_dlclose(libgnutls_handle, NULL, 0);
+    schan_imp_deinit();
 }
 
-#else /* SONAME_LIBGNUTLS */
+#else /* HAVE_SECURITY_SECURITY_H || SONAME_LIBGNUTLS */
 
 void SECUR32_initSchannelSP(void)
 {
-    ERR("libgnutls not found, SSL connections will fail\n");
+    ERR("TLS library not found, SSL connections will fail\n");
 }
 
 void SECUR32_deinitSchannelSP(void) {}
 
-#endif /* SONAME_LIBGNUTLS */
+#endif /* HAVE_SECURITY_SECURITY_H || SONAME_LIBGNUTLS */
diff --git a/include/config.h.in b/include/config.h.in
index 2e4370d..18210d8 100644
--- a/include/config.h.in
+++ b/include/config.h.in
@@ -701,6 +701,9 @@
 /* Define to 1 if you have the <scsi/sg.h> header file. */
 #undef HAVE_SCSI_SG_H
 
+/* Define to 1 if you have the <Security/Security.h> header file. */
+#undef HAVE_SECURITY_SECURITY_H
+
 /* Define to 1 if you have the `select' function. */
 #undef HAVE_SELECT
 
