+++ /dev/null
-/*
- * 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.
- */
-module thrift.async.ssl;
-
-import core.thread : Fiber;
-import core.time : Duration;
-import std.array : empty;
-import std.conv : to;
-import std.exception : enforce;
-import std.socket;
-import deimos.openssl.err;
-import deimos.openssl.ssl;
-import thrift.base;
-import thrift.async.base;
-import thrift.async.socket;
-import thrift.internal.ssl;
-import thrift.internal.ssl_bio;
-import thrift.transport.base;
-import thrift.transport.ssl;
-
-/**
- * Provides SSL/TLS encryption for async sockets.
- *
- * This implementation should be considered experimental, as it context-switches
- * between fibers from within OpenSSL calls, and the safety of this has not yet
- * been verified.
- *
- * For obvious reasons (the SSL connection is stateful), more than one instance
- * should never be used on a given socket at the same time.
- */
-// Note: This could easily be extended to other transports in the future as well.
-// There are only two parts of the implementation which don't work with a generic
-// TTransport: 1) the certificate verification, for which peer name/address are
-// needed from the socket, and 2) the connection shutdown, where the associated
-// async manager is needed because close() is not usually called from within a
-// work item.
-final class TAsyncSSLSocket : TBaseTransport {
- /**
- * Constructor.
- *
- * Params:
- * context = The SSL socket context to use. A reference to it is stored so
- * that it does not get cleaned up while the socket is used.
- * transport = The underlying async network transport to use for
- * communication.
- */
- this(TAsyncSocket underlyingSocket, TSSLContext context) {
- socket_ = underlyingSocket;
- context_ = context;
- serverSide_ = context.serverSide;
- accessManager_ = context.accessManager;
- }
-
- override bool isOpen() @property {
- if (ssl_ is null || !socket_.isOpen) return false;
-
- auto shutdown = SSL_get_shutdown(ssl_);
- bool shutdownReceived = (shutdown & SSL_RECEIVED_SHUTDOWN) != 0;
- bool shutdownSent = (shutdown & SSL_SENT_SHUTDOWN) != 0;
- return !(shutdownReceived && shutdownSent);
- }
-
- override bool peek() {
- if (!isOpen) return false;
- checkHandshake();
-
- byte bt = void;
- auto rc = SSL_peek(ssl_, &bt, bt.sizeof);
- sslEnforce(rc >= 0, "SSL_peek");
-
- if (rc == 0) {
- ERR_clear_error();
- }
- return (rc > 0);
- }
-
- override void open() {
- enforce(!serverSide_, "Cannot open a server-side SSL socket.");
- if (isOpen) return;
-
- if (ssl_) {
- // If the underlying socket was automatically closed because of an error
- // (i.e. close() was called from inside a socket method), we can land
- // here with the SSL object still allocated; delete it here.
- cleanupSSL();
- }
-
- socket_.open();
- }
-
- override void close() {
- if (!isOpen) return;
-
- if (ssl_ !is null) {
- // SSL needs to send/receive data over the socket as part of the shutdown
- // protocol, so we must execute the calls in the context of the associated
- // async manager. On the other hand, TTransport clients expect the socket
- // to be closed when close() returns, so we have to block until the
- // shutdown work item has been executed.
- import core.sync.condition;
- import core.sync.mutex;
-
- int rc = void;
- auto doneMutex = new Mutex;
- auto doneCond = new Condition(doneMutex);
- synchronized (doneMutex) {
- socket_.asyncManager.execute(socket_, {
- rc = SSL_shutdown(ssl_);
- if (rc == 0) {
- rc = SSL_shutdown(ssl_);
- }
- synchronized (doneMutex) doneCond.notifyAll();
- });
- doneCond.wait();
- }
-
- if (rc < 0) {
- // Do not throw an exception here as leaving the transport "open" will
- // probably produce only more errors, and the chance we can do
- // something about the error e.g. by retrying is very low.
- logError("Error while shutting down SSL: %s", getSSLException());
- }
-
- cleanupSSL();
- }
-
- socket_.close();
- }
-
- override size_t read(ubyte[] buf) {
- checkHandshake();
- auto rc = SSL_read(ssl_, buf.ptr, cast(int)buf.length);
- sslEnforce(rc >= 0, "SSL_read");
- return rc;
- }
-
- override void write(in ubyte[] buf) {
- checkHandshake();
-
- // Loop in case SSL_MODE_ENABLE_PARTIAL_WRITE is set in SSL_CTX.
- size_t written = 0;
- while (written < buf.length) {
- auto bytes = SSL_write(ssl_, buf.ptr + written,
- cast(int)(buf.length - written));
- sslEnforce(bytes > 0, "SSL_write");
- written += bytes;
- }
- }
-
- override void flush() {
- checkHandshake();
-
- auto bio = SSL_get_wbio(ssl_);
- enforce(bio !is null, new TSSLException("SSL_get_wbio returned null"));
-
- auto rc = BIO_flush(bio);
- sslEnforce(rc == 1, "BIO_flush");
- }
-
- /**
- * Whether to use client or server side SSL handshake protocol.
- */
- bool serverSide() @property const {
- return serverSide_;
- }
-
- /// Ditto
- void serverSide(bool value) @property {
- serverSide_ = value;
- }
-
- /**
- * The access manager to use.
- */
- void accessManager(TAccessManager value) @property {
- accessManager_ = value;
- }
-
-private:
- /**
- * If the condition is false, cleans up the SSL connection and throws the
- * exception for the last SSL error.
- */
- void sslEnforce(bool condition, string location) {
- if (!condition) {
- // We need to fetch the error first, as the error stack will be cleaned
- // when shutting down SSL.
- auto e = getSSLException(location);
- cleanupSSL();
- throw e;
- }
- }
-
- /**
- * Frees the SSL connection object and clears the SSL error state.
- */
- void cleanupSSL() {
- SSL_free(ssl_);
- ssl_ = null;
- ERR_remove_state(0);
- }
-
- /**
- * Makes sure the SSL connection is up and running, and initializes it if not.
- */
- void checkHandshake() {
- enforce(socket_.isOpen, new TTransportException(
- TTransportException.Type.NOT_OPEN));
-
- if (ssl_ !is null) return;
- ssl_ = context_.createSSL();
-
- auto bio = createTTransportBIO(socket_, false);
- SSL_set_bio(ssl_, bio, bio);
-
- int rc = void;
- if (serverSide_) {
- rc = SSL_accept(ssl_);
- } else {
- rc = SSL_connect(ssl_);
- }
- enforce(rc > 0, getSSLException());
-
- auto addr = socket_.getPeerAddress();
- authorize(ssl_, accessManager_, addr,
- (serverSide_ ? addr.toHostNameString() : socket_.host));
- }
-
- TAsyncSocket socket_;
- bool serverSide_;
- SSL* ssl_;
- TSSLContext context_;
- TAccessManager accessManager_;
-}
-
-/**
- * Wraps passed TAsyncSocket instances into TAsyncSSLSockets.
- *
- * Typically used with TAsyncClient. As an unfortunate consequence of the
- * async client design, the passed transports cannot be statically verified to
- * be of type TAsyncSocket. Instead, the type is verified at runtime – if a
- * transport of an unexpected type is passed to getTransport(), it fails,
- * throwing a TTransportException.
- *
- * Example:
- * ---
- * auto context = nwe TSSLContext();
- * ... // Configure SSL context.
- * auto factory = new TAsyncSSLSocketFactory(context);
- *
- * auto socket = new TAsyncSocket(someAsyncManager, host, port);
- * socket.open();
- *
- * auto client = new TAsyncClient!Service(transport, factory,
- * new TBinaryProtocolFactory!());
- * ---
- */
-class TAsyncSSLSocketFactory : TTransportFactory {
- ///
- this(TSSLContext context) {
- context_ = context;
- }
-
- override TAsyncSSLSocket getTransport(TTransport transport) {
- auto socket = cast(TAsyncSocket)transport;
- enforce(socket, new TTransportException(
- "TAsyncSSLSocketFactory requires a TAsyncSocket to work on, not a " ~
- to!string(typeid(transport)) ~ ".",
- TTransportException.Type.INTERNAL_ERROR
- ));
- return new TAsyncSSLSocket(socket, context_);
- }
-
-private:
- TSSLContext context_;
-}