Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id AB87F200BC7 for ; Fri, 25 Nov 2016 22:01:00 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id A8529160B2F; Fri, 25 Nov 2016 21:01:00 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id A4865160B22 for ; Fri, 25 Nov 2016 22:00:58 +0100 (CET) Received: (qmail 94434 invoked by uid 500); 25 Nov 2016 21:00:57 -0000 Mailing-List: contact commits-help@qpid.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@qpid.apache.org Delivered-To: mailing list commits@qpid.apache.org Received: (qmail 94412 invoked by uid 99); 25 Nov 2016 21:00:57 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 25 Nov 2016 21:00:57 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 6DE9BE07D6; Fri, 25 Nov 2016 21:00:57 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: aconway@apache.org To: commits@qpid.apache.org Date: Fri, 25 Nov 2016 21:01:00 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [04/48] 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 archived-at: Fri, 25 Nov 2016 21:01:00 -0000 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 -#include -#include "engine/engine-internal.h" -#include "platform.h" -#include "util.h" -#include "transport/autodetect.h" - -#include - -// security.h needs to see this to distinguish from kernel use. -#include -#define SECURITY_WIN32 -#include -#include -#include -#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(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(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