]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/jaegertracing/thrift/lib/d/src/thrift/internal/ssl.d
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / jaegertracing / thrift / lib / d / src / thrift / internal / ssl.d
diff --git a/ceph/src/jaegertracing/thrift/lib/d/src/thrift/internal/ssl.d b/ceph/src/jaegertracing/thrift/lib/d/src/thrift/internal/ssl.d
new file mode 100644 (file)
index 0000000..3af54b5
--- /dev/null
@@ -0,0 +1,240 @@
+/*
+ * 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);
+}