You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by jr...@apache.org on 2018/04/05 19:34:02 UTC
[27/51] [partial] qpid-proton git commit: PROTON-1728: Reorganize the
source tree
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/37136940/c/src/ssl/schannel.c
----------------------------------------------------------------------
diff --git a/c/src/ssl/schannel.c b/c/src/ssl/schannel.c
new file mode 100644
index 0000000..b541c96
--- /dev/null
+++ b/c/src/ssl/schannel.c
@@ -0,0 +1,2326 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/*
+ * SChannel is designed to encrypt and decrypt data in place. So a
+ * given buffer is expected to sometimes contain encrypted data,
+ * sometimes decrypted data, and occasionally both. Outgoing buffers
+ * need to reserve space for the TLS header and trailer. Read
+ * operations need to ignore the same headers and trailers from
+ * incoming buffers. Outgoing is simple because we choose record
+ * boundaries. Incoming is complicated by handling incomplete TLS
+ * records, and buffering contiguous data for the app layer that may
+ * span many records. A lazy double buffering system is used for
+ * the latter.
+ */
+
+#include <proton/ssl.h>
+#include <proton/engine.h>
+#include "core/engine-internal.h"
+#include "platform/platform.h"
+#include "core/util.h"
+#include "core/autodetect.h"
+
+#include <assert.h>
+
+// security.h needs to see this to distinguish from kernel use.
+#include <windows.h>
+#define SECURITY_WIN32
+#include <security.h>
+#include <Schnlsp.h>
+#include <WinInet.h>
+#undef SECURITY_WIN32
+
+
+/** @file
+ * SSL/TLS support API.
+ *
+ * This file contains an SChannel-based implemention of the SSL/TLS API for Windows platforms.
+ */
+
+static void ssl_log_error(const char *fmt, ...);
+static void ssl_log(pn_transport_t *transport, const char *fmt, ...);
+static void ssl_log_error_status(HRESULT status, const char *fmt, ...);
+static HCERTSTORE open_cert_db(const char *store_name, const char *passwd, int *error);
+
+// Thread support. Some SChannel objects are shared or ref-counted.
+// Consistent with the rest of Proton, we assume a connection (and
+// therefore its pn_ssl_t) will not be accessed concurrently by
+// multiple threads.
+class csguard {
+ public:
+ csguard(CRITICAL_SECTION *cs) : cs_(cs), set_(true) { EnterCriticalSection(cs_); }
+ ~csguard() { if (set_) LeaveCriticalSection(cs_); }
+ void release() {
+ if (set_) {
+ set_ = false;
+ LeaveCriticalSection(cs_);
+ }
+ }
+ private:
+ LPCRITICAL_SECTION cs_;
+ bool set_;
+};
+
+/*
+ * win_credential_t: SChannel context that must accompany TLS connections.
+ *
+ * SChannel attempts session resumption for shared CredHandle objects.
+ * To mimic openssl behavior, server CredHandle handles must be shared
+ * by derived connections, client CredHandle handles must be unique
+ * when app's session_id is null and tracked for reuse otherwise
+ * (TODO).
+ *
+ * Ref counted by parent ssl_domain_t and each derived connection.
+ */
+struct win_credential_t {
+ CRITICAL_SECTION cslock;
+ pn_ssl_mode_t mode;
+ PCCERT_CONTEXT cert_context; // Particulars of the certificate (if any)
+ CredHandle cred_handle; // Bound session parameters, certificate, CAs, verification_mode
+ HCERTSTORE trust_store; // Store of root CAs for validating
+ HCERTSTORE server_CA_certs; // CAs advertised by server (may be a duplicate of the trust_store)
+ char *trust_store_name;
+};
+
+#define win_credential_compare NULL
+#define win_credential_inspect NULL
+#define win_credential_hashcode NULL
+
+static void win_credential_initialize(void *object)
+{
+ win_credential_t *c = (win_credential_t *) object;
+ InitializeCriticalSectionAndSpinCount(&c->cslock, 4000);
+ SecInvalidateHandle(&c->cred_handle);
+ c->cert_context = 0;
+ c->trust_store = 0;
+ c->server_CA_certs = 0;
+ c->trust_store_name = 0;
+}
+
+static void win_credential_finalize(void *object)
+{
+ win_credential_t *c = (win_credential_t *) object;
+ if (SecIsValidHandle(&c->cred_handle))
+ FreeCredentialsHandle(&c->cred_handle);
+ if (c->cert_context)
+ CertFreeCertificateContext(c->cert_context);
+ if (c->trust_store)
+ CertCloseStore(c->trust_store, 0);
+ if (c->server_CA_certs)
+ CertCloseStore(c->server_CA_certs, 0);
+ DeleteCriticalSection(&c->cslock);
+ free(c->trust_store_name);
+}
+
+static win_credential_t *win_credential(pn_ssl_mode_t m)
+{
+ static const pn_cid_t CID_win_credential = CID_pn_void;
+ static const pn_class_t clazz = PN_CLASS(win_credential);
+ win_credential_t *c = (win_credential_t *) pn_class_new(&clazz, sizeof(win_credential_t));
+ c->mode = m;
+ csguard g(&c->cslock);
+ pn_incref(c); // See next comment regarding refcounting and locks
+ return c;
+}
+
+// Hack strategy for Proton object refcounting. Just hold the lock for incref.
+// Use the next two functions for decref, one with, the other without the lock.
+// The refcount is artificially bumped by one in win_credential() so that we
+// can use refcount == 1 to actually delete (by calling decref one last time).
+static bool win_credential_decref(win_credential_t *c)
+{
+ // Call with lock held. Caller MUST call win_credential_delete if this returns true.
+ return pn_decref(c) == 1;
+}
+
+static void win_credential_delete(win_credential_t *c)
+{
+ // Call without lock held.
+ assert(pn_refcount(c) == 1);
+ pn_decref(c);
+}
+
+static int win_credential_load_cert(win_credential_t *cred, const char *store_name, const char *cert_name, const char *passwd)
+{
+ if (!store_name)
+ return -2;
+
+ int ec = 0;
+ HCERTSTORE cert_store = open_cert_db(store_name, passwd, &ec);
+ if (!cert_store)
+ return ec;
+
+ // find friendly name that matches cert_name, or sole certificate
+ PCCERT_CONTEXT tmpctx = NULL;
+ PCCERT_CONTEXT found_ctx = NULL;
+ int cert_count = 0;
+ int name_len = cert_name ? strlen(cert_name) : 0;
+ char *fn = name_len ? (char *) malloc(name_len + 1) : 0;
+ while (tmpctx = CertEnumCertificatesInStore(cert_store, tmpctx)) {
+ cert_count++;
+ if (cert_name && *cert_name) {
+ DWORD len = CertGetNameString(tmpctx, CERT_NAME_FRIENDLY_DISPLAY_TYPE,
+ 0, NULL, NULL, 0);
+ if (len != name_len + 1)
+ continue;
+ CertGetNameString(tmpctx, CERT_NAME_FRIENDLY_DISPLAY_TYPE,
+ 0, NULL, fn, len);
+ if (!strcmp(cert_name, fn)) {
+ found_ctx = tmpctx;
+ tmpctx= NULL;
+ break;
+ }
+ } else {
+ // Test for single certificate
+ if (cert_count == 1) {
+ found_ctx = CertDuplicateCertificateContext(tmpctx);
+ } else {
+ ssl_log_error("Multiple certificates to choose from certificate store %s\n", store_name);
+ found_ctx = NULL;
+ break;
+ }
+ }
+ }
+
+ if (tmpctx) {
+ CertFreeCertificateContext(tmpctx);
+ tmpctx = false;
+ }
+ if (!found_ctx && cert_name && cert_count == 1)
+ ssl_log_error("Could not find certificate %s in store %s\n", cert_name, store_name);
+ cred->cert_context = found_ctx;
+
+ free(fn);
+ CertCloseStore(cert_store, 0);
+ return found_ctx ? 0 : -8;
+}
+
+
+// call with win_credential lock held
+static CredHandle win_credential_cred_handle(win_credential_t *cred, pn_ssl_verify_mode_t verify_mode,
+ const char *session_id, SECURITY_STATUS *status)
+{
+ if (cred->mode == PN_SSL_MODE_SERVER && SecIsValidHandle(&cred->cred_handle)) {
+ *status = SEC_E_OK;
+ return cred->cred_handle; // Server always reuses cached value
+ }
+ // TODO: if (is_client && session_id != NULL) create or use cached value based on
+ // session_id+server_host_name (per domain? reclaimed after X hours?)
+
+ CredHandle tmp_handle;
+ SecInvalidateHandle(&tmp_handle);
+ TimeStamp expiry; // Not used
+ SCHANNEL_CRED descriptor;
+ memset(&descriptor, 0, sizeof(descriptor));
+
+ descriptor.dwVersion = SCHANNEL_CRED_VERSION;
+ descriptor.dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION;
+ if (cred->cert_context != NULL) {
+ // assign the certificate into the credentials
+ descriptor.paCred = &cred->cert_context;
+ descriptor.cCreds = 1;
+ }
+
+ if (cred->mode == PN_SSL_MODE_SERVER) {
+ descriptor.dwFlags |= SCH_CRED_NO_SYSTEM_MAPPER;
+ if (cred->server_CA_certs) {
+ descriptor.hRootStore = cred->server_CA_certs;
+ }
+ }
+
+ ULONG direction = (cred->mode == PN_SSL_MODE_SERVER) ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND;
+ *status = AcquireCredentialsHandle(NULL, UNISP_NAME, direction, NULL,
+ &descriptor, NULL, NULL, &tmp_handle, &expiry);
+ if (cred->mode == PN_SSL_MODE_SERVER && *status == SEC_E_OK)
+ cred->cred_handle = tmp_handle;
+
+ return tmp_handle;
+}
+
+static bool win_credential_has_certificate(win_credential_t *cred)
+{
+ if (!cred) return false;
+ return (cred->cert_context != NULL);
+}
+
+#define SSL_DATA_SIZE 16384
+#define SSL_BUF_SIZE (SSL_DATA_SIZE + 5 + 2048 + 32)
+
+typedef enum { UNKNOWN_CONNECTION, SSL_CONNECTION, CLEAR_CONNECTION } connection_mode_t;
+typedef struct pn_ssl_session_t pn_ssl_session_t;
+
+struct pn_ssl_domain_t {
+ CRITICAL_SECTION cslock;
+ int ref_count;
+ pn_ssl_mode_t mode;
+ bool has_ca_db; // true when CA database configured
+ pn_ssl_verify_mode_t verify_mode;
+ bool allow_unsecured;
+ win_credential_t *cred;
+};
+
+typedef enum { CREATED, CLIENT_HELLO, NEGOTIATING,
+ RUNNING, SHUTTING_DOWN, SSL_CLOSED } ssl_state_t;
+
+struct pni_ssl_t {
+ pn_ssl_domain_t *domain;
+ const char *session_id;
+ const char *peer_hostname;
+ ssl_state_t state;
+
+ bool protocol_detected;
+ bool queued_shutdown;
+ bool ssl_closed; // shutdown complete, or SSL error
+ ssize_t app_input_closed; // error code returned by upper layer process input
+ ssize_t app_output_closed; // error code returned by upper layer process output
+
+ // OpenSSL hides the protocol envelope bytes, SChannel has them in-line.
+ char *sc_outbuf; // SChannel output buffer
+ size_t sc_out_size;
+ size_t sc_out_count;
+ char *network_outp; // network ready bytes within sc_outbuf
+ size_t network_out_pending;
+
+ char *sc_inbuf; // SChannel input buffer
+ size_t sc_in_size;
+ size_t sc_in_count;
+ bool sc_in_incomplete;
+
+ char *inbuf_extra; // Still encrypted data from following Record(s)
+ size_t extra_count;
+
+ char *in_data; // Just the plaintext data part of sc_inbuf, decrypted in place
+ size_t in_data_size;
+ size_t in_data_count;
+ bool decrypting;
+ size_t max_data_size; // computed in the handshake
+
+ pn_bytes_t app_inbytes; // Virtual decrypted datastream, presented to app layer
+
+ pn_buffer_t *inbuf2; // Second input buf if longer contiguous bytes needed
+ bool double_buffered;
+
+ bool sc_input_shutdown;
+
+ CredHandle cred_handle;
+ CtxtHandle ctxt_handle;
+ SecPkgContext_StreamSizes sc_sizes;
+ pn_ssl_verify_mode_t verify_mode;
+ win_credential_t *cred;
+ char *subject;
+};
+
+static inline pn_transport_t *get_transport_internal(pn_ssl_t *ssl)
+{
+ // The external pn_sasl_t is really a pointer to the internal pni_transport_t
+ return ((pn_transport_t *)ssl);
+}
+
+static inline pni_ssl_t *get_ssl_internal(pn_ssl_t *ssl)
+{
+ // The external pn_sasl_t is really a pointer to the internal pni_transport_t
+ return ssl ? ((pn_transport_t *)ssl)->ssl : NULL;
+}
+
+struct pn_ssl_session_t {
+ const char *id;
+// TODO
+ pn_ssl_session_t *ssn_cache_next;
+ pn_ssl_session_t *ssn_cache_prev;
+};
+
+
+static ssize_t process_input_ssl( pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len);
+static ssize_t process_output_ssl( pn_transport_t *transport, unsigned int layer, char *input_data, size_t len);
+static ssize_t process_input_done(pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len);
+static ssize_t process_output_done(pn_transport_t *transport, unsigned int layer, char *input_data, size_t len);
+static pn_ssl_session_t *ssn_cache_find( pn_ssl_domain_t *, const char * );
+static void ssl_session_free( pn_ssl_session_t *);
+static size_t buffered_output( pn_transport_t *transport );
+static void start_ssl_shutdown(pn_transport_t *transport);
+static void rewind_sc_inbuf(pni_ssl_t *ssl);
+static bool grow_inbuf2(pn_transport_t *ssl, size_t minimum_size);
+static HRESULT verify_peer(pni_ssl_t *ssl, HCERTSTORE root_store, const char *server_name, bool tracing);
+
+// @todo: used to avoid littering the code with calls to printf...
+static void ssl_log_error(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fflush(stderr);
+}
+
+// @todo: used to avoid littering the code with calls to printf...
+static void ssl_log(pn_transport_t *transport, const char *fmt, ...)
+{
+ if (PN_TRACE_DRV & transport->trace) {
+ va_list ap;
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fflush(stderr);
+ }
+}
+
+static void ssl_log_error_status(HRESULT status, const char *fmt, ...)
+{
+ char buf[512];
+ va_list ap;
+
+ if (fmt) {
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ }
+
+ if (FormatMessage(FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, status, 0, buf, sizeof(buf), 0))
+ ssl_log_error(" : %s\n", buf);
+ else
+ fprintf(stderr, "pn internal Windows error: %x for %x\n", GetLastError(), status);
+
+ fflush(stderr);
+}
+
+static void ssl_log_clear_data(pn_transport_t *transport, const char *data, size_t len)
+{
+ if (PN_TRACE_RAW & transport->trace) {
+ fprintf(stderr, "SSL decrypted data: \"");
+ pn_fprint_data( stderr, data, len );
+ fprintf(stderr, "\"\n");
+ }
+}
+
+static size_t _pni_min(size_t a, size_t b)
+{
+ return (a < b) ? a : b;
+}
+
+// unrecoverable SSL failure occured, notify transport and generate error code.
+static int ssl_failed(pn_transport_t *transport, const char *reason)
+{
+ char buf[512] = "Unknown error.";
+ if (!reason) {
+ HRESULT status = GetLastError();
+
+ FormatMessage(FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_SYSTEM,
+ 0, status, 0, buf, sizeof(buf), 0);
+ reason = buf;
+ }
+ pni_ssl_t *ssl = transport->ssl;
+ ssl->ssl_closed = true;
+ ssl->app_input_closed = ssl->app_output_closed = PN_EOS;
+ ssl->state = SSL_CLOSED;
+ pn_do_error(transport, "amqp:connection:framing-error", "SSL Failure: %s", reason);
+ return PN_EOS;
+}
+
+static pn_ssl_session_t *ssn_cache_find( pn_ssl_domain_t *domain, const char *id )
+{
+// TODO:
+ return NULL;
+}
+
+static void ssl_session_free( pn_ssl_session_t *ssn)
+{
+ if (ssn) {
+ if (ssn->id) free( (void *)ssn->id );
+ free( ssn );
+ }
+}
+
+
+/** Public API - visible to application code */
+
+bool pn_ssl_present(void)
+{
+ return true;
+}
+
+pn_ssl_domain_t *pn_ssl_domain( pn_ssl_mode_t mode )
+{
+ pn_ssl_domain_t *domain = (pn_ssl_domain_t *) calloc(1, sizeof(pn_ssl_domain_t));
+ if (!domain) return NULL;
+
+ InitializeCriticalSectionAndSpinCount(&domain->cslock, 4000);
+ csguard(&domain->cslock);
+ domain->ref_count = 1;
+ domain->mode = mode;
+ switch(mode) {
+ case PN_SSL_MODE_CLIENT:
+ case PN_SSL_MODE_SERVER:
+ break;
+
+ default:
+ ssl_log_error("Invalid mode for pn_ssl_mode_t: %d\n", mode);
+ free(domain);
+ return NULL;
+ }
+ domain->cred = win_credential(mode);
+ return domain;
+}
+
+// call with no locks
+void pn_ssl_domain_free( pn_ssl_domain_t *domain )
+{
+ if (!domain) return;
+ {
+ csguard g(&domain->cslock);
+ if (--domain->ref_count)
+ return;
+ }
+ {
+ csguard g2(&domain->cred->cslock);
+ if (win_credential_decref(domain->cred)) {
+ g2.release();
+ win_credential_delete(domain->cred);
+ }
+ }
+ DeleteCriticalSection(&domain->cslock);
+ free(domain);
+}
+
+
+int pn_ssl_domain_set_credentials( pn_ssl_domain_t *domain,
+ const char *certificate_file,
+ const char *private_key_file,
+ const char *password)
+{
+ if (!domain) return -1;
+ csguard g(&domain->cslock);
+ csguard g2(&domain->cred->cslock);
+
+ if (win_credential_has_certificate(domain->cred)) {
+ // Need a new win_credential_t to hold new certificate
+ if (win_credential_decref(domain->cred)) {
+ g2.release();
+ win_credential_delete(domain->cred);
+ }
+ domain->cred = win_credential(domain->mode);
+ if (!domain->cred)
+ return -1;
+ }
+ return win_credential_load_cert(domain->cred, certificate_file, private_key_file, password);
+}
+
+
+int pn_ssl_domain_set_trusted_ca_db(pn_ssl_domain_t *domain,
+ const char *certificate_db)
+{
+ if (!domain || !certificate_db) return -1;
+ csguard g(&domain->cslock);
+
+ int ec = 0;
+ HCERTSTORE store = open_cert_db(certificate_db, NULL, &ec);
+ if (!store)
+ return ec;
+
+ if (domain->has_ca_db) {
+ csguard g2(&domain->cred->cslock);
+ win_credential_t *new_cred = win_credential(domain->mode);
+ if (!new_cred) {
+ CertCloseStore(store, 0);
+ return -1;
+ }
+ new_cred->cert_context = CertDuplicateCertificateContext(domain->cred->cert_context);
+ if (win_credential_decref(domain->cred)) {
+ g2.release();
+ win_credential_delete(domain->cred);
+ }
+ domain->cred = new_cred;
+ }
+
+ csguard g3(&domain->cred->cslock);
+ domain->cred->trust_store = store;
+ domain->cred->trust_store_name = pn_strdup(certificate_db);
+ domain->has_ca_db = true;
+ return 0;
+}
+
+
+int pn_ssl_domain_set_peer_authentication(pn_ssl_domain_t *domain,
+ const pn_ssl_verify_mode_t mode,
+ const char *trusted_CAs)
+{
+ if (!domain) return -1;
+ csguard g(&domain->cslock);
+ csguard g2(&domain->cred->cslock);
+
+ if (!domain->has_ca_db && (mode == PN_SSL_VERIFY_PEER || mode == PN_SSL_VERIFY_PEER_NAME)) {
+ ssl_log_error("Error: cannot verify peer without a trusted CA configured.\n"
+ " Use pn_ssl_domain_set_trusted_ca_db()\n");
+ return -1;
+ }
+
+ HCERTSTORE store = 0;
+ bool changed = domain->verify_mode && mode != domain->verify_mode;
+
+ switch (mode) {
+ case PN_SSL_VERIFY_PEER:
+ case PN_SSL_VERIFY_PEER_NAME:
+ if (domain->mode == PN_SSL_MODE_SERVER) {
+ if (!trusted_CAs) {
+ ssl_log_error("Error: a list of trusted CAs must be provided.");
+ return -1;
+ }
+ if (!win_credential_has_certificate(domain->cred)) {
+ ssl_log_error("Error: Server cannot verify peer without configuring a certificate.\n"
+ " Use pn_ssl_domain_set_credentials()");
+ return -1;
+ }
+ int ec = 0;
+ if (!strcmp(trusted_CAs, domain->cred->trust_store_name)) {
+ changed = true;
+ store = open_cert_db(trusted_CAs, NULL, &ec);
+ if (!store)
+ return ec;
+ }
+ }
+ break;
+
+ case PN_SSL_ANONYMOUS_PEER: // hippie free love mode... :)
+ break;
+
+ default:
+ ssl_log_error("Invalid peer authentication mode given.\n");
+ return -1;
+ }
+
+ if (changed) {
+ win_credential_t *new_cred = win_credential(domain->mode);
+ if (!new_cred) {
+ if (store)
+ CertCloseStore(store, 0);
+ return -1;
+ }
+ new_cred->cert_context = CertDuplicateCertificateContext(domain->cred->cert_context);
+ new_cred->trust_store = CertDuplicateStore(domain->cred->trust_store);
+ new_cred->trust_store_name = pn_strdup(domain->cred->trust_store_name);
+ if (win_credential_decref(domain->cred)) {
+ g2.release();
+ win_credential_delete(domain->cred);
+ }
+ domain->cred = new_cred;
+ domain->cred->server_CA_certs = store;
+ }
+
+ domain->verify_mode = mode;
+
+ return 0;
+}
+
+int pn_ssl_domain_set_ciphers(pn_ssl_domain_t *domain, const char *ciphers)
+{
+ return PN_ERR;
+}
+
+int pn_ssl_domain_set_protocols(pn_ssl_domain_t *domain, const char *protocols)
+{
+ return PN_ERR;
+}
+
+const pn_io_layer_t ssl_layer = {
+ process_input_ssl,
+ process_output_ssl,
+ NULL,
+ NULL,
+ buffered_output
+};
+
+const pn_io_layer_t ssl_input_closed_layer = {
+ process_input_done,
+ process_output_ssl,
+ NULL,
+ NULL,
+ buffered_output
+};
+
+const pn_io_layer_t ssl_output_closed_layer = {
+ process_input_ssl,
+ process_output_done,
+ NULL,
+ NULL,
+ buffered_output
+};
+
+const pn_io_layer_t ssl_closed_layer = {
+ process_input_done,
+ process_output_done,
+ NULL,
+ NULL,
+ buffered_output
+};
+
+int pn_ssl_init(pn_ssl_t *ssl0, pn_ssl_domain_t *domain, const char *session_id)
+{
+ pn_transport_t *transport = get_transport_internal(ssl0);
+ pni_ssl_t *ssl = transport->ssl;
+ if (!ssl || !domain || ssl->domain) return -1;
+ if (ssl->state != CREATED) return -1;
+
+ csguard g(&domain->cslock);
+ csguard g2(&domain->cred->cslock);
+ ssl->domain = domain;
+ domain->ref_count++;
+ if (session_id && domain->mode == PN_SSL_MODE_CLIENT)
+ ssl->session_id = pn_strdup(session_id);
+
+ // If SSL doesn't specifically allow skipping encryption, require SSL
+ // TODO: This is a probably a stop-gap until allow_unsecured is removed
+ if (!domain->allow_unsecured) transport->encryption_required = true;
+
+ ssl->cred = domain->cred;
+ pn_incref(domain->cred);
+
+ SECURITY_STATUS status = SEC_E_OK;
+ ssl->cred_handle = win_credential_cred_handle(ssl->cred, ssl->verify_mode,
+ ssl->session_id, &status);
+ if (status != SEC_E_OK) {
+ ssl_log_error_status(status, "Credentials handle failure");
+ return -1;
+ }
+
+ ssl->state = (domain->mode == PN_SSL_MODE_CLIENT) ? CLIENT_HELLO : NEGOTIATING;
+ ssl->verify_mode = domain->verify_mode;
+ return 0;
+}
+
+
+int pn_ssl_domain_allow_unsecured_client(pn_ssl_domain_t *domain)
+{
+ if (!domain) return -1;
+ if (domain->mode != PN_SSL_MODE_SERVER) {
+ ssl_log_error("Cannot permit unsecured clients - not a server.\n");
+ return -1;
+ }
+ domain->allow_unsecured = true;
+ return 0;
+}
+
+
+// TODO: This is just an untested guess
+int pn_ssl_get_ssf(pn_ssl_t *ssl0)
+{
+ SecPkgContext_ConnectionInfo info;
+
+ pni_ssl_t *ssl = get_ssl_internal(ssl0);
+ if (ssl &&
+ ssl->state == RUNNING &&
+ SecIsValidHandle(&ssl->ctxt_handle) &&
+ QueryContextAttributes(&ssl->ctxt_handle, SECPKG_ATTR_CONNECTION_INFO, &info) == SEC_E_OK) {
+ return info.dwCipherStrength;
+ }
+ return 0;
+}
+
+bool pn_ssl_get_cipher_name(pn_ssl_t *ssl0, char *buffer, size_t size )
+{
+ pni_ssl_t *ssl = get_ssl_internal(ssl0);
+ *buffer = '\0';
+ if (ssl->state != RUNNING || !SecIsValidHandle(&ssl->ctxt_handle))
+ return false;
+ SecPkgContext_ConnectionInfo info;
+ if (QueryContextAttributes(&ssl->ctxt_handle, SECPKG_ATTR_CONNECTION_INFO, &info) == SEC_E_OK) {
+ // TODO: come up with string for all permutations?
+ pni_snprintf( buffer, size, "%x_%x:%x_%x:%x_%x",
+ info.aiExch, info.dwExchStrength,
+ info.aiCipher, info.dwCipherStrength,
+ info.aiHash, info.dwHashStrength);
+ return true;
+ }
+ return false;
+}
+
+bool pn_ssl_get_protocol_name(pn_ssl_t *ssl0, char *buffer, size_t size )
+{
+ pni_ssl_t *ssl = get_ssl_internal(ssl0);
+ *buffer = '\0';
+ if (ssl->state != RUNNING || !SecIsValidHandle(&ssl->ctxt_handle))
+ return false;
+ SecPkgContext_ConnectionInfo info;
+ if (QueryContextAttributes(&ssl->ctxt_handle, SECPKG_ATTR_CONNECTION_INFO, &info) == SEC_E_OK) {
+ if (info.dwProtocol & (SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_SERVER))
+ pni_snprintf(buffer, size, "%s", "TLSv1");
+ // TLSV1.1 and TLSV1.2 are supported as of XP-SP3, but not defined until VS2010
+ else if ((info.dwProtocol & 0x300))
+ pni_snprintf(buffer, size, "%s", "TLSv1.1");
+ else if ((info.dwProtocol & 0xC00))
+ pni_snprintf(buffer, size, "%s", "TLSv1.2");
+ else {
+ ssl_log_error("unexpected protocol %x\n", info.dwProtocol);
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+
+void pn_ssl_free( pn_transport_t *transport)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ if (!ssl) return;
+ ssl_log( transport, "SSL socket freed.\n" );
+ // clean up Windows per TLS session data before releasing the domain count
+ csguard g(&ssl->domain->cslock);
+ csguard g2(&ssl->cred->cslock);
+ if (SecIsValidHandle(&ssl->ctxt_handle))
+ DeleteSecurityContext(&ssl->ctxt_handle);
+ if (ssl->cred) {
+ if (ssl->domain->mode == PN_SSL_MODE_CLIENT && ssl->session_id == NULL) {
+ // Responsible for unshared handle
+ if (SecIsValidHandle(&ssl->cred_handle))
+ FreeCredentialsHandle(&ssl->cred_handle);
+ }
+ if (win_credential_decref(ssl->cred)) {
+ g2.release();
+ win_credential_delete(ssl->cred);
+ }
+ }
+
+ g2.release();
+ g.release();
+ pn_ssl_domain_free(ssl->domain);
+
+ if (ssl->session_id) free((void *)ssl->session_id);
+ if (ssl->peer_hostname) free((void *)ssl->peer_hostname);
+ if (ssl->sc_inbuf) free((void *)ssl->sc_inbuf);
+ if (ssl->sc_outbuf) free((void *)ssl->sc_outbuf);
+ if (ssl->inbuf2) pn_buffer_free(ssl->inbuf2);
+ if (ssl->subject) free(ssl->subject);
+
+ free(ssl);
+}
+
+pn_ssl_t *pn_ssl(pn_transport_t *transport)
+{
+ if (!transport) return NULL;
+ if (transport->ssl) return (pn_ssl_t *)transport;
+
+ pni_ssl_t *ssl = (pni_ssl_t *) calloc(1, sizeof(pni_ssl_t));
+ if (!ssl) return NULL;
+ ssl->sc_out_size = ssl->sc_in_size = SSL_BUF_SIZE;
+
+ ssl->sc_outbuf = (char *)malloc(ssl->sc_out_size);
+ if (!ssl->sc_outbuf) {
+ free(ssl);
+ return NULL;
+ }
+ ssl->sc_inbuf = (char *)malloc(ssl->sc_in_size);
+ if (!ssl->sc_inbuf) {
+ free(ssl->sc_outbuf);
+ free(ssl);
+ return NULL;
+ }
+
+ ssl->inbuf2 = pn_buffer(0);
+ if (!ssl->inbuf2) {
+ free(ssl->sc_inbuf);
+ free(ssl->sc_outbuf);
+ free(ssl);
+ return NULL;
+ }
+
+ transport->ssl = ssl;
+
+ // Set up hostname from any bound connection
+ if (transport->connection) {
+ if (pn_string_size(transport->connection->hostname)) {
+ pn_ssl_set_peer_hostname((pn_ssl_t *) transport, pn_string_get(transport->connection->hostname));
+ }
+ }
+
+ SecInvalidateHandle(&ssl->cred_handle);
+ SecInvalidateHandle(&ssl->ctxt_handle);
+ ssl->state = CREATED;
+ ssl->decrypting = true;
+
+ return (pn_ssl_t *)transport;
+}
+
+
+pn_ssl_resume_status_t pn_ssl_resume_status( pn_ssl_t *ssl )
+{
+ // TODO
+ return PN_SSL_RESUME_UNKNOWN;
+}
+
+
+int pn_ssl_set_peer_hostname( pn_ssl_t *ssl0, const char *hostname )
+{
+ pni_ssl_t *ssl = get_ssl_internal(ssl0);
+ if (!ssl) return -1;
+
+ if (ssl->peer_hostname) free((void *)ssl->peer_hostname);
+ ssl->peer_hostname = NULL;
+ if (hostname) {
+ ssl->peer_hostname = pn_strdup(hostname);
+ if (!ssl->peer_hostname) return -2;
+ }
+ return 0;
+}
+
+int pn_ssl_get_peer_hostname( pn_ssl_t *ssl0, char *hostname, size_t *bufsize )
+{
+ pni_ssl_t *ssl = get_ssl_internal(ssl0);
+ if (!ssl) return -1;
+ if (!ssl->peer_hostname) {
+ *bufsize = 0;
+ if (hostname) *hostname = '\0';
+ return 0;
+ }
+ unsigned len = strlen(ssl->peer_hostname);
+ if (hostname) {
+ if (len >= *bufsize) return -1;
+ strcpy( hostname, ssl->peer_hostname );
+ }
+ *bufsize = len;
+ return 0;
+}
+
+const char* pn_ssl_get_remote_subject(pn_ssl_t *ssl0)
+{
+ // RFC 2253 compliant, but differs from openssl's subject string with space separators and
+ // ordering of multicomponent RDNs. Made to work as similarly as possible with choice of flags.
+ pni_ssl_t *ssl = get_ssl_internal(ssl0);
+ if (!ssl || !SecIsValidHandle(&ssl->ctxt_handle))
+ return NULL;
+ if (!ssl->subject) {
+ SECURITY_STATUS status;
+ PCCERT_CONTEXT peer_cc = 0;
+ status = QueryContextAttributes(&ssl->ctxt_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &peer_cc);
+ if (status != SEC_E_OK) {
+ ssl_log_error_status(status, "can't obtain remote certificate subject");
+ return NULL;
+ }
+ DWORD flags = CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG;
+ DWORD strlen = CertNameToStr(peer_cc->dwCertEncodingType, &peer_cc->pCertInfo->Subject,
+ flags, NULL, 0);
+ if (strlen > 0) {
+ ssl->subject = (char*) malloc(strlen);
+ if (ssl->subject) {
+ DWORD len = CertNameToStr(peer_cc->dwCertEncodingType, &peer_cc->pCertInfo->Subject,
+ flags, ssl->subject, strlen);
+ if (len != strlen) {
+ free(ssl->subject);
+ ssl->subject = NULL;
+ ssl_log_error("pn_ssl_get_remote_subject failure in CertNameToStr");
+ }
+ }
+ }
+ CertFreeCertificateContext(peer_cc);
+ }
+ return ssl->subject;
+}
+
+
+/** SChannel specific: */
+
+const char *tls_version_check(pni_ssl_t *ssl)
+{
+ SecPkgContext_ConnectionInfo info;
+ QueryContextAttributes(&ssl->ctxt_handle, SECPKG_ATTR_CONNECTION_INFO, &info);
+ // Ascending bit patterns denote newer SSL/TLS protocol versions.
+ // SP_PROT_TLS1_0_SERVER is not defined until VS2010.
+ return (info.dwProtocol < SP_PROT_TLS1_SERVER) ?
+ "peer does not support TLS 1.0 security" : NULL;
+}
+
+static void ssl_encrypt(pn_transport_t *transport, char *app_data, size_t count)
+{
+ pni_ssl_t *ssl = transport->ssl;
+
+ // Get SChannel to encrypt exactly one Record.
+ SecBuffer buffs[4];
+ buffs[0].cbBuffer = ssl->sc_sizes.cbHeader;
+ buffs[0].BufferType = SECBUFFER_STREAM_HEADER;
+ buffs[0].pvBuffer = ssl->sc_outbuf;
+ buffs[1].cbBuffer = count;
+ buffs[1].BufferType = SECBUFFER_DATA;
+ buffs[1].pvBuffer = app_data;
+ buffs[2].cbBuffer = ssl->sc_sizes.cbTrailer;
+ buffs[2].BufferType = SECBUFFER_STREAM_TRAILER;
+ buffs[2].pvBuffer = &app_data[count];
+ buffs[3].cbBuffer = 0;
+ buffs[3].BufferType = SECBUFFER_EMPTY;
+ buffs[3].pvBuffer = 0;
+ SecBufferDesc buff_desc;
+ buff_desc.ulVersion = SECBUFFER_VERSION;
+ buff_desc.cBuffers = 4;
+ buff_desc.pBuffers = buffs;
+ SECURITY_STATUS status = EncryptMessage(&ssl->ctxt_handle, 0, &buff_desc, 0);
+ assert(status == SEC_E_OK);
+
+ // EncryptMessage encrypts the data in place. The header and trailer
+ // areas were reserved previously and must now be included in the updated
+ // count of bytes to write to the peer.
+ ssl->sc_out_count = buffs[0].cbBuffer + buffs[1].cbBuffer + buffs[2].cbBuffer;
+ ssl->network_outp = ssl->sc_outbuf;
+ ssl->network_out_pending = ssl->sc_out_count;
+ ssl_log(transport, "ssl_encrypt %d network bytes\n", ssl->network_out_pending);
+}
+
+// Returns true if decryption succeeded (even for empty content)
+static bool ssl_decrypt(pn_transport_t *transport)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ // Get SChannel to decrypt input. May have an incomplete Record,
+ // exactly one, or more than one. Check also for session ending,
+ // session renegotiation.
+
+ SecBuffer recv_buffs[4];
+ recv_buffs[0].cbBuffer = ssl->sc_in_count;
+ recv_buffs[0].BufferType = SECBUFFER_DATA;
+ recv_buffs[0].pvBuffer = ssl->sc_inbuf;
+ recv_buffs[1].BufferType = SECBUFFER_EMPTY;
+ recv_buffs[2].BufferType = SECBUFFER_EMPTY;
+ recv_buffs[3].BufferType = SECBUFFER_EMPTY;
+ SecBufferDesc buff_desc;
+ buff_desc.ulVersion = SECBUFFER_VERSION;
+ buff_desc.cBuffers = 4;
+ buff_desc.pBuffers = recv_buffs;
+ SECURITY_STATUS status = DecryptMessage(&ssl->ctxt_handle, &buff_desc, 0, NULL);
+
+ if (status == SEC_E_INCOMPLETE_MESSAGE) {
+ // Less than a full Record, come back later with more network data
+ ssl->sc_in_incomplete = true;
+ return false;
+ }
+
+ ssl->decrypting = false;
+
+ if (status != SEC_E_OK) {
+ rewind_sc_inbuf(ssl);
+ switch (status) {
+ case SEC_I_CONTEXT_EXPIRED:
+ // TLS shutdown alert record. Ignore all subsequent input.
+ ssl->state = SHUTTING_DOWN;
+ ssl->sc_input_shutdown = true;
+ return false;
+
+ case SEC_I_RENEGOTIATE:
+ ssl_log_error("unexpected TLS renegotiation\n");
+ // TODO. Fall through for now.
+ default:
+ ssl_failed(transport, 0);
+ return false;
+ }
+ }
+
+ ssl->decrypting = false;
+ // have a decrypted Record and possible (still-encrypted) data of
+ // one (or more) later Recordss. Adjust pointers accordingly.
+ for (int i = 0; i < 4; i++) {
+ switch (recv_buffs[i].BufferType) {
+ case SECBUFFER_DATA:
+ ssl->in_data = (char *) recv_buffs[i].pvBuffer;
+ ssl->in_data_size = ssl->in_data_count = recv_buffs[i].cbBuffer;
+ break;
+ case SECBUFFER_EXTRA:
+ ssl->inbuf_extra = (char *)recv_buffs[i].pvBuffer;
+ ssl->extra_count = recv_buffs[i].cbBuffer;
+ break;
+ default:
+ // SECBUFFER_STREAM_HEADER:
+ // SECBUFFER_STREAM_TRAILER:
+ break;
+ }
+ }
+ return true;
+}
+
+static void client_handshake_init(pn_transport_t *transport)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ // Tell SChannel to create the first handshake token (ClientHello)
+ // and place it in sc_outbuf
+ SEC_CHAR *host = (SEC_CHAR *)(ssl->peer_hostname);
+ ULONG ctxt_requested = ISC_REQ_STREAM | ISC_REQ_USE_SUPPLIED_CREDS | ISC_REQ_EXTENDED_ERROR;
+ ULONG ctxt_attrs;
+
+ SecBuffer send_buffs[2];
+ send_buffs[0].cbBuffer = ssl->sc_out_size;
+ send_buffs[0].BufferType = SECBUFFER_TOKEN;
+ send_buffs[0].pvBuffer = ssl->sc_outbuf;
+ send_buffs[1].cbBuffer = 0;
+ send_buffs[1].BufferType = SECBUFFER_EMPTY;
+ send_buffs[1].pvBuffer = 0;
+ SecBufferDesc send_buff_desc;
+ send_buff_desc.ulVersion = SECBUFFER_VERSION;
+ send_buff_desc.cBuffers = 2;
+ send_buff_desc.pBuffers = send_buffs;
+ csguard g(&ssl->cred->cslock);
+ SECURITY_STATUS status = InitializeSecurityContext(&ssl->cred_handle,
+ NULL, host, ctxt_requested, 0, 0, NULL, 0,
+ &ssl->ctxt_handle, &send_buff_desc,
+ &ctxt_attrs, NULL);
+
+ if (status == SEC_I_CONTINUE_NEEDED) {
+ ssl->sc_out_count = send_buffs[0].cbBuffer;
+ ssl->network_out_pending = ssl->sc_out_count;
+ // the token is the whole quantity to send
+ ssl->network_outp = ssl->sc_outbuf;
+ ssl_log(transport, "Sending client hello %d bytes\n", ssl->network_out_pending);
+ } else {
+ ssl_log_error_status(status, "InitializeSecurityContext failed");
+ ssl_failed(transport, 0);
+ }
+}
+
+static void client_handshake( pn_transport_t* transport) {
+ pni_ssl_t *ssl = transport->ssl;
+ // Feed SChannel ongoing responses from the server until the handshake is complete.
+ SEC_CHAR *host = (SEC_CHAR *)(ssl->peer_hostname);
+ ULONG ctxt_requested = ISC_REQ_STREAM | ISC_REQ_USE_SUPPLIED_CREDS;
+ ULONG ctxt_attrs;
+ size_t max = 0;
+
+ // token_buffs describe the buffer that's coming in. It should have
+ // a token from the SSL server, or empty if sending final shutdown alert.
+ bool shutdown = ssl->state == SHUTTING_DOWN;
+ SecBuffer token_buffs[2];
+ token_buffs[0].cbBuffer = shutdown ? 0 : ssl->sc_in_count;
+ token_buffs[0].BufferType = SECBUFFER_TOKEN;
+ token_buffs[0].pvBuffer = shutdown ? 0 : ssl->sc_inbuf;
+ token_buffs[1].cbBuffer = 0;
+ token_buffs[1].BufferType = SECBUFFER_EMPTY;
+ token_buffs[1].pvBuffer = 0;
+ SecBufferDesc token_buff_desc;
+ token_buff_desc.ulVersion = SECBUFFER_VERSION;
+ token_buff_desc.cBuffers = 2;
+ token_buff_desc.pBuffers = token_buffs;
+
+ // send_buffs will hold information to forward to the peer.
+ SecBuffer send_buffs[2];
+ send_buffs[0].cbBuffer = ssl->sc_out_size;
+ send_buffs[0].BufferType = SECBUFFER_TOKEN;
+ send_buffs[0].pvBuffer = ssl->sc_outbuf;
+ send_buffs[1].cbBuffer = 0;
+ send_buffs[1].BufferType = SECBUFFER_EMPTY;
+ send_buffs[1].pvBuffer = 0;
+ SecBufferDesc send_buff_desc;
+ send_buff_desc.ulVersion = SECBUFFER_VERSION;
+ send_buff_desc.cBuffers = 2;
+ send_buff_desc.pBuffers = send_buffs;
+
+ SECURITY_STATUS status;
+ {
+ csguard g(&ssl->cred->cslock);
+ status = InitializeSecurityContext(&ssl->cred_handle,
+ &ssl->ctxt_handle, host, ctxt_requested, 0, 0,
+ &token_buff_desc, 0, NULL, &send_buff_desc,
+ &ctxt_attrs, NULL);
+ }
+
+ switch (status) {
+ case SEC_E_INCOMPLETE_MESSAGE:
+ // Not enough - get more data from the server then try again.
+ // Leave input buffers untouched.
+ ssl_log(transport, "client handshake: incomplete record\n");
+ ssl->sc_in_incomplete = true;
+ return;
+
+ case SEC_I_CONTINUE_NEEDED:
+ // Successful handshake step, requiring data to be sent to peer.
+ ssl->sc_out_count = send_buffs[0].cbBuffer;
+ // the token is the whole quantity to send
+ ssl->network_out_pending = ssl->sc_out_count;
+ ssl->network_outp = ssl->sc_outbuf;
+ ssl_log(transport, "client handshake token %d bytes\n", ssl->network_out_pending);
+ break;
+
+ case SEC_E_OK:
+ // Handshake complete.
+ if (shutdown) {
+ if (send_buffs[0].cbBuffer > 0) {
+ ssl->sc_out_count = send_buffs[0].cbBuffer;
+ // the token is the whole quantity to send
+ ssl->network_out_pending = ssl->sc_out_count;
+ ssl->network_outp = ssl->sc_outbuf;
+ ssl_log(transport, "client shutdown token %d bytes\n", ssl->network_out_pending);
+ } else {
+ ssl->state = SSL_CLOSED;
+ }
+ // we didn't touch sc_inbuf, no need to reset
+ return;
+ }
+ if (send_buffs[0].cbBuffer != 0) {
+ ssl_failed(transport, "unexpected final server token");
+ break;
+ }
+ if (const char *err = tls_version_check(ssl)) {
+ ssl_failed(transport, err);
+ break;
+ }
+ if (ssl->verify_mode == PN_SSL_VERIFY_PEER || ssl->verify_mode == PN_SSL_VERIFY_PEER_NAME) {
+ bool tracing = PN_TRACE_DRV & transport->trace;
+ HRESULT ec = verify_peer(ssl, ssl->cred->trust_store, ssl->peer_hostname, tracing);
+ if (ec) {
+ if (ssl->peer_hostname)
+ ssl_log_error_status(ec, "certificate verification failed for host %s\n", ssl->peer_hostname);
+ else
+ ssl_log_error_status(ec, "certificate verification failed\n");
+ ssl_failed(transport, "TLS certificate verification error");
+ break;
+ }
+ }
+
+ if (token_buffs[1].BufferType == SECBUFFER_EXTRA && token_buffs[1].cbBuffer > 0) {
+ // This seems to work but not documented, plus logic differs from decrypt message
+ // since the pvBuffer value is not set. Grrr.
+ ssl->extra_count = token_buffs[1].cbBuffer;
+ ssl->inbuf_extra = ssl->sc_inbuf + (ssl->sc_in_count - ssl->extra_count);
+ }
+
+ QueryContextAttributes(&ssl->ctxt_handle,
+ SECPKG_ATTR_STREAM_SIZES, &ssl->sc_sizes);
+ max = ssl->sc_sizes.cbMaximumMessage + ssl->sc_sizes.cbHeader + ssl->sc_sizes.cbTrailer;
+ if (max > ssl->sc_out_size) {
+ ssl_log_error("Buffer size mismatch have %d, need %d\n", (int) ssl->sc_out_size, (int) max);
+ ssl->state = SHUTTING_DOWN;
+ ssl->app_input_closed = ssl->app_output_closed = PN_ERR;
+ start_ssl_shutdown(transport);
+ pn_do_error(transport, "amqp:connection:framing-error", "SSL Failure: buffer size");
+ break;
+ }
+
+ ssl->state = RUNNING;
+ ssl->max_data_size = max - ssl->sc_sizes.cbHeader - ssl->sc_sizes.cbTrailer;
+ ssl_log(transport, "client handshake successful %d max record size\n", max);
+ break;
+
+ case SEC_I_CONTEXT_EXPIRED:
+ // ended before we got going
+ default:
+ ssl_log(transport, "client handshake failed %d\n", (int) status);
+ ssl_failed(transport, 0);
+ break;
+ }
+
+ if (token_buffs[1].BufferType == SECBUFFER_EXTRA && token_buffs[1].cbBuffer > 0 &&
+ !ssl->ssl_closed) {
+ // remaining data after the consumed TLS record(s)
+ ssl->extra_count = token_buffs[1].cbBuffer;
+ ssl->inbuf_extra = ssl->sc_inbuf + (ssl->sc_in_count - ssl->extra_count);
+ }
+
+ ssl->decrypting = false;
+ rewind_sc_inbuf(ssl);
+}
+
+
+static void server_handshake(pn_transport_t* transport)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ if (!ssl->protocol_detected) {
+ // SChannel fails less aggressively than openssl on client hello, causing hangs
+ // waiting for more bytes. Help out here.
+ pni_protocol_type_t type = pni_sniff_header(ssl->sc_inbuf, ssl->sc_in_count);
+ if (type == PNI_PROTOCOL_INSUFFICIENT) {
+ ssl_log(transport, "server handshake: incomplete record\n");
+ ssl->sc_in_incomplete = true;
+ return;
+ } else {
+ ssl->protocol_detected = true;
+ if (type != PNI_PROTOCOL_SSL) {
+ ssl_failed(transport, "bad client hello");
+ ssl->decrypting = false;
+ rewind_sc_inbuf(ssl);
+ return;
+ }
+ }
+ }
+
+ // Feed SChannel ongoing handshake records from the client until the handshake is complete.
+ ULONG ctxt_requested = ASC_REQ_STREAM | ASC_REQ_EXTENDED_ERROR;
+ if (ssl->verify_mode == PN_SSL_VERIFY_PEER || ssl->verify_mode == PN_SSL_VERIFY_PEER_NAME)
+ ctxt_requested |= ASC_REQ_MUTUAL_AUTH;
+ ULONG ctxt_attrs;
+ size_t max = 0;
+
+ // token_buffs describe the buffer that's coming in. It should have
+ // a token from the SSL client except if shutting down or renegotiating.
+ bool shutdown = ssl->state == SHUTTING_DOWN;
+ SecBuffer token_buffs[2];
+ token_buffs[0].cbBuffer = shutdown ? 0 : ssl->sc_in_count;
+ token_buffs[0].BufferType = SECBUFFER_TOKEN;
+ token_buffs[0].pvBuffer = shutdown ? 0 : ssl->sc_inbuf;
+ token_buffs[1].cbBuffer = 0;
+ token_buffs[1].BufferType = SECBUFFER_EMPTY;
+ token_buffs[1].pvBuffer = 0;
+ SecBufferDesc token_buff_desc;
+ token_buff_desc.ulVersion = SECBUFFER_VERSION;
+ token_buff_desc.cBuffers = 2;
+ token_buff_desc.pBuffers = token_buffs;
+
+ // send_buffs will hold information to forward to the peer.
+ SecBuffer send_buffs[2];
+ send_buffs[0].cbBuffer = ssl->sc_out_size;
+ send_buffs[0].BufferType = SECBUFFER_TOKEN;
+ send_buffs[0].pvBuffer = ssl->sc_outbuf;
+ send_buffs[1].cbBuffer = 0;
+ send_buffs[1].BufferType = SECBUFFER_EMPTY;
+ send_buffs[1].pvBuffer = 0;
+ SecBufferDesc send_buff_desc;
+ send_buff_desc.ulVersion = SECBUFFER_VERSION;
+ send_buff_desc.cBuffers = 2;
+ send_buff_desc.pBuffers = send_buffs;
+ PCtxtHandle ctxt_handle_ptr = (SecIsValidHandle(&ssl->ctxt_handle)) ? &ssl->ctxt_handle : 0;
+
+ SECURITY_STATUS status;
+ {
+ csguard g(&ssl->cred->cslock);
+ status = AcceptSecurityContext(&ssl->cred_handle, ctxt_handle_ptr,
+ &token_buff_desc, ctxt_requested, 0, &ssl->ctxt_handle,
+ &send_buff_desc, &ctxt_attrs, NULL);
+ }
+ bool outbound_token = false;
+ switch(status) {
+ case SEC_E_INCOMPLETE_MESSAGE:
+ // Not enough - get more data from the client then try again.
+ // Leave input buffers untouched.
+ ssl_log(transport, "server handshake: incomplete record\n");
+ ssl->sc_in_incomplete = true;
+ return;
+
+ case SEC_I_CONTINUE_NEEDED:
+ outbound_token = true;
+ break;
+
+ case SEC_E_OK:
+ // Handshake complete.
+ if (shutdown) {
+ if (send_buffs[0].cbBuffer > 0) {
+ ssl->sc_out_count = send_buffs[0].cbBuffer;
+ // the token is the whole quantity to send
+ ssl->network_out_pending = ssl->sc_out_count;
+ ssl->network_outp = ssl->sc_outbuf;
+ ssl_log(transport, "server shutdown token %d bytes\n", ssl->network_out_pending);
+ } else {
+ ssl->state = SSL_CLOSED;
+ }
+ // we didn't touch sc_inbuf, no need to reset
+ return;
+ }
+ if (const char *err = tls_version_check(ssl)) {
+ ssl_failed(transport, err);
+ break;
+ }
+ // Handshake complete.
+
+ if (ssl->verify_mode == PN_SSL_VERIFY_PEER || ssl->verify_mode == PN_SSL_VERIFY_PEER_NAME) {
+ bool tracing = PN_TRACE_DRV & transport->trace;
+ HRESULT ec = verify_peer(ssl, ssl->cred->trust_store, NULL, tracing);
+ if (ec) {
+ ssl_log_error_status(ec, "certificate verification failed\n");
+ ssl_failed(transport, "certificate verification error");
+ break;
+ }
+ }
+
+ QueryContextAttributes(&ssl->ctxt_handle,
+ SECPKG_ATTR_STREAM_SIZES, &ssl->sc_sizes);
+ max = ssl->sc_sizes.cbMaximumMessage + ssl->sc_sizes.cbHeader + ssl->sc_sizes.cbTrailer;
+ if (max > ssl->sc_out_size) {
+ ssl_log_error("Buffer size mismatch have %d, need %d\n", (int) ssl->sc_out_size, (int) max);
+ ssl->state = SHUTTING_DOWN;
+ ssl->app_input_closed = ssl->app_output_closed = PN_ERR;
+ start_ssl_shutdown(transport);
+ pn_do_error(transport, "amqp:connection:framing-error", "SSL Failure: buffer size");
+ break;
+ }
+
+ if (send_buffs[0].cbBuffer != 0)
+ outbound_token = true;
+
+ ssl->state = RUNNING;
+ ssl->max_data_size = max - ssl->sc_sizes.cbHeader - ssl->sc_sizes.cbTrailer;
+ ssl_log(transport, "server handshake successful %d max record size\n", max);
+ break;
+
+ case SEC_I_CONTEXT_EXPIRED:
+ // ended before we got going
+ default:
+ ssl_log(transport, "server handshake failed %d\n", (int) status);
+ ssl_failed(transport, 0);
+ break;
+ }
+
+ if (outbound_token) {
+ // Successful handshake step, requiring data to be sent to peer.
+ assert(ssl->network_out_pending == 0);
+ ssl->sc_out_count = send_buffs[0].cbBuffer;
+ // the token is the whole quantity to send
+ ssl->network_out_pending = ssl->sc_out_count;
+ ssl->network_outp = ssl->sc_outbuf;
+ ssl_log(transport, "server handshake token %d bytes\n", ssl->network_out_pending);
+ }
+
+ if (token_buffs[1].BufferType == SECBUFFER_EXTRA && token_buffs[1].cbBuffer > 0 &&
+ !ssl->ssl_closed) {
+ // remaining data after the consumed TLS record(s)
+ ssl->extra_count = token_buffs[1].cbBuffer;
+ ssl->inbuf_extra = ssl->sc_inbuf + (ssl->sc_in_count - ssl->extra_count);
+ }
+
+ ssl->decrypting = false;
+ rewind_sc_inbuf(ssl);
+}
+
+static void ssl_handshake(pn_transport_t* transport) {
+ if (transport->ssl->domain->mode == PN_SSL_MODE_CLIENT)
+ client_handshake(transport);
+ else {
+ server_handshake(transport);
+ }
+}
+
+static bool grow_inbuf2(pn_transport_t *transport, size_t minimum_size) {
+ pni_ssl_t *ssl = transport->ssl;
+ size_t old_capacity = pn_buffer_capacity(ssl->inbuf2);
+ size_t new_capacity = old_capacity ? old_capacity * 2 : 1024;
+
+ while (new_capacity < minimum_size)
+ new_capacity *= 2;
+
+ uint32_t max_frame = pn_transport_get_max_frame(transport);
+ if (max_frame != 0) {
+ if (old_capacity >= max_frame) {
+ // already big enough
+ ssl_log(transport, "Application expecting %d bytes (> negotiated maximum frame)\n", new_capacity);
+ ssl_failed(transport, "TLS: transport maximum frame size error");
+ return false;
+ }
+ }
+
+ size_t extra_bytes = new_capacity - pn_buffer_size(ssl->inbuf2);
+ int err = pn_buffer_ensure(ssl->inbuf2, extra_bytes);
+ if (err) {
+ ssl_log(transport, "TLS memory allocation failed for %d bytes\n", max_frame);
+ ssl_failed(transport, "TLS memory allocation failed");
+ return false;
+ }
+ return true;
+}
+
+
+// Peer initiated a session end by sending us a shutdown alert (and we should politely
+// reciprocate), or else we are initiating the session end (and will not bother to wait
+// for the peer shutdown alert). Stop processing input immediately, and stop processing
+// output once this is sent.
+
+static void start_ssl_shutdown(pn_transport_t *transport)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ assert(ssl->network_out_pending == 0);
+ if (ssl->queued_shutdown)
+ return;
+ ssl->queued_shutdown = true;
+ ssl_log(transport, "Shutting down SSL connection...\n");
+
+ DWORD shutdown = SCHANNEL_SHUTDOWN;
+ SecBuffer shutBuff;
+ shutBuff.cbBuffer = sizeof(DWORD);
+ shutBuff.BufferType = SECBUFFER_TOKEN;
+ shutBuff.pvBuffer = &shutdown;
+ SecBufferDesc desc;
+ desc.ulVersion = SECBUFFER_VERSION;
+ desc.cBuffers = 1;
+ desc.pBuffers = &shutBuff;
+ ApplyControlToken(&ssl->ctxt_handle, &desc);
+
+ // Next handshake will generate the shutdown alert token
+ ssl_handshake(transport);
+}
+
+static void rewind_sc_inbuf(pni_ssl_t *ssl)
+{
+ // Decrypted bytes have been drained or double buffered. Prepare for the next SSL Record.
+ assert(ssl->in_data_count == 0);
+ if (ssl->decrypting)
+ return;
+ ssl->decrypting = true;
+ if (ssl->inbuf_extra) {
+ // A previous read picked up more than one Record. Move it to the beginning.
+ memmove(ssl->sc_inbuf, ssl->inbuf_extra, ssl->extra_count);
+ ssl->sc_in_count = ssl->extra_count;
+ ssl->inbuf_extra = 0;
+ ssl->extra_count = 0;
+ } else {
+ ssl->sc_in_count = 0;
+ }
+}
+
+static void app_inbytes_add(pn_transport_t *transport)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ if (!ssl->app_inbytes.start) {
+ ssl->app_inbytes.start = ssl->in_data;
+ ssl->app_inbytes.size = ssl->in_data_count;
+ return;
+ }
+
+ if (ssl->double_buffered) {
+ if (pn_buffer_available(ssl->inbuf2) == 0) {
+ if (!grow_inbuf2(transport, 1024))
+ // could not add room
+ return;
+ }
+ size_t count = _pni_min(ssl->in_data_count, pn_buffer_available(ssl->inbuf2));
+ pn_buffer_append(ssl->inbuf2, ssl->in_data, count);
+ ssl->in_data += count;
+ ssl->in_data_count -= count;
+ ssl->app_inbytes = pn_buffer_bytes(ssl->inbuf2);
+ } else {
+ assert(ssl->app_inbytes.size == 0);
+ ssl->app_inbytes.start = ssl->in_data;
+ ssl->app_inbytes.size = ssl->in_data_count;
+ }
+}
+
+
+static void app_inbytes_progress(pn_transport_t *transport, size_t minimum)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ // Make more decrypted data available, if possible. Otherwise, move
+ // unread bytes to front of inbuf2 to make room for next bulk decryption.
+ // SSL may have chopped up data that app layer expects to be
+ // contiguous. Start, continue or stop double buffering here.
+ if (ssl->double_buffered) {
+ if (ssl->app_inbytes.size == 0) {
+ // no straggler bytes, optimistically stop for now
+ ssl->double_buffered = false;
+ pn_buffer_clear(ssl->inbuf2);
+ ssl->app_inbytes.start = ssl->in_data;
+ ssl->app_inbytes.size = ssl->in_data_count;
+ } else {
+ pn_bytes_t ib2 = pn_buffer_bytes(ssl->inbuf2);
+ assert(ssl->app_inbytes.size <= ib2.size);
+ size_t consumed = ib2.size - ssl->app_inbytes.size;
+ if (consumed > 0) {
+ memmove((void *)ib2.start, ib2.start + consumed, ssl->app_inbytes.size);
+ pn_buffer_trim(ssl->inbuf2, 0, consumed);
+ }
+ if (!pn_buffer_available(ssl->inbuf2)) {
+ if (!grow_inbuf2(transport, minimum))
+ // could not add room
+ return;
+ }
+ size_t count = _pni_min(ssl->in_data_count, pn_buffer_available(ssl->inbuf2));
+ pn_buffer_append(ssl->inbuf2, ssl->in_data, count);
+ ssl->in_data += count;
+ ssl->in_data_count -= count;
+ ssl->app_inbytes = pn_buffer_bytes(ssl->inbuf2);
+ }
+ } else {
+ if (ssl->app_inbytes.size) {
+ // start double buffering the left over bytes
+ ssl->double_buffered = true;
+ pn_buffer_clear(ssl->inbuf2);
+ if (!pn_buffer_available(ssl->inbuf2)) {
+ if (!grow_inbuf2(transport, minimum))
+ // could not add room
+ return;
+ }
+ size_t count = _pni_min(ssl->in_data_count, pn_buffer_available(ssl->inbuf2));
+ pn_buffer_append(ssl->inbuf2, ssl->in_data, count);
+ ssl->in_data += count;
+ ssl->in_data_count -= count;
+ ssl->app_inbytes = pn_buffer_bytes(ssl->inbuf2);
+ } else {
+ // already pointing at all available bytes until next decrypt
+ }
+ }
+ if (ssl->in_data_count == 0)
+ rewind_sc_inbuf(ssl);
+}
+
+
+static void app_inbytes_advance(pn_transport_t *transport, size_t consumed)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ if (consumed == 0) {
+ // more contiguous bytes required
+ app_inbytes_progress(transport, ssl->app_inbytes.size + 1);
+ return;
+ }
+ assert(consumed <= ssl->app_inbytes.size);
+ ssl->app_inbytes.start += consumed;
+ ssl->app_inbytes.size -= consumed;
+ if (!ssl->double_buffered) {
+ ssl->in_data += consumed;
+ ssl->in_data_count -= consumed;
+ }
+ if (ssl->app_inbytes.size == 0)
+ app_inbytes_progress(transport, 0);
+}
+
+static void read_closed(pn_transport_t *transport, unsigned int layer, ssize_t error)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ if (ssl->app_input_closed)
+ return;
+ if (ssl->state == RUNNING && !error) {
+ // Signal end of stream
+ ssl->app_input_closed = transport->io_layers[layer+1]->process_input(transport, layer+1, ssl->app_inbytes.start, 0);
+ }
+ if (!ssl->app_input_closed)
+ ssl->app_input_closed = error ? error : PN_ERR;
+
+ if (ssl->app_output_closed) {
+ // both sides of app closed, and no more app output pending:
+ ssl->state = SHUTTING_DOWN;
+ if (ssl->network_out_pending == 0 && !ssl->queued_shutdown) {
+ start_ssl_shutdown(transport);
+ }
+ }
+}
+
+
+// Read up to "available" bytes from the network, decrypt it and pass plaintext to application.
+
+static ssize_t process_input_ssl(pn_transport_t *transport, unsigned int layer, const char *input_data, size_t available)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ ssl_log( transport, "process_input_ssl( data size=%d )\n",available );
+ ssize_t consumed = 0;
+ ssize_t forwarded = 0;
+ bool new_app_input;
+
+ if (available == 0) {
+ // No more inbound network data
+ read_closed(transport, layer, 0);
+ return 0;
+ }
+
+ do {
+ if (ssl->sc_input_shutdown) {
+ // TLS protocol shutdown detected on input, so we are done.
+ read_closed(transport, layer, 0);
+ return PN_EOS;
+ }
+
+ // sc_inbuf should be ready for new or additional network encrypted bytes.
+ // i.e. no straggling decrypted bytes pending.
+ assert(ssl->in_data_count == 0 && ssl->decrypting);
+ new_app_input = false;
+ size_t count;
+
+ if (ssl->state != RUNNING) {
+ count = _pni_min(ssl->sc_in_size - ssl->sc_in_count, available);
+ } else {
+ // look for TLS record boundaries
+ if (ssl->sc_in_count < 5) {
+ ssl->sc_in_incomplete = true;
+ size_t hc = _pni_min(available, 5 - ssl->sc_in_count);
+ memmove(ssl->sc_inbuf + ssl->sc_in_count, input_data, hc);
+ ssl->sc_in_count += hc;
+ input_data += hc;
+ available -= hc;
+ consumed += hc;
+ if (ssl->sc_in_count < 5 || available == 0)
+ break;
+ }
+
+ // Top up sc_inbuf from network input_data hoping for a complete TLS Record
+ // We try to guess the length as an optimization, but let SChannel
+ // ultimately decide if there is spoofing going on.
+ unsigned char low = (unsigned char) ssl->sc_inbuf[4];
+ unsigned char high = (unsigned char) ssl->sc_inbuf[3];
+ size_t rec_len = high * 256 + low + 5;
+ if (rec_len < 5 || rec_len == ssl->sc_in_count || rec_len > ssl->sc_in_size)
+ rec_len = ssl->sc_in_size;
+
+ count = _pni_min(rec_len - ssl->sc_in_count, available);
+ }
+
+ if (count > 0) {
+ memmove(ssl->sc_inbuf + ssl->sc_in_count, input_data, count);
+ ssl->sc_in_count += count;
+ input_data += count;
+ available -= count;
+ consumed += count;
+ ssl->sc_in_incomplete = false;
+ }
+
+ // Try to decrypt another TLS Record.
+
+ if (ssl->sc_in_count > 0 && ssl->state <= SHUTTING_DOWN) {
+ if (ssl->state == NEGOTIATING) {
+ ssl_handshake(transport);
+ } else {
+ if (ssl_decrypt(transport)) {
+ // Ignore TLS Record with 0 length data (does not mean EOS)
+ if (ssl->in_data_size > 0) {
+ new_app_input = true;
+ app_inbytes_add(transport);
+ } else {
+ assert(ssl->decrypting == false);
+ rewind_sc_inbuf(ssl);
+ }
+ }
+ ssl_log(transport, "Next decryption, %d left over\n", available);
+ }
+ }
+
+ if (ssl->state == SHUTTING_DOWN) {
+ if (ssl->network_out_pending == 0 && !ssl->queued_shutdown) {
+ start_ssl_shutdown(transport);
+ }
+ } else if (ssl->state == SSL_CLOSED) {
+ return PN_EOS;
+ }
+
+ // Consume or discard the decrypted bytes
+ if (new_app_input && (ssl->state == RUNNING || ssl->state == SHUTTING_DOWN)) {
+ // present app_inbytes to io_next only if it has new content
+ while (ssl->app_inbytes.size > 0) {
+ if (!ssl->app_input_closed) {
+ ssize_t count = transport->io_layers[layer+1]->process_input(transport, layer+1, ssl->app_inbytes.start, ssl->app_inbytes.size);
+ if (count > 0) {
+ forwarded += count;
+ // advance() can increase app_inbytes.size if double buffered
+ app_inbytes_advance(transport, count);
+ ssl_log(transport, "Application consumed %d bytes from peer\n", (int) count);
+ } else if (count == 0) {
+ size_t old_size = ssl->app_inbytes.size;
+ app_inbytes_advance(transport, 0);
+ if (ssl->app_inbytes.size == old_size) {
+ break; // no additional contiguous decrypted data available, get more network data
+ }
+ } else {
+ // count < 0
+ ssl_log(transport, "Application layer closed its input, error=%d (discarding %d bytes)\n",
+ (int) count, (int)ssl->app_inbytes.size);
+ app_inbytes_advance(transport, ssl->app_inbytes.size); // discard
+ read_closed(transport, layer, count);
+ }
+ } else {
+ ssl_log(transport, "Input closed discard %d bytes\n",
+ (int)ssl->app_inbytes.size);
+ app_inbytes_advance(transport, ssl->app_inbytes.size); // discard
+ }
+ }
+ }
+ } while (available || (ssl->sc_in_count && !ssl->sc_in_incomplete));
+
+ if (ssl->state >= SHUTTING_DOWN) {
+ if (ssl->app_input_closed || ssl->sc_input_shutdown) {
+ // Next layer doesn't want more bytes, or it can't process without more data than it has seen so far
+ // but the ssl stream has ended
+ consumed = ssl->app_input_closed ? ssl->app_input_closed : PN_EOS;
+ if (transport->io_layers[layer]==&ssl_output_closed_layer) {
+ transport->io_layers[layer] = &ssl_closed_layer;
+ } else {
+ transport->io_layers[layer] = &ssl_input_closed_layer;
+ }
+ }
+ }
+ ssl_log(transport, "process_input_ssl() returning %d, forwarded %d\n", (int) consumed, (int) forwarded);
+ return consumed;
+}
+
+static ssize_t process_output_ssl( pn_transport_t *transport, unsigned int layer, char *buffer, size_t max_len)
+{
+ pni_ssl_t *ssl = transport->ssl;
+ if (!ssl) return PN_EOS;
+ ssl_log( transport, "process_output_ssl( max_len=%d )\n",max_len );
+
+ ssize_t written = 0;
+ ssize_t total_app_bytes = 0;
+ bool work_pending;
+
+ if (ssl->state == CLIENT_HELLO) {
+ // output buffers eclusively for internal handshake use until negotiation complete
+ client_handshake_init(transport);
+ if (ssl->state == SSL_CLOSED)
+ return PN_EOS;
+ ssl->state = NEGOTIATING;
+ }
+
+ do {
+ work_pending = false;
+
+ if (ssl->network_out_pending > 0) {
+ size_t wcount = _pni_min(ssl->network_out_pending, max_len);
+ memmove(buffer, ssl->network_outp, wcount);
+ ssl->network_outp += wcount;
+ ssl->network_out_pending -= wcount;
+ buffer += wcount;
+ max_len -= wcount;
+ written += wcount;
+ }
+
+ if (ssl->network_out_pending == 0 && ssl->state == RUNNING && !ssl->app_output_closed) {
+ // refill the buffer with app data and encrypt it
+
+ char *app_data = ssl->sc_outbuf + ssl->sc_sizes.cbHeader;
+ char *app_outp = app_data;
+ size_t remaining = ssl->max_data_size;
+ ssize_t app_bytes;
+ do {
+ app_bytes = transport->io_layers[layer+1]->process_output(transport, layer+1, app_outp, remaining);
+ if (app_bytes > 0) {
+ app_outp += app_bytes;
+ remaining -= app_bytes;
+ ssl_log( transport, "Gathered %d bytes from app to send to peer\n", app_bytes );
+ } else {
+ if (app_bytes < 0) {
+ ssl_log(transport, "Application layer closed its output, error=%d (%d bytes pending send)\n",
+ (int) app_bytes, (int) ssl->network_out_pending);
+ ssl->app_output_closed = app_bytes;
+ if (ssl->app_input_closed)
+ ssl->state = SHUTTING_DOWN;
+ } else if (total_app_bytes == 0 && ssl->app_input_closed) {
+ // We've drained all the App layer can provide
+ ssl_log(transport, "Application layer blocked on input, closing\n");
+ ssl->state = SHUTTING_DOWN;
+ ssl->app_output_closed = PN_ERR;
+ }
+ }
+ } while (app_bytes > 0);
+ if (app_outp > app_data) {
+ work_pending = (max_len > 0);
+ ssl_encrypt(transport, app_data, app_outp - app_data);
+ }
+ }
+
+ if (ssl->network_out_pending == 0) {
+ if (ssl->state == SHUTTING_DOWN) {
+ if (!ssl->queued_shutdown) {
+ start_ssl_shutdown(transport);
+ work_pending = true;
+ } else {
+ ssl->state = SSL_CLOSED;
+ }
+ }
+ else if (ssl->state == NEGOTIATING && ssl->app_input_closed) {
+ ssl->app_output_closed = PN_EOS;
+ ssl->state = SSL_CLOSED;
+ }
+ }
+ } while (work_pending);
+
+ if (written == 0 && ssl->state == SSL_CLOSED) {
+ written = ssl->app_output_closed ? ssl->app_output_closed : PN_EOS;
+ if (transport->io_layers[layer]==&ssl_input_closed_layer) {
+ transport->io_layers[layer] = &ssl_closed_layer;
+ } else {
+ transport->io_layers[layer] = &ssl_output_closed_layer;
+ }
+ }
+ ssl_log(transport, "process_output_ssl() returning %d\n", (int) written);
+ return written;
+}
+
+
+static ssize_t process_input_done(pn_transport_t *transport, unsigned int layer, const char *input_data, size_t len)
+{
+ return PN_EOS;
+}
+
+static ssize_t process_output_done(pn_transport_t *transport, unsigned int layer, char *input_data, size_t len)
+{
+ return PN_EOS;
+}
+
+// return # output bytes sitting in this layer
+static size_t buffered_output(pn_transport_t *transport)
+{
+ size_t count = 0;
+ pni_ssl_t *ssl = transport->ssl;
+ if (ssl) {
+ count += ssl->network_out_pending;
+ if (count == 0 && ssl->state == SHUTTING_DOWN && ssl->queued_shutdown)
+ count++;
+ }
+ return count;
+}
+
+static HCERTSTORE open_cert_db(const char *store_name, const char *passwd, int *error) {
+ *error = 0;
+ DWORD sys_store_type = 0;
+ HCERTSTORE cert_store = 0;
+
+ if (store_name) {
+ if (strncmp(store_name, "ss:", 3) == 0) {
+ store_name += 3;
+ sys_store_type = CERT_SYSTEM_STORE_CURRENT_USER;
+ }
+ else if (strncmp(store_name, "lmss:", 5) == 0) {
+ store_name += 5;
+ sys_store_type = CERT_SYSTEM_STORE_LOCAL_MACHINE;
+ }
+ }
+
+ if (sys_store_type) {
+ // Opening a system store, names are not case sensitive.
+ // Map confusing GUI name to actual registry store name.
+ if (!pn_strcasecmp(store_name, "personal")) store_name= "my";
+ cert_store = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, 0, NULL,
+ CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG |
+ sys_store_type, store_name);
+ if (!cert_store) {
+ ssl_log_error_status(GetLastError(), "Failed to open system certificate store %s", store_name);
+ *error = -3;
+ return NULL;
+ }
+ } else {
+ // PKCS#12 file
+ HANDLE cert_file = CreateFile(store_name, GENERIC_READ, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (INVALID_HANDLE_VALUE == cert_file) {
+ HRESULT status = GetLastError();
+ ssl_log_error_status(status, "Failed to open the file holding the private key: %s", store_name);
+ *error = -4;
+ return NULL;
+ }
+ DWORD nread = 0L;
+ const DWORD file_size = GetFileSize(cert_file, NULL);
+ char *buf = NULL;
+ if (INVALID_FILE_SIZE != file_size)
+ buf = (char *) malloc(file_size);
+ if (!buf || !ReadFile(cert_file, buf, file_size, &nread, NULL)
+ || file_size != nread) {
+ HRESULT status = GetLastError();
+ CloseHandle(cert_file);
+ free(buf);
+ ssl_log_error_status(status, "Reading the private key from file failed %s", store_name);
+ *error = -5;
+ return NULL;
+ }
+ CloseHandle(cert_file);
+
+ CRYPT_DATA_BLOB blob;
+ blob.cbData = nread;
+ blob.pbData = (BYTE *) buf;
+
+ wchar_t *pwUCS2 = NULL;
+ int pwlen = 0;
+ if (passwd) {
+ // convert passwd to null terminated wchar_t (Windows UCS2)
+ pwlen = strlen(passwd);
+ pwUCS2 = (wchar_t *) calloc(pwlen + 1, sizeof(wchar_t));
+ int nwc = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, passwd, pwlen, &pwUCS2[0], pwlen);
+ if (!nwc) {
+ ssl_log_error_status(GetLastError(), "Error converting password from UTF8");
+ free(buf);
+ free(pwUCS2);
+ *error = -6;
+ return NULL;
+ }
+ }
+
+ cert_store = PFXImportCertStore(&blob, pwUCS2, 0);
+ if (pwUCS2) {
+ SecureZeroMemory(pwUCS2, pwlen * sizeof(wchar_t));
+ free(pwUCS2);
+ }
+ if (cert_store == NULL) {
+ ssl_log_error_status(GetLastError(), "Failed to import the file based certificate store");
+ free(buf);
+ *error = -7;
+ return NULL;
+ }
+
+ free(buf);
+ }
+
+ return cert_store;
+}
+
+static bool store_contains(HCERTSTORE store, PCCERT_CONTEXT cert)
+{
+ DWORD find_type = CERT_FIND_EXISTING; // Require exact match
+ PCCERT_CONTEXT tcert = CertFindCertificateInStore(store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ 0, find_type, cert, 0);
+ if (tcert) {
+ CertFreeCertificateContext(tcert);
+ return true;
+ }
+ return false;
+}
+
+/* Match the DNS name pattern from the peer certificate against our configured peer
+ hostname */
+static bool match_dns_pattern(const char *hostname, const char *pattern, int plen)
+{
+ int slen = (int) strlen(hostname);
+ if (memchr( pattern, '*', plen ) == NULL)
+ return (plen == slen &&
+ pn_strncasecmp( pattern, hostname, plen ) == 0);
+
+ /* dns wildcarded pattern - RFC2818 */
+ char plabel[64]; /* max label length < 63 - RFC1034 */
+ char slabel[64];
+
+ while (plen > 0 && slen > 0) {
+ const char *cptr;
+ int len;
+
+ cptr = (const char *) memchr( pattern, '.', plen );
+ len = (cptr) ? cptr - pattern : plen;
+ if (len > (int) sizeof(plabel) - 1) return false;
+ memcpy( plabel, pattern, len );
+ plabel[len] = 0;
+ if (cptr) ++len; // skip matching '.'
+ pattern += len;
+ plen -= len;
+
+ cptr = (const char *) memchr( hostname, '.', slen );
+ len = (cptr) ? cptr - hostname : slen;
+ if (len > (int) sizeof(slabel) - 1) return false;
+ memcpy( slabel, hostname, len );
+ slabel[len] = 0;
+ if (cptr) ++len; // skip matching '.'
+ hostname += len;
+ slen -= len;
+
+ char *star = strchr( plabel, '*' );
+ if (!star) {
+ if (pn_strcasecmp( plabel, slabel )) return false;
+ } else {
+ *star = '\0';
+ char *prefix = plabel;
+ int prefix_len = strlen(prefix);
+ char *suffix = star + 1;
+ int suffix_len = strlen(suffix);
+ if (prefix_len && pn_strncasecmp( prefix, slabel, prefix_len )) return false;
+ if (suffix_len && pn_strncasecmp( suffix,
+ slabel + (strlen(slabel) - suffix_len),
+ suffix_len )) return false;
+ }
+ }
+
+ return plen == slen;
+}
+
+// Caller must free the returned buffer
+static char* wide_to_utf8(LPWSTR wstring)
+{
+ int len = WideCharToMultiByte(CP_UTF8, 0, wstring, -1, 0, 0, 0, 0);
+ if (!len) {
+ ssl_log_error_status(GetLastError(), "converting UCS2 to UTF8");
+ return NULL;
+ }
+ char *p = (char *) malloc(len);
+ if (!p) return NULL;
+ if (WideCharToMultiByte(CP_UTF8, 0, wstring, -1, p, len, 0, 0))
+ return p;
+ ssl_log_error_status(GetLastError(), "converting UCS2 to UTF8");
+ free (p);
+ return NULL;
+}
+
+static bool server_name_matches(const char *server_name, CERT_EXTENSION *alt_name_ext, PCCERT_CONTEXT cert)
+{
+ // As for openssl.c: alt names first, then CN
+ bool matched = false;
+
+ if (alt_name_ext) {
+ CERT_ALT_NAME_INFO* alt_name_info = NULL;
+ DWORD size = 0;
+ if(!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, szOID_SUBJECT_ALT_NAME2,
+ alt_name_ext->Value.pbData, alt_name_ext->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
+ 0, &alt_name_info, &size)) {
+ ssl_log_error_status(GetLastError(), "Alternative name match internal error");
+ return false;
+ }
+
+ int name_ct = alt_name_info->cAltEntry;
+ for (int i = 0; !matched && i < name_ct; ++i) {
+ if (alt_name_info->rgAltEntry[i].dwAltNameChoice == CERT_ALT_NAME_DNS_NAME) {
+ char *alt_name = wide_to_utf8(alt_name_info->rgAltEntry[i].pwszDNSName);
+ if (alt_name) {
+ matched = match_dns_pattern(server_name, (const char *) alt_name, strlen(alt_name));
+ free(alt_name);
+ }
+ }
+ }
+ LocalFree(&alt_name_info);
+ }
+
+ if (!matched) {
+ PCERT_INFO info = cert->pCertInfo;
+ DWORD len = CertGetNameString(cert, CERT_NAME_ATTR_TYPE, 0, szOID_COMMON_NAME, 0, 0);
+ char *name = (char *) malloc(len);
+ if (name) {
+ int count = CertGetNameString(cert, CERT_NAME_ATTR_TYPE, 0, szOID_COMMON_NAME, name, len);
+ if (count)
+ matched = match_dns_pattern(server_name, (const char *) name, strlen(name));
+ free(name);
+ }
+ }
+ return matched;
+}
+
+const char* pn_ssl_get_remote_subject_subfield(pn_ssl_t *ssl0, pn_ssl_cert_subject_subfield field)
+{
+ return NULL;
+}
+
+int pn_ssl_get_cert_fingerprint(pn_ssl_t *ssl0,
+ char *fingerprint,
+ size_t fingerprint_length,
+ pn_ssl_hash_alg hash_alg)
+{
+ *fingerprint = '\0';
+ return -1;
+}
+
+static HRESULT verify_peer(pni_ssl_t *ssl, HCERTSTORE root_store, const char *server_name, bool tracing)
+{
+ // Free/release the following before return:
+ PCCERT_CONTEXT peer_cc = 0;
+ PCCERT_CONTEXT trust_anchor = 0;
+ PCCERT_CHAIN_CONTEXT chain_context = 0;
+ wchar_t *nameUCS2 = 0;
+
+ if (server_name && strlen(server_name) > 255) {
+ ssl_log_error("invalid server name: %s\n", server_name);
+ return WSAENAMETOOLONG;
+ }
+
+ // Get peer's certificate.
+ SECURITY_STATUS status;
+ status = QueryContextAttributes(&ssl->ctxt_handle, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &peer_cc);
+ if (status != SEC_E_OK) {
+ ssl_log_error_status(status, "can't obtain remote peer certificate information");
+ return status;
+ }
+
+ // Build the peer's certificate chain. Multiple chains may be built but we
+ // care about rgpChain[0], which is the best. Custom root stores are not
+ // allowed until W8/server 2012: see CERT_CHAIN_ENGINE_CONFIG. For now, we
+ // manually override to taste.
+
+ // Chain verification functions give false reports for CRL if the trust anchor
+ // is not in the official root store. We ignore CRL completely if it doesn't
+ // apply to any untrusted certs in the chain, and defer to SChannel's veto
+ // otherwise. To rely on CRL, the CA must be in both the official system
+ // trusted root store and the Proton cred->trust_store. To defeat CRL, the
+ // most distal cert with CRL must be placed in the Proton cred->trust_store.
+ // Similarly, certificate usage checking is overly strict at times.
+
+ CERT_CHAIN_PARA desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.cbSize = sizeof(desc);
+
+ LPSTR usages[] = { szOID_PKIX_KP_SERVER_AUTH };
+ DWORD n_usages = sizeof(usages) / sizeof(LPSTR);
+ desc.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
+ desc.RequestedUsage.Usage.cUsageIdentifier = n_usages;
+ desc.RequestedUsage.Usage.rgpszUsageIdentifier = usages;
+
+ if(!CertGetCertificateChain(0, peer_cc, 0, peer_cc->hCertStore, &desc,
+ CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT |
+ CERT_CHAIN_CACHE_END_CERT,
+ 0, &chain_context)){
+ HRESULT st = GetLastError();
+ ssl_log_error_status(st, "Basic certificate chain check failed");
+ CertFreeCertificateContext(peer_cc);
+ return st;
+ }
+ if (chain_context->cChain < 1 || chain_context->rgpChain[0]->cElement < 1) {
+ ssl_log_error("empty chain with status %x %x\n", chain_context->TrustStatus.dwErrorStatus,
+ chain_context->TrustStatus.dwInfoStatus);
+ return SEC_E_CERT_UNKNOWN;
+ }
+
+ int chain_len = chain_context->rgpChain[0]->cElement;
+ PCCERT_CONTEXT leaf_cert = chain_context->rgpChain[0]->rgpElement[0]->pCertContext;
+ PCCERT_CONTEXT trunk_cert = chain_context->rgpChain[0]->rgpElement[chain_len - 1]->pCertContext;
+ if (tracing)
+ // See doc for CERT_CHAIN_POLICY_STATUS for bit field error and info status values
+ ssl_log_error("status for complete chain: error bits %x info bits %x\n",
+ chain_context->TrustStatus.dwErrorStatus, chain_context->TrustStatus.dwInfoStatus);
+
+ // Supplement with checks against Proton's trusted_ca_db, custom revocation and usage.
+ HRESULT error = 0;
+ do {
+ // Do not return from this do loop. Set error = SEC_E_XXX and break.
+ bool revocable = false; // unless we see any untrusted certs that could be
+ for (int i = 0; i < chain_len; i++) {
+ CERT_CHAIN_ELEMENT *ce = chain_context->rgpChain[0]->rgpElement[i];
+ PCCERT_CONTEXT cc = ce->pCertContext;
+ if (cc->pCertInfo->dwVersion != CERT_V3) {
+ if (tracing)
+ ssl_log_error("certificate chain element %d is not version 3\n", i);
+ error = SEC_E_CERT_WRONG_USAGE; // A fossil
+ break;
+ }
+
+ if (!trust_anchor && store_contains(root_store, cc))
+ trust_anchor = CertDuplicateCertificateContext(cc);
+
+ int n_ext = cc->pCertInfo->cExtension;
+ for (int ii = 0; ii < n_ext && !revocable && !trust_anchor; ii++) {
+ CERT_EXTENSION *p = &cc->pCertInfo->rgExtension[ii];
+ // rfc 5280 extensions for revocation
+ if (!strcmp(p->pszObjId, szOID_AUTHORITY_INFO_ACCESS) ||
+ !strcmp(p->pszObjId, szOID_CRL_DIST_POINTS) ||
+ !strcmp(p->pszObjId, szOID_FRESHEST_CRL)) {
+ revocable = true;
+ }
+ }
+
+ if (tracing) {
+ char name[512];
+ const char *is_anchor = (cc == trust_anchor) ? " trust anchor" : "";
+ if (!CertNameToStr(cc->dwCertEncodingType, &cc->pCertInfo->Subject,
+ CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, name, sizeof(name)))
+ strcpy(name, "[too long]");
+ ssl_log_error("element %d (name: %s)%s error bits %x info bits %x\n", i, name, is_anchor,
+ ce->TrustStatus.dwErrorStatus, ce->TrustStatus.dwInfoStatus);
+ }
+ }
+ if (error)
+ break;
+
+ if (!trust_anchor) {
+ // We don't trust any of the certs in the chain, see if the last cert
+ // is issued by a Proton trusted CA.
+ DWORD flags = CERT_STORE_NO_ISSUER_FLAG || CERT_STORE_SIGNATURE_FLAG ||
+ CERT_STORE_TIME_VALIDITY_FLAG;
+ trust_anchor = CertGetIssuerCertificateFromStore(root_store, trunk_cert, 0, &flags);
+ if (trust_anchor) {
+ if (tracing) {
+ if (flags & CERT_STORE_SIGNATURE_FLAG)
+ ssl_log_error("root certificate signature failure\n");
+ if (flags & CERT_STORE_TIME_VALIDITY_FLAG)
+ ssl_log_error("root certificate time validity failure\n");
+ }
+ if (flags) {
+ CertFreeCertificateContext(trust_anchor);
+ trust_anchor = 0;
+ }
+ }
+ }
+ if (!trust_anchor) {
+ error = SEC_E_UNTRUSTED_ROOT;
+ break;
+ }
+
+ bool strict_usage = false;
+ CERT_EXTENSION *leaf_alt_names = 0;
+ if (leaf_cert != trust_anchor) {
+ int n_ext = leaf_cert->pCertInfo->cExtension;
+ for (int ii = 0; ii < n_ext; ii++) {
+ CERT_EXTENSION *p = &leaf_cert->pCertInfo->rgExtension[ii];
+ if (!strcmp(p->pszObjId, szOID_ENHANCED_KEY_USAGE))
+ strict_usage = true;
+ if (!strcmp(p->pszObjId, szOID_SUBJECT_ALT_NAME2))
+ if (p->Value.pbData)
+ leaf_alt_names = p;
+ }
+ }
+
+ if (server_name) {
+ int len = strlen(server_name);
+ nameUCS2 = (wchar_t *) calloc(len + 1, sizeof(wchar_t));
+ int nwc = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, server_name, len, &nameUCS2[0], len);
+ if (!nwc) {
+ error = GetLastError();
+ ssl_log_error_status(error, "Error converting server name from UTF8");
+ break;
+ }
+ }
+
+ // SSL-specific parameters (ExtraPolicy below)
+ SSL_EXTRA_CERT_CHAIN_POLICY_PARA ssl_desc;
+ memset(&ssl_desc, 0, sizeof(ssl_desc));
+ ssl_desc.cbSize = sizeof(ssl_desc);
+ ssl_desc.pwszServerName = nameUCS2;
+ ssl_desc.dwAuthType = nameUCS2 ? AUTHTYPE_SERVER : AUTHTYPE_CLIENT;
+ ssl_desc.fdwChecks = SECURITY_FLAG_IGNORE_UNKNOWN_CA;
+ if (server_name)
+ ssl_desc.fdwChecks |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
+ if (!revocable)
+ ssl_desc.fdwChecks |= SECURITY_FLAG_IGNORE_REVOCATION;
+ if (!strict_usage)
+ ssl_desc.fdwChecks |= SECURITY_FLAG_IGNORE_WRONG_USAGE;
+
+ // General certificate chain parameters
+ CERT_CHAIN_POLICY_PARA chain_desc;
+ memset(&chain_desc, 0, sizeof(chain_desc));
+ chain_desc.cbSize = sizeof(chain_desc);
+ chain_desc.dwFlags = CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG;
+ if (!revocable)
+ chain_desc.dwFlags |= CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
+ if (!strict_usage)
+ chain_desc.dwFlags |= CERT_CHAIN_POLICY_IGNORE_WRONG_USAGE_FLAG;
+ chain_desc.pvExtraPolicyPara = &ssl_desc;
+
+ CERT_CHAIN_POLICY_STATUS chain_status;
+ memset(&chain_status, 0, sizeof(chain_status));
+ chain_status.cbSize = sizeof(chain_status);
+
+ if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chain_context,
+ &chain_desc, &chain_status)) {
+ error = GetLastError();
+ // Failure to complete the check, does not (in)validate the cert.
+ ssl_log_error_status(error, "Supplemental certificate chain check failed");
+ break;
+ }
+
+ if (chain_status.dwError) {
+ error = chain_status.dwError;
+ if (tracing) {
+ ssl_log_error_status(chain_status.dwError, "Certificate chain verification error");
+ if (chain_status.lChainIndex == 0 && chain_status.lElementIndex != -1) {
+ int idx = chain_status.lElementIndex;
+ CERT_CHAIN_ELEMENT *ce = chain_context->rgpChain[0]->rgpElement[idx];
+ ssl_log_error(" chain failure at %d error/info: %x %x\n", idx,
+ ce->TrustStatus.dwErrorStatus, ce->TrustStatus.dwInfoStatus);
+ }
+ }
+ break;
+ }
+
+ if (server_name && ssl->verify_mode == PN_SSL_VERIFY_PEER_NAME &&
+ !server_name_matches(server_name, leaf_alt_names, leaf_cert)) {
+ error = SEC_E_WRONG_PRINCIPAL;
+ break;
+ }
+ else if (ssl->verify_mode == PN_SSL_VERIFY_PEER_NAME && !server_name) {
+ ssl_log_error("Error: configuration error: PN_SSL_VERIFY_PEER_NAME configured, but no peer hostname set!");
+ error = SEC_E_WRONG_PRINCIPAL;
+ break;
+ }
+ } while (0);
+
+ if (tracing && !error)
+ ssl_log_error("peer certificate authenticated\n");
+
+ // Lots to clean up.
+ if (peer_cc)
+ CertFreeCertificateContext(peer_cc);
+ if (trust_anchor)
+ CertFreeCertificateContext(trust_anchor);
+ if (chain_context)
+ CertFreeCertificateChain(chain_context);
+ free(nameUCS2);
+ return error;
+}
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/37136940/c/src/ssl/ssl-internal.h
----------------------------------------------------------------------
diff --git a/c/src/ssl/ssl-internal.h b/c/src/ssl/ssl-internal.h
new file mode 100644
index 0000000..d3205ea
--- /dev/null
+++ b/c/src/ssl/ssl-internal.h
@@ -0,0 +1,36 @@
+#ifndef PROTON_SSL_INTERNAL_H
+#define PROTON_SSL_INTERNAL_H 1
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "proton/ssl.h"
+
+/** @file
+ * Internal API for SSL/TLS support in the Driver Layer.
+ *
+ * Generic API to abstract the implementation of SSL/TLS from the Driver codebase.
+ *
+ */
+
+// release the SSL context
+void pn_ssl_free(pn_transport_t *transport);
+
+#endif /* ssl-internal.h */
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/37136940/c/src/ssl/ssl_stub.c
----------------------------------------------------------------------
diff --git a/c/src/ssl/ssl_stub.c b/c/src/ssl/ssl_stub.c
new file mode 100644
index 0000000..0a6a13f
--- /dev/null
+++ b/c/src/ssl/ssl_stub.c
@@ -0,0 +1,172 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <proton/ssl.h>
+#include <proton/error.h>
+#include <proton/transport.h>
+#include "core/engine-internal.h"
+
+
+/** @file
+ * SSL/TLS support API.
+ *
+ * This file contains stub implementations of the SSL/TLS API. This implementation is
+ * used if there is no SSL/TLS support in the system's environment.
+ */
+
+bool pn_ssl_present(void)
+{
+ return false;
+}
+
+pn_ssl_t *pn_ssl(pn_transport_t *transport)
+{
+ return NULL;
+}
+
+int pn_ssl_init(pn_ssl_t *ssl, pn_ssl_domain_t *domain,
+ const char *session_id)
+{
+ return -1;
+}
+
+void pn_ssl_free( pn_ssl_t *ssl)
+{
+}
+
+void pn_ssl_trace(pn_ssl_t *ssl, pn_trace_t trace)
+{
+}
+
+ssize_t pn_ssl_input(pn_transport_t *transport, unsigned int layer, const char *bytes, size_t available)
+{
+ return PN_EOS;
+}
+
+ssize_t pn_ssl_output(pn_transport_t *transport, unsigned int layer, char *buffer, size_t max_size)
+{
+ return PN_EOS;
+}
+
+const pn_io_layer_t ssl_layer = {
+ pn_ssl_input,
+ pn_ssl_output,
+ NULL,
+ NULL
+};
+
+bool pn_ssl_get_cipher_name(pn_ssl_t *ssl, char *buffer, size_t size)
+{
+ *buffer = '\0';
+ return false;
+}
+
+bool pn_ssl_get_protocol_name(pn_ssl_t *ssl, char *buffer, size_t size)
+{
+ *buffer = '\0';
+ return false;
+}
+
+pn_ssl_domain_t *pn_ssl_domain( pn_ssl_mode_t mode)
+{
+ return NULL;
+}
+
+void pn_ssl_domain_free( pn_ssl_domain_t *d )
+{
+}
+
+int pn_ssl_domain_set_credentials( pn_ssl_domain_t *domain,
+ const char *certificate_file,
+ const char *private_key_file,
+ const char *password)
+{
+ return -1;
+}
+
+int pn_ssl_domain_set_trusted_ca_db(pn_ssl_domain_t *domain,
+ const char *certificate_db)
+{
+ return -1;
+}
+
+int pn_ssl_domain_set_peer_authentication(pn_ssl_domain_t *domain,
+ const pn_ssl_verify_mode_t mode,
+ const char *trusted_CAs)
+{
+ return -1;
+}
+
+int pn_ssl_domain_allow_unsecured_client(pn_ssl_domain_t *domain)
+{
+ return -1;
+}
+
+int pn_ssl_domain_set_ciphers(pn_ssl_domain_t *domain, const char *ciphers)
+{
+ return -1;
+}
+
+int pn_ssl_domain_set_protocols(pn_ssl_domain_t* domain, const char* protocols)
+{
+ return -1;
+}
+
+bool pn_ssl_allow_unsecured(pn_ssl_t *ssl)
+{
+ return true;
+}
+
+pn_ssl_resume_status_t pn_ssl_resume_status( pn_ssl_t *s )
+{
+ return PN_SSL_RESUME_UNKNOWN;
+}
+
+int pn_ssl_set_peer_hostname( pn_ssl_t *ssl, const char *hostname)
+{
+ return -1;
+}
+
+int pn_ssl_get_peer_hostname( pn_ssl_t *ssl, char *hostname, size_t *bufsize )
+{
+ return -1;
+}
+
+const char* pn_ssl_get_remote_subject(pn_ssl_t *ssl)
+{
+ return NULL;
+}
+
+int pn_ssl_get_ssf(pn_ssl_t *ssl)
+{
+ return 0;
+}
+
+int pn_ssl_get_cert_fingerprint(pn_ssl_t *ssl0, char *fingerprint, size_t fingerprint_length, pn_ssl_hash_alg hash_alg)
+{
+ *fingerprint = '\0';
+ return -1;
+}
+
+const char* pn_ssl_get_remote_subject_subfield(pn_ssl_t *ssl0, pn_ssl_cert_subject_subfield field)
+{
+ return NULL;
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org