You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by as...@apache.org on 2016/11/14 18:28:23 UTC
[02/20] qpid-proton git commit: PROTON-1350 PROTON-1351: Introduce
proton-c core library - Created new core proton library qpid-proton-core
which only contains protocol processsing and no IO. - Rearranged source tree
to separate core protocol code and
http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/a5850716/proton-c/src/windows/schannel.c
----------------------------------------------------------------------
diff --git a/proton-c/src/windows/schannel.c b/proton-c/src/windows/schannel.c
deleted file mode 100644
index 0201034..0000000
--- a/proton-c/src/windows/schannel.c
+++ /dev/null
@@ -1,2239 +0,0 @@
-/*
- *
- * 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 "engine/engine-internal.h"
-#include "platform.h"
-#include "util.h"
-#include "transport/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);
-
-/*
- * 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 {
- 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;
- 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);
- 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;
- return 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) {
- 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;
-}
-
-
-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 {
- 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;
-
- 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;
-}
-
-void pn_ssl_domain_free( pn_ssl_domain_t *domain )
-{
- if (!domain) return;
-
- if (--domain->ref_count == 0) {
- pn_decref(domain->cred);
- 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;
-
- if (win_credential_has_certificate(domain->cred)) {
- // Need a new win_credential_t to hold new certificate
- pn_decref(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;
-
- int ec = 0;
- HCERTSTORE store = open_cert_db(certificate_db, NULL, &ec);
- if (!store)
- return ec;
-
- if (domain->has_ca_db) {
- win_credential_t *new_cred = win_credential(domain->mode);
- if (!new_cred)
- return -1;
- new_cred->cert_context = CertDuplicateCertificateContext(domain->cred->cert_context);
- pn_decref(domain->cred);
- domain->cred = new_cred;
- }
-
- 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;
- 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)) {
- store = open_cert_db(trusted_CAs, NULL, &ec);
- if (!store)
- return ec;
- } else {
- store = CertDuplicateStore(domain->cred->trust_store);
- }
-
- if (domain->cred->server_CA_certs) {
- // Already have one
- changed = true;
- 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);
- new_cred->trust_store = CertDuplicateStore(domain->cred->trust_store);
- new_cred->trust_store_name = pn_strdup(domain->cred->trust_store_name);
- pn_decref(domain->cred);
- domain->cred = new_cred;
- }
-
- domain->cred->server_CA_certs = store;
- }
- 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) {
- 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);
- pn_decref(domain->cred);
- domain->cred = new_cred;
- }
-
- domain->verify_mode = mode;
- domain->cred->server_CA_certs = store;
-
- return 0;
-}
-
-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;
-
- 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);
- if (ssl->state != RUNNING || !SecIsValidHandle(&ssl->ctxt_handle))
- return false;
- *buffer = '\0';
- SecPkgContext_ConnectionInfo info;
- if (QueryContextAttributes(&ssl->ctxt_handle, SECPKG_ATTR_CONNECTION_INFO, &info) == SEC_E_OK) {
- // TODO: come up with string for all permutations?
- 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);
- if (ssl->state != RUNNING || !SecIsValidHandle(&ssl->ctxt_handle))
- return false;
- *buffer = '\0';
- 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))
- 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))
- snprintf(buffer, size, "%s", "TLSv1.1");
- else if ((info.dwProtocol & 0xC00))
- 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
- 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);
- }
- pn_decref(ssl->cred);
- }
-
- if (ssl->domain) 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 = const_cast<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;
- 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 = const_cast<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 = 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 = 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 maximimum 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 shudown 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)
-{
- 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;
-}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org