]> git.proxmox.com Git - ceph.git/blob - ceph/src/jaegertracing/thrift/lib/d/src/thrift/async/ssl.d
buildsys: switch source download to quincy
[ceph.git] / ceph / src / jaegertracing / thrift / lib / d / src / thrift / async / ssl.d
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 module thrift.async.ssl;
20
21 import core.thread : Fiber;
22 import core.time : Duration;
23 import std.array : empty;
24 import std.conv : to;
25 import std.exception : enforce;
26 import std.socket;
27 import deimos.openssl.err;
28 import deimos.openssl.ssl;
29 import thrift.base;
30 import thrift.async.base;
31 import thrift.async.socket;
32 import thrift.internal.ssl;
33 import thrift.internal.ssl_bio;
34 import thrift.transport.base;
35 import thrift.transport.ssl;
36
37 /**
38 * Provides SSL/TLS encryption for async sockets.
39 *
40 * This implementation should be considered experimental, as it context-switches
41 * between fibers from within OpenSSL calls, and the safety of this has not yet
42 * been verified.
43 *
44 * For obvious reasons (the SSL connection is stateful), more than one instance
45 * should never be used on a given socket at the same time.
46 */
47 // Note: This could easily be extended to other transports in the future as well.
48 // There are only two parts of the implementation which don't work with a generic
49 // TTransport: 1) the certificate verification, for which peer name/address are
50 // needed from the socket, and 2) the connection shutdown, where the associated
51 // async manager is needed because close() is not usually called from within a
52 // work item.
53 final class TAsyncSSLSocket : TBaseTransport {
54 /**
55 * Constructor.
56 *
57 * Params:
58 * context = The SSL socket context to use. A reference to it is stored so
59 * that it does not get cleaned up while the socket is used.
60 * transport = The underlying async network transport to use for
61 * communication.
62 */
63 this(TAsyncSocket underlyingSocket, TSSLContext context) {
64 socket_ = underlyingSocket;
65 context_ = context;
66 serverSide_ = context.serverSide;
67 accessManager_ = context.accessManager;
68 }
69
70 override bool isOpen() @property {
71 if (ssl_ is null || !socket_.isOpen) return false;
72
73 auto shutdown = SSL_get_shutdown(ssl_);
74 bool shutdownReceived = (shutdown & SSL_RECEIVED_SHUTDOWN) != 0;
75 bool shutdownSent = (shutdown & SSL_SENT_SHUTDOWN) != 0;
76 return !(shutdownReceived && shutdownSent);
77 }
78
79 override bool peek() {
80 if (!isOpen) return false;
81 checkHandshake();
82
83 byte bt = void;
84 auto rc = SSL_peek(ssl_, &bt, bt.sizeof);
85 sslEnforce(rc >= 0, "SSL_peek");
86
87 if (rc == 0) {
88 ERR_clear_error();
89 }
90 return (rc > 0);
91 }
92
93 override void open() {
94 enforce(!serverSide_, "Cannot open a server-side SSL socket.");
95 if (isOpen) return;
96
97 if (ssl_) {
98 // If the underlying socket was automatically closed because of an error
99 // (i.e. close() was called from inside a socket method), we can land
100 // here with the SSL object still allocated; delete it here.
101 cleanupSSL();
102 }
103
104 socket_.open();
105 }
106
107 override void close() {
108 if (!isOpen) return;
109
110 if (ssl_ !is null) {
111 // SSL needs to send/receive data over the socket as part of the shutdown
112 // protocol, so we must execute the calls in the context of the associated
113 // async manager. On the other hand, TTransport clients expect the socket
114 // to be closed when close() returns, so we have to block until the
115 // shutdown work item has been executed.
116 import core.sync.condition;
117 import core.sync.mutex;
118
119 int rc = void;
120 auto doneMutex = new Mutex;
121 auto doneCond = new Condition(doneMutex);
122 synchronized (doneMutex) {
123 socket_.asyncManager.execute(socket_, {
124 rc = SSL_shutdown(ssl_);
125 if (rc == 0) {
126 rc = SSL_shutdown(ssl_);
127 }
128 synchronized (doneMutex) doneCond.notifyAll();
129 });
130 doneCond.wait();
131 }
132
133 if (rc < 0) {
134 // Do not throw an exception here as leaving the transport "open" will
135 // probably produce only more errors, and the chance we can do
136 // something about the error e.g. by retrying is very low.
137 logError("Error while shutting down SSL: %s", getSSLException());
138 }
139
140 cleanupSSL();
141 }
142
143 socket_.close();
144 }
145
146 override size_t read(ubyte[] buf) {
147 checkHandshake();
148 auto rc = SSL_read(ssl_, buf.ptr, cast(int)buf.length);
149 sslEnforce(rc >= 0, "SSL_read");
150 return rc;
151 }
152
153 override void write(in ubyte[] buf) {
154 checkHandshake();
155
156 // Loop in case SSL_MODE_ENABLE_PARTIAL_WRITE is set in SSL_CTX.
157 size_t written = 0;
158 while (written < buf.length) {
159 auto bytes = SSL_write(ssl_, buf.ptr + written,
160 cast(int)(buf.length - written));
161 sslEnforce(bytes > 0, "SSL_write");
162 written += bytes;
163 }
164 }
165
166 override void flush() {
167 checkHandshake();
168
169 auto bio = SSL_get_wbio(ssl_);
170 enforce(bio !is null, new TSSLException("SSL_get_wbio returned null"));
171
172 auto rc = BIO_flush(bio);
173 sslEnforce(rc == 1, "BIO_flush");
174 }
175
176 /**
177 * Whether to use client or server side SSL handshake protocol.
178 */
179 bool serverSide() @property const {
180 return serverSide_;
181 }
182
183 /// Ditto
184 void serverSide(bool value) @property {
185 serverSide_ = value;
186 }
187
188 /**
189 * The access manager to use.
190 */
191 void accessManager(TAccessManager value) @property {
192 accessManager_ = value;
193 }
194
195 private:
196 /**
197 * If the condition is false, cleans up the SSL connection and throws the
198 * exception for the last SSL error.
199 */
200 void sslEnforce(bool condition, string location) {
201 if (!condition) {
202 // We need to fetch the error first, as the error stack will be cleaned
203 // when shutting down SSL.
204 auto e = getSSLException(location);
205 cleanupSSL();
206 throw e;
207 }
208 }
209
210 /**
211 * Frees the SSL connection object and clears the SSL error state.
212 */
213 void cleanupSSL() {
214 SSL_free(ssl_);
215 ssl_ = null;
216 ERR_remove_state(0);
217 }
218
219 /**
220 * Makes sure the SSL connection is up and running, and initializes it if not.
221 */
222 void checkHandshake() {
223 enforce(socket_.isOpen, new TTransportException(
224 TTransportException.Type.NOT_OPEN));
225
226 if (ssl_ !is null) return;
227 ssl_ = context_.createSSL();
228
229 auto bio = createTTransportBIO(socket_, false);
230 SSL_set_bio(ssl_, bio, bio);
231
232 int rc = void;
233 if (serverSide_) {
234 rc = SSL_accept(ssl_);
235 } else {
236 rc = SSL_connect(ssl_);
237 }
238 enforce(rc > 0, getSSLException());
239
240 auto addr = socket_.getPeerAddress();
241 authorize(ssl_, accessManager_, addr,
242 (serverSide_ ? addr.toHostNameString() : socket_.host));
243 }
244
245 TAsyncSocket socket_;
246 bool serverSide_;
247 SSL* ssl_;
248 TSSLContext context_;
249 TAccessManager accessManager_;
250 }
251
252 /**
253 * Wraps passed TAsyncSocket instances into TAsyncSSLSockets.
254 *
255 * Typically used with TAsyncClient. As an unfortunate consequence of the
256 * async client design, the passed transports cannot be statically verified to
257 * be of type TAsyncSocket. Instead, the type is verified at runtime – if a
258 * transport of an unexpected type is passed to getTransport(), it fails,
259 * throwing a TTransportException.
260 *
261 * Example:
262 * ---
263 * auto context = nwe TSSLContext();
264 * ... // Configure SSL context.
265 * auto factory = new TAsyncSSLSocketFactory(context);
266 *
267 * auto socket = new TAsyncSocket(someAsyncManager, host, port);
268 * socket.open();
269 *
270 * auto client = new TAsyncClient!Service(transport, factory,
271 * new TBinaryProtocolFactory!());
272 * ---
273 */
274 class TAsyncSSLSocketFactory : TTransportFactory {
275 ///
276 this(TSSLContext context) {
277 context_ = context;
278 }
279
280 override TAsyncSSLSocket getTransport(TTransport transport) {
281 auto socket = cast(TAsyncSocket)transport;
282 enforce(socket, new TTransportException(
283 "TAsyncSSLSocketFactory requires a TAsyncSocket to work on, not a " ~
284 to!string(typeid(transport)) ~ ".",
285 TTransportException.Type.INTERNAL_ERROR
286 ));
287 return new TAsyncSSLSocket(socket, context_);
288 }
289
290 private:
291 TSSLContext context_;
292 }