--- /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.internal.ssl;
+
+import core.memory : GC;
+import core.stdc.config;
+import core.stdc.errno : errno;
+import core.stdc.string : strerror;
+import deimos.openssl.err;
+import deimos.openssl.ssl;
+import deimos.openssl.x509v3;
+import std.array : empty, appender;
+import std.conv : to;
+import std.socket : Address;
+import thrift.transport.ssl;
+
+/**
+ * Checks if the peer is authorized after the SSL handshake has been
+ * completed on the given conncetion and throws an TSSLException if not.
+ *
+ * Params:
+ * ssl = The SSL connection to check.
+ * accessManager = The access manager to check the peer againts.
+ * peerAddress = The (IP) address of the peer.
+ * hostName = The host name of the peer.
+ */
+void authorize(SSL* ssl, TAccessManager accessManager,
+ Address peerAddress, lazy string hostName
+) {
+ alias TAccessManager.Decision Decision;
+
+ auto rc = SSL_get_verify_result(ssl);
+ if (rc != X509_V_OK) {
+ throw new TSSLException("SSL_get_verify_result(): " ~
+ to!string(X509_verify_cert_error_string(rc)));
+ }
+
+ auto cert = SSL_get_peer_certificate(ssl);
+ if (cert is null) {
+ // Certificate is not present.
+ if (SSL_get_verify_mode(ssl) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
+ throw new TSSLException(
+ "Authorize: Required certificate not present.");
+ }
+
+ // If we don't have an access manager set, we don't intend to authorize
+ // the client, so everything's fine.
+ if (accessManager) {
+ throw new TSSLException(
+ "Authorize: Certificate required for authorization.");
+ }
+ return;
+ }
+
+ if (accessManager is null) {
+ // No access manager set, can return immediately as the cert is valid
+ // and all peers are authorized.
+ X509_free(cert);
+ return;
+ }
+
+ // both certificate and access manager are present
+ auto decision = accessManager.verify(peerAddress);
+
+ if (decision != Decision.SKIP) {
+ X509_free(cert);
+ if (decision != Decision.ALLOW) {
+ throw new TSSLException("Authorize: Access denied based on remote IP.");
+ }
+ return;
+ }
+
+ // Check subjectAltName(s), if present.
+ auto alternatives = cast(STACK_OF!(GENERAL_NAME)*)
+ X509_get_ext_d2i(cert, NID_subject_alt_name, null, null);
+ if (alternatives != null) {
+ auto count = sk_GENERAL_NAME_num(alternatives);
+ for (int i = 0; decision == Decision.SKIP && i < count; i++) {
+ auto name = sk_GENERAL_NAME_value(alternatives, i);
+ if (name is null) {
+ continue;
+ }
+ auto data = ASN1_STRING_data(name.d.ia5);
+ auto length = ASN1_STRING_length(name.d.ia5);
+ switch (name.type) {
+ case GENERAL_NAME.GEN_DNS:
+ decision = accessManager.verify(hostName, cast(char[])data[0 .. length]);
+ break;
+ case GENERAL_NAME.GEN_IPADD:
+ decision = accessManager.verify(peerAddress, data[0 .. length]);
+ break;
+ default:
+ // Do nothing.
+ }
+ }
+
+ // DMD @@BUG@@: Empty template arguments parens should not be needed.
+ sk_GENERAL_NAME_pop_free!()(alternatives, &GENERAL_NAME_free);
+ }
+
+ // If we are alredy done, return.
+ if (decision != Decision.SKIP) {
+ X509_free(cert);
+ if (decision != Decision.ALLOW) {
+ throw new TSSLException("Authorize: Access denied.");
+ }
+ return;
+ }
+
+ // Check commonName.
+ auto name = X509_get_subject_name(cert);
+ if (name !is null) {
+ X509_NAME_ENTRY* entry;
+ char* utf8;
+ int last = -1;
+ while (decision == Decision.SKIP) {
+ last = X509_NAME_get_index_by_NID(name, NID_commonName, last);
+ if (last == -1)
+ break;
+ entry = X509_NAME_get_entry(name, last);
+ if (entry is null)
+ continue;
+ auto common = X509_NAME_ENTRY_get_data(entry);
+ auto size = ASN1_STRING_to_UTF8(&utf8, common);
+ decision = accessManager.verify(hostName, utf8[0 .. size]);
+ CRYPTO_free(utf8);
+ }
+ }
+ X509_free(cert);
+ if (decision != Decision.ALLOW) {
+ throw new TSSLException("Authorize: Could not authorize peer.");
+ }
+}
+
+/*
+ * OpenSSL error information used for storing D exceptions on the OpenSSL
+ * error stack.
+ */
+enum ERR_LIB_D_EXCEPTION = ERR_LIB_USER;
+enum ERR_F_D_EXCEPTION = 0; // function id - what to use here?
+enum ERR_R_D_EXCEPTION = 1234; // 99 and above are reserved for applications
+enum ERR_FILE_D_EXCEPTION = "d_exception";
+enum ERR_LINE_D_EXCEPTION = 0;
+enum ERR_FLAGS_D_EXCEPTION = 0;
+
+/**
+ * Returns an exception for the last.
+ *
+ * Params:
+ * location = An optional "location" to add to the error message (typically
+ * the last SSL API call).
+ */
+Exception getSSLException(string location = null, string clientFile = __FILE__,
+ size_t clientLine = __LINE__
+) {
+ // We can return either an exception saved from D BIO code, or a "true"
+ // OpenSSL error. Because there can possibly be more than one error on the
+ // error stack, we have to fetch all of them, and pick the last, i.e. newest
+ // one. We concatenate multiple successive OpenSSL error messages into a
+ // single one, but always just return the last D expcetion.
+ string message; // Probably better use an Appender here.
+ bool hadMessage;
+ Exception exception;
+
+ void initMessage() {
+ message.destroy();
+ hadMessage = false;
+ if (!location.empty) {
+ message ~= location;
+ message ~= ": ";
+ }
+ }
+ initMessage();
+
+ auto errn = errno;
+
+ const(char)* file = void;
+ int line = void;
+ const(char)* data = void;
+ int flags = void;
+ c_ulong code = void;
+ while ((code = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) {
+ if (ERR_GET_REASON(code) == ERR_R_D_EXCEPTION) {
+ initMessage();
+ GC.removeRoot(cast(void*)data);
+ exception = cast(Exception)data;
+ } else {
+ exception = null;
+
+ if (hadMessage) {
+ message ~= ", ";
+ }
+
+ auto reason = ERR_reason_error_string(code);
+ if (reason) {
+ message ~= "SSL error: " ~ to!string(reason);
+ } else {
+ message ~= "SSL error #" ~ to!string(code);
+ }
+
+ hadMessage = true;
+ }
+ }
+
+ // If the last item from the stack was a D exception, throw it.
+ if (exception) return exception;
+
+ // We are dealing with an OpenSSL error that doesn't root in a D exception.
+ if (!hadMessage) {
+ // If we didn't get an actual error from the stack yet, try errno.
+ string errnString;
+ if (errn != 0) {
+ errnString = to!string(strerror(errn));
+ }
+ if (errnString.empty) {
+ message ~= "Unknown error";
+ } else {
+ message ~= errnString;
+ }
+ }
+
+ message ~= ".";
+ return new TSSLException(message, clientFile, clientLine);
+}