SSL cleanup: moving some OpenSSL definitions to new dir folly/io/async/ssl
authorAnirudh Ramachandran <avr@fb.com>
Wed, 9 Mar 2016 02:09:22 +0000 (18:09 -0800)
committerFacebook Github Bot 1 <facebook-github-bot-1-bot@fb.com>
Wed, 9 Mar 2016 02:20:19 +0000 (18:20 -0800)
Summary:SSLContext and AsyncSSLSocket are growing with a lot of code that is
OpenSSL-specific, and it may be good to refactor some of these before it gets
out of hand.

This is also useful to reduce complexity as we some additional features such as ServerHello parsing and TLS Cached Info (D2936570)

Main changes:
 * Created a subdirectory folly/io/async/ssl to refactor code from folly/io/async. We may want to consider moving this out of folly/io/async
 * Moved OpenSSLPtrTypes.h to folly/io/async/ssl/OpenSSLPtrTypes.h
 * Moved 'OpenSSLUtils' from SSLContext to separate file OpenSSLUtils.{h,cpp}
 * Moved TLSExtensions and ClientHelloInfo from AsyncSSLSocket to TLSDefinitions.h

Reviewed By: siyengar

Differential Revision: D2978707

fb-gh-sync-id: a21f02947aeffccc447da2124a91cc99315df1c7
shipit-source-id: a21f02947aeffccc447da2124a91cc99315df1c7

12 files changed:
folly/Makefile.am
folly/io/async/AsyncSSLSocket.cpp
folly/io/async/AsyncSSLSocket.h
folly/io/async/AsyncTransport.h
folly/io/async/OpenSSLPtrTypes.h [deleted file]
folly/io/async/SSLContext.cpp
folly/io/async/SSLContext.h
folly/io/async/ssl/OpenSSLPtrTypes.h [new file with mode: 0644]
folly/io/async/ssl/OpenSSLUtils.cpp [new file with mode: 0644]
folly/io/async/ssl/OpenSSLUtils.h [new file with mode: 0644]
folly/io/async/ssl/TLSDefinitions.h [new file with mode: 0644]
folly/io/async/test/AsyncSSLSocketTest.cpp

index 70de07e..3f443ee 100644 (file)
@@ -229,7 +229,9 @@ nobase_follyinclude_HEADERS = \
        io/async/EventUtil.h \
        io/async/NotificationQueue.h \
        io/async/HHWheelTimer.h \
-       io/async/OpenSSLPtrTypes.h \
+       io/async/ssl/OpenSSLPtrTypes.h \
+       io/async/ssl/OpenSSLUtils.h \
+       io/async/ssl/TLSDefinitions.h \
        io/async/Request.h \
        io/async/SSLContext.h \
        io/async/ScopedEventBaseThread.h \
@@ -397,6 +399,7 @@ libfolly_la_SOURCES = \
        io/async/HHWheelTimer.cpp \
        io/async/test/SocketPair.cpp \
        io/async/test/TimeUtil.cpp \
+       io/async/ssl/OpenSSLUtils.cpp \
        json.cpp \
        detail/MemoryIdler.cpp \
        MacAddress.cpp \
index a022487..97cce2d 100644 (file)
@@ -1634,7 +1634,7 @@ int AsyncSSLSocket::sslVerifyCallback(int preverifyOk,
 
 void AsyncSSLSocket::enableClientHelloParsing()  {
     parseClientHello_ = true;
-    clientHelloInfo_.reset(new ClientHelloInfo());
+    clientHelloInfo_.reset(new ssl::ClientHelloInfo());
 }
 
 void AsyncSSLSocket::resetClientHelloParsing(SSL *ssl)  {
@@ -1711,22 +1711,22 @@ void AsyncSSLSocket::clientHelloParsingCallback(int written,
     if (cursor.totalLength() > 0) {
       uint16_t extensionsLength = cursor.readBE<uint16_t>();
       while (extensionsLength) {
-        TLSExtension extensionType = static_cast<TLSExtension>(
-            cursor.readBE<uint16_t>());
+        ssl::TLSExtension extensionType =
+            static_cast<ssl::TLSExtension>(cursor.readBE<uint16_t>());
         sock->clientHelloInfo_->
           clientHelloExtensions_.push_back(extensionType);
         extensionsLength -= 2;
         uint16_t extensionDataLength = cursor.readBE<uint16_t>();
         extensionsLength -= 2;
 
-        if (extensionType == TLSExtension::SIGNATURE_ALGORITHMS) {
+        if (extensionType == ssl::TLSExtension::SIGNATURE_ALGORITHMS) {
           cursor.skip(2);
           extensionDataLength -= 2;
           while (extensionDataLength) {
-            HashAlgorithm hashAlg = static_cast<HashAlgorithm>(
-                cursor.readBE<uint8_t>());
-            SignatureAlgorithm sigAlg = static_cast<SignatureAlgorithm>(
-                cursor.readBE<uint8_t>());
+            ssl::HashAlgorithm hashAlg =
+                static_cast<ssl::HashAlgorithm>(cursor.readBE<uint8_t>());
+            ssl::SignatureAlgorithm sigAlg =
+                static_cast<ssl::SignatureAlgorithm>(cursor.readBE<uint8_t>());
             extensionDataLength -= 2;
             sock->clientHelloInfo_->
               clientHelloSigAlgs_.emplace_back(hashAlg, sigAlg);
index 4ab0486..2e4d5ba 100644 (file)
 #include <folly/Optional.h>
 #include <folly/String.h>
 #include <folly/io/async/AsyncSocket.h>
-#include <folly/io/async/SSLContext.h>
 #include <folly/io/async/AsyncTimeout.h>
-#include <folly/io/async/OpenSSLPtrTypes.h>
+#include <folly/io/async/SSLContext.h>
 #include <folly/io/async/TimeoutManager.h>
+#include <folly/io/async/ssl/OpenSSLPtrTypes.h>
+#include <folly/io/async/ssl/OpenSSLUtils.h>
+#include <folly/io/async/ssl/TLSDefinitions.h>
 
 #include <folly/Bits.h>
 #include <folly/io/IOBuf.h>
@@ -652,68 +654,8 @@ class AsyncSSLSocket : public virtual AsyncSocket {
       int content_type, const void *buf, size_t len, SSL *ssl, void *arg);
   static const char* getSSLServerNameFromSSL(SSL* ssl);
 
-  // http://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
-  enum class TLSExtension: uint16_t {
-    SERVER_NAME = 0,
-    MAX_FRAGMENT_LENGTH = 1,
-    CLIENT_CERTIFICATE_URL = 2,
-    TRUSTED_CA_KEYS = 3,
-    TRUNCATED_HMAC = 4,
-    STATUS_REQUEST = 5,
-    USER_MAPPING = 6,
-    CLIENT_AUTHZ = 7,
-    SERVER_AUTHZ = 8,
-    CERT_TYPE = 9,
-    SUPPORTED_GROUPS = 10,
-    EC_POINT_FORMATS = 11,
-    SRP = 12,
-    SIGNATURE_ALGORITHMS = 13,
-    USE_SRTP = 14,
-    HEARTBEAT = 15,
-    APPLICATION_LAYER_PROTOCOL_NEGOTIATION = 16,
-    STATUS_REQUEST_V2 = 17,
-    SIGNED_CERTIFICATE_TIMESTAMP = 18,
-    CLIENT_CERTIFICATE_TYPE = 19,
-    SERVER_CERTIFICATE_TYPE = 20,
-    PADDING = 21,
-    ENCRYPT_THEN_MAC = 22,
-    EXTENDED_MASTER_SECRET = 23,
-    SESSION_TICKET = 35,
-    RENEGOTIATION_INFO = 65281
-  };
-
-  // http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18
-  enum class HashAlgorithm: uint8_t {
-    NONE = 0,
-    MD5 = 1,
-    SHA1 = 2,
-    SHA224 = 3,
-    SHA256 = 4,
-    SHA384 = 5,
-    SHA512 = 6
-  };
-
-  // http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16
-  enum class SignatureAlgorithm: uint8_t {
-    ANONYMOUS = 0,
-    RSA = 1,
-    DSA = 2,
-    ECDSA = 3
-  };
-
-  struct ClientHelloInfo {
-    folly::IOBufQueue clientHelloBuf_;
-    uint8_t clientHelloMajorVersion_;
-    uint8_t clientHelloMinorVersion_;
-    std::vector<uint16_t> clientHelloCipherSuites_;
-    std::vector<uint8_t> clientHelloCompressionMethods_;
-    std::vector<TLSExtension> clientHelloExtensions_;
-    std::vector<
-      std::pair<HashAlgorithm, SignatureAlgorithm>> clientHelloSigAlgs_;
-  };
-
   // For unit-tests
-  ClientHelloInfo* getClientHelloInfo() const {
+  ssl::ClientHelloInfo* getClientHelloInfo() const {
     return clientHelloInfo_.get();
   }
 
@@ -737,13 +679,13 @@ class AsyncSSLSocket : public virtual AsyncSocket {
   /**
    * Returns the peer certificate, or nullptr if no peer certificate received.
    */
-  virtual X509_UniquePtr getPeerCert() const override {
+  virtual ssl::X509UniquePtr getPeerCert() const override {
     if (!ssl_) {
       return nullptr;
     }
 
     X509* cert = SSL_get_peer_certificate(ssl_);
-    return X509_UniquePtr(cert);
+    return ssl::X509UniquePtr(cert);
   }
 
   /**
@@ -886,7 +828,7 @@ class AsyncSSLSocket : public virtual AsyncSocket {
 
   bool parseClientHello_{false};
   bool cacheAddrOnFailure_{false};
-  std::unique_ptr<ClientHelloInfo> clientHelloInfo_;
+  std::unique_ptr<ssl::ClientHelloInfo> clientHelloInfo_;
 
   // Time taken to complete the ssl handshake.
   std::chrono::steady_clock::time_point handshakeStartTime_;
index 08ba328..07e6783 100644 (file)
 #include <sys/uio.h>
 
 #include <folly/io/IOBuf.h>
+#include <folly/io/async/AsyncSocketBase.h>
 #include <folly/io/async/DelayedDestruction.h>
 #include <folly/io/async/EventBase.h>
-#include <folly/io/async/AsyncSocketBase.h>
-#include <folly/io/async/OpenSSLPtrTypes.h>
+#include <folly/io/async/ssl/OpenSSLPtrTypes.h>
 
 #include <openssl/ssl.h>
 
@@ -324,7 +324,7 @@ class AsyncTransport : public DelayedDestruction, public AsyncSocketBase {
   /**
    * Get the certificate used to authenticate the peer.
    */
-  virtual X509_UniquePtr getPeerCert() const { return nullptr; }
+  virtual ssl::X509UniquePtr getPeerCert() const { return nullptr; }
 
   /**
    * @return True iff end of record tracking is enabled
diff --git a/folly/io/async/OpenSSLPtrTypes.h b/folly/io/async/OpenSSLPtrTypes.h
deleted file mode 100644 (file)
index 57d73b8..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2016 Facebook, Inc.
- *
- * Licensed 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.
- */
-
-#pragma once
-
-#include <folly/Memory.h>
-#include <openssl/ssl.h>
-
-namespace folly {
-
-using X509_deleter = static_function_deleter<X509, &X509_free>;
-using X509_UniquePtr = std::unique_ptr<X509, X509_deleter>;
-
-using EVP_PKEY_deleter =
-    folly::static_function_deleter<EVP_PKEY, &EVP_PKEY_free>;
-using EVP_PKEY_UniquePtr = std::unique_ptr<EVP_PKEY, EVP_PKEY_deleter>;
-
-using SSL_deleter = folly::static_function_deleter<SSL, &SSL_free>;
-using SSL_UniquePtr = std::unique_ptr<SSL, SSL_deleter>;
-}
index 637a0e1..4706ac6 100644 (file)
@@ -24,7 +24,6 @@
 #include <folly/Format.h>
 #include <folly/Memory.h>
 #include <folly/SpinLock.h>
-#include <folly/io/async/OpenSSLPtrTypes.h>
 
 // ---------------------------------------------------------------------
 // SSLContext implementation
@@ -45,9 +44,6 @@ std::mutex& initMutex() {
   return m;
 }
 
-inline void BIO_free_fb(BIO* bio) { CHECK_EQ(1, BIO_free(bio)); }
-using BIO_deleter = folly::static_function_deleter<BIO, &BIO_free_fb>;
-
 } // anonymous namespace
 
 #ifdef OPENSSL_NPN_NEGOTIATED
@@ -196,7 +192,7 @@ void SSLContext::loadCertificateFromBufferPEM(folly::StringPiece cert) {
     throw std::invalid_argument("loadCertificate: <cert> is nullptr");
   }
 
-  std::unique_ptr<BIO, BIO_deleter> bio(BIO_new(BIO_s_mem()));
+  ssl::BioUniquePtr bio(BIO_new(BIO_s_mem()));
   if (bio == nullptr) {
     throw std::runtime_error("BIO_new: " + getErrors());
   }
@@ -206,7 +202,8 @@ void SSLContext::loadCertificateFromBufferPEM(folly::StringPiece cert) {
     throw std::runtime_error("BIO_write: " + getErrors());
   }
 
-  X509_UniquePtr x509(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
+  ssl::X509UniquePtr x509(
+      PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
   if (x509 == nullptr) {
     throw std::runtime_error("PEM_read_bio_X509: " + getErrors());
   }
@@ -235,7 +232,7 @@ void SSLContext::loadPrivateKeyFromBufferPEM(folly::StringPiece pkey) {
     throw std::invalid_argument("loadPrivateKey: <pkey> is nullptr");
   }
 
-  std::unique_ptr<BIO, BIO_deleter> bio(BIO_new(BIO_s_mem()));
+  ssl::BioUniquePtr bio(BIO_new(BIO_s_mem()));
   if (bio == nullptr) {
     throw std::runtime_error("BIO_new: " + getErrors());
   }
@@ -245,7 +242,7 @@ void SSLContext::loadPrivateKeyFromBufferPEM(folly::StringPiece pkey) {
     throw std::runtime_error("BIO_write: " + getErrors());
   }
 
-  EVP_PKEY_UniquePtr key(
+  ssl::EvpPkeyUniquePtr key(
       PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr));
   if (key == nullptr) {
     throw std::runtime_error("PEM_read_bio_PrivateKey: " + getErrors());
@@ -799,83 +796,4 @@ operator<<(std::ostream& os, const PasswordCollector& collector) {
   return os;
 }
 
-bool OpenSSLUtils::getPeerAddressFromX509StoreCtx(X509_STORE_CTX* ctx,
-                                                  sockaddr_storage* addrStorage,
-                                                  socklen_t* addrLen) {
-  // Grab the ssl idx and then the ssl object so that we can get the peer
-  // name to compare against the ips in the subjectAltName
-  auto sslIdx = SSL_get_ex_data_X509_STORE_CTX_idx();
-  auto ssl =
-    reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, sslIdx));
-  int fd = SSL_get_fd(ssl);
-  if (fd < 0) {
-    LOG(ERROR) << "Inexplicably couldn't get fd from SSL";
-    return false;
-  }
-
-  *addrLen = sizeof(*addrStorage);
-  if (getpeername(fd, reinterpret_cast<sockaddr*>(addrStorage), addrLen) != 0) {
-    PLOG(ERROR) << "Unable to get peer name";
-    return false;
-  }
-  CHECK(*addrLen <= sizeof(*addrStorage));
-  return true;
-}
-
-bool OpenSSLUtils::validatePeerCertNames(X509* cert,
-                                         const sockaddr* addr,
-                                         socklen_t /* addrLen */) {
-  // Try to extract the names within the SAN extension from the certificate
-  auto altNames =
-    reinterpret_cast<STACK_OF(GENERAL_NAME)*>(
-        X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
-  SCOPE_EXIT {
-    if (altNames != nullptr) {
-      sk_GENERAL_NAME_pop_free(altNames, GENERAL_NAME_free);
-    }
-  };
-  if (altNames == nullptr) {
-    LOG(WARNING) << "No subjectAltName provided and we only support ip auth";
-    return false;
-  }
-
-  const sockaddr_in* addr4 = nullptr;
-  const sockaddr_in6* addr6 = nullptr;
-  if (addr != nullptr) {
-    if (addr->sa_family == AF_INET) {
-      addr4 = reinterpret_cast<const sockaddr_in*>(addr);
-    } else if (addr->sa_family == AF_INET6) {
-      addr6 = reinterpret_cast<const sockaddr_in6*>(addr);
-    } else {
-      LOG(FATAL) << "Unsupported sockaddr family: " << addr->sa_family;
-    }
-  }
-
-
-  for (int i = 0; i < sk_GENERAL_NAME_num(altNames); i++) {
-    auto name = sk_GENERAL_NAME_value(altNames, i);
-    if ((addr4 != nullptr || addr6 != nullptr) && name->type == GEN_IPADD) {
-      // Extra const-ness for paranoia
-      unsigned char const * const rawIpStr = name->d.iPAddress->data;
-      int const rawIpLen = name->d.iPAddress->length;
-
-      if (rawIpLen == 4 && addr4 != nullptr) {
-        if (::memcmp(rawIpStr, &addr4->sin_addr, rawIpLen) == 0) {
-          return true;
-        }
-      } else if (rawIpLen == 16 && addr6 != nullptr) {
-        if (::memcmp(rawIpStr, &addr6->sin6_addr, rawIpLen) == 0) {
-          return true;
-        }
-      } else if (rawIpLen != 4 && rawIpLen != 16) {
-        LOG(WARNING) << "Unexpected IP length: " << rawIpLen;
-      }
-    }
-  }
-
-  LOG(WARNING) << "Unable to match client cert against alt name ip";
-  return false;
-}
-
-
 } // folly
index 03d4175..d63031a 100644 (file)
@@ -38,6 +38,8 @@
 
 #include <folly/Random.h>
 #include <folly/Range.h>
+#include <folly/io/async/ssl/OpenSSLPtrTypes.h>
+#include <folly/io/async/ssl/OpenSSLUtils.h>
 
 namespace folly {
 
@@ -549,40 +551,5 @@ typedef std::shared_ptr<SSLContext> SSLContextPtr;
 
 std::ostream& operator<<(std::ostream& os, const folly::PasswordCollector& collector);
 
-class OpenSSLUtils {
- public:
-  /**
-   * Validate that the peer certificate's common name or subject alt names
-   * match what we expect.  Currently this only checks for IPs within
-   * subject alt names but it could easily be expanded to check common name
-   * and hostnames as well.
-   *
-   * @param cert    X509* peer certificate
-   * @param addr    sockaddr object containing sockaddr to verify
-   * @param addrLen length of sockaddr as returned by getpeername or accept
-   * @return true iff a subject altname IP matches addr
-   */
-  // TODO(agartrell): Add support for things like common name when
-  // necessary.
-  static bool validatePeerCertNames(X509* cert,
-                                    const sockaddr* addr,
-                                    socklen_t addrLen);
-
-  /**
-   * Get the peer socket address from an X509_STORE_CTX*.  Unlike the
-   * accept, getsockname, getpeername, etc family of operations, addrLen's
-   * initial value is ignored and reset.
-   *
-   * @param ctx         Context from which to retrieve peer sockaddr
-   * @param addrStorage out param for address
-   * @param addrLen     out param for length of address
-   * @return true on success, false on failure
-   */
-  static bool getPeerAddressFromX509StoreCtx(X509_STORE_CTX* ctx,
-                                             sockaddr_storage* addrStorage,
-                                             socklen_t* addrLen);
-
-};
-
 
 } // folly
diff --git a/folly/io/async/ssl/OpenSSLPtrTypes.h b/folly/io/async/ssl/OpenSSLPtrTypes.h
new file mode 100644 (file)
index 0000000..4cee332
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed 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.
+ */
+
+#pragma once
+
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+#include <openssl/ssl.h>
+#ifndef OPENSSL_NO_EC
+#include <openssl/ec.h>
+#endif
+#include <folly/Memory.h>
+#include <openssl/bio.h>
+#include <openssl/x509.h>
+
+namespace folly {
+namespace ssl {
+
+// X509
+using X509Deleter = folly::static_function_deleter<X509, &X509_free>;
+using X509UniquePtr = std::unique_ptr<X509, X509Deleter>;
+using X509StoreCtxDeleter =
+    folly::static_function_deleter<X509_STORE_CTX, &X509_STORE_CTX_free>;
+using X509StoreCtxUniquePtr =
+    std::unique_ptr<X509_STORE_CTX, X509StoreCtxDeleter>;
+
+// EVP
+using EvpPkeyDel = folly::static_function_deleter<EVP_PKEY, &EVP_PKEY_free>;
+using EvpPkeyUniquePtr = std::unique_ptr<EVP_PKEY, EvpPkeyDel>;
+using EvpPkeySharedPtr = std::shared_ptr<EVP_PKEY>;
+
+// No EVP_PKEY_CTX <= 0.9.8b
+#if OPENSSL_VERSION_NUMBER >= 0x10000002L
+using EvpPkeyCtxDeleter =
+    folly::static_function_deleter<EVP_PKEY_CTX, &EVP_PKEY_CTX_free>;
+using EvpPkeyCtxUniquePtr = std::unique_ptr<EVP_PKEY_CTX, EvpPkeyCtxDeleter>;
+#else
+struct EVP_PKEY_CTX;
+#endif
+using EvpMdCtxDeleter =
+    folly::static_function_deleter<EVP_MD_CTX, &EVP_MD_CTX_destroy>;
+using EvpMdCtxUniquePtr = std::unique_ptr<EVP_MD_CTX, EvpMdCtxDeleter>;
+
+// BIO
+using BioDeleter = folly::static_function_deleter<BIO, &BIO_vfree>;
+using BioUniquePtr = std::unique_ptr<BIO, BioDeleter>;
+using BioChainDeleter = folly::static_function_deleter<BIO, &BIO_free_all>;
+using BioChainUniquePtr = std::unique_ptr<BIO, BioChainDeleter>;
+inline void BIO_free_fb(BIO* bio) { CHECK_EQ(1, BIO_free(bio)); }
+using BioDeleterFb = folly::static_function_deleter<BIO, &BIO_free_fb>;
+using BioUniquePtrFb = std::unique_ptr<BIO, BioDeleterFb>;
+
+// RSA and EC
+using RsaDeleter = folly::static_function_deleter<RSA, &RSA_free>;
+using RsaUniquePtr = std::unique_ptr<RSA, RsaDeleter>;
+#ifndef OPENSSL_NO_EC
+using EcKeyDeleter = folly::static_function_deleter<EC_KEY, &EC_KEY_free>;
+using EcKeyUniquePtr = std::unique_ptr<EC_KEY, EcKeyDeleter>;
+#endif
+
+// SSL and SSL_CTX
+using SSLDeleter = folly::static_function_deleter<SSL, &SSL_free>;
+using SSLUniquePtr = std::unique_ptr<SSL, SSLDeleter>;
+}
+}
diff --git a/folly/io/async/ssl/OpenSSLUtils.cpp b/folly/io/async/ssl/OpenSSLUtils.cpp
new file mode 100644 (file)
index 0000000..c276f6d
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed 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.
+ */
+#include <folly/io/async/ssl/OpenSSLUtils.h>
+#include <folly/ScopeGuard.h>
+
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include <glog/logging.h>
+
+namespace folly {
+namespace ssl {
+
+bool OpenSSLUtils::getPeerAddressFromX509StoreCtx(X509_STORE_CTX* ctx,
+                                                  sockaddr_storage* addrStorage,
+                                                  socklen_t* addrLen) {
+  // Grab the ssl idx and then the ssl object so that we can get the peer
+  // name to compare against the ips in the subjectAltName
+  auto sslIdx = SSL_get_ex_data_X509_STORE_CTX_idx();
+  auto ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(ctx, sslIdx));
+  int fd = SSL_get_fd(ssl);
+  if (fd < 0) {
+    LOG(ERROR) << "Inexplicably couldn't get fd from SSL";
+    return false;
+  }
+
+  *addrLen = sizeof(*addrStorage);
+  if (getpeername(fd, reinterpret_cast<sockaddr*>(addrStorage), addrLen) != 0) {
+    PLOG(ERROR) << "Unable to get peer name";
+    return false;
+  }
+  CHECK(*addrLen <= sizeof(*addrStorage));
+  return true;
+}
+
+bool OpenSSLUtils::validatePeerCertNames(X509* cert,
+                                         const sockaddr* addr,
+                                         socklen_t /* addrLen */) {
+  // Try to extract the names within the SAN extension from the certificate
+  auto altNames = reinterpret_cast<STACK_OF(GENERAL_NAME)*>(
+      X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
+  SCOPE_EXIT {
+    if (altNames != nullptr) {
+      sk_GENERAL_NAME_pop_free(altNames, GENERAL_NAME_free);
+    }
+  };
+  if (altNames == nullptr) {
+    LOG(WARNING) << "No subjectAltName provided and we only support ip auth";
+    return false;
+  }
+
+  const sockaddr_in* addr4 = nullptr;
+  const sockaddr_in6* addr6 = nullptr;
+  if (addr != nullptr) {
+    if (addr->sa_family == AF_INET) {
+      addr4 = reinterpret_cast<const sockaddr_in*>(addr);
+    } else if (addr->sa_family == AF_INET6) {
+      addr6 = reinterpret_cast<const sockaddr_in6*>(addr);
+    } else {
+      LOG(FATAL) << "Unsupported sockaddr family: " << addr->sa_family;
+    }
+  }
+
+  for (int i = 0; i < sk_GENERAL_NAME_num(altNames); i++) {
+    auto name = sk_GENERAL_NAME_value(altNames, i);
+    if ((addr4 != nullptr || addr6 != nullptr) && name->type == GEN_IPADD) {
+      // Extra const-ness for paranoia
+      unsigned char const* const rawIpStr = name->d.iPAddress->data;
+      int const rawIpLen = name->d.iPAddress->length;
+
+      if (rawIpLen == 4 && addr4 != nullptr) {
+        if (::memcmp(rawIpStr, &addr4->sin_addr, rawIpLen) == 0) {
+          return true;
+        }
+      } else if (rawIpLen == 16 && addr6 != nullptr) {
+        if (::memcmp(rawIpStr, &addr6->sin6_addr, rawIpLen) == 0) {
+          return true;
+        }
+      } else if (rawIpLen != 4 && rawIpLen != 16) {
+        LOG(WARNING) << "Unexpected IP length: " << rawIpLen;
+      }
+    }
+  }
+
+  LOG(WARNING) << "Unable to match client cert against alt name ip";
+  return false;
+}
+
+} // ssl
+} // folly
diff --git a/folly/io/async/ssl/OpenSSLUtils.h b/folly/io/async/ssl/OpenSSLUtils.h
new file mode 100644 (file)
index 0000000..5d232c4
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed 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.
+ */
+#pragma once
+
+#include <openssl/x509v3.h>
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+namespace folly {
+namespace ssl {
+
+class OpenSSLUtils {
+ public:
+  /**
+   * Validate that the peer certificate's common name or subject alt names
+   * match what we expect.  Currently this only checks for IPs within
+   * subject alt names but it could easily be expanded to check common name
+   * and hostnames as well.
+   *
+   * @param cert    X509* peer certificate
+   * @param addr    sockaddr object containing sockaddr to verify
+   * @param addrLen length of sockaddr as returned by getpeername or accept
+   * @return true iff a subject altname IP matches addr
+   */
+  // TODO(agartrell): Add support for things like common name when
+  // necessary.
+  static bool validatePeerCertNames(X509* cert,
+                                    const sockaddr* addr,
+                                    socklen_t addrLen);
+
+  /**
+   * Get the peer socket address from an X509_STORE_CTX*.  Unlike the
+   * accept, getsockname, getpeername, etc family of operations, addrLen's
+   * initial value is ignored and reset.
+   *
+   * @param ctx         Context from which to retrieve peer sockaddr
+   * @param addrStorage out param for address
+   * @param addrLen     out param for length of address
+   * @return true on success, false on failure
+   */
+  static bool getPeerAddressFromX509StoreCtx(X509_STORE_CTX* ctx,
+                                             sockaddr_storage* addrStorage,
+                                             socklen_t* addrLen);
+};
+
+} // ssl
+} // folly
diff --git a/folly/io/async/ssl/TLSDefinitions.h b/folly/io/async/ssl/TLSDefinitions.h
new file mode 100644 (file)
index 0000000..cf803d1
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * Licensed 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.
+ */
+
+#pragma once
+
+#include <folly/io/Cursor.h>
+#include <folly/io/IOBuf.h>
+#include <map>
+#include <openssl/ssl.h>
+#include <openssl/tls1.h>
+#include <vector>
+
+namespace folly {
+namespace ssl {
+
+// http://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
+enum class TLSExtension : uint16_t {
+  SERVER_NAME = 0,
+  MAX_FRAGMENT_LENGTH = 1,
+  CLIENT_CERTIFICATE_URL = 2,
+  TRUSTED_CA_KEYS = 3,
+  TRUNCATED_HMAC = 4,
+  STATUS_REQUEST = 5,
+  USER_MAPPING = 6,
+  CLIENT_AUTHZ = 7,
+  SERVER_AUTHZ = 8,
+  CERT_TYPE = 9,
+  SUPPORTED_GROUPS = 10,
+  EC_POINT_FORMATS = 11,
+  SRP = 12,
+  SIGNATURE_ALGORITHMS = 13,
+  USE_SRTP = 14,
+  HEARTBEAT = 15,
+  APPLICATION_LAYER_PROTOCOL_NEGOTIATION = 16,
+  STATUS_REQUEST_V2 = 17,
+  SIGNED_CERTIFICATE_TIMESTAMP = 18,
+  CLIENT_CERTIFICATE_TYPE = 19,
+  SERVER_CERTIFICATE_TYPE = 20,
+  PADDING = 21,
+  ENCRYPT_THEN_MAC = 22,
+  EXTENDED_MASTER_SECRET = 23,
+  SESSION_TICKET = 35,
+  // Facebook-specific, not IANA assigned yet
+  TLS_CACHED_INFO_FB = 60001,
+  // End Facebook-specific
+  RENEGOTIATION_INFO = 65281
+};
+
+// http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-18
+enum class HashAlgorithm : uint8_t {
+  NONE = 0,
+  MD5 = 1,
+  SHA1 = 2,
+  SHA224 = 3,
+  SHA256 = 4,
+  SHA384 = 5,
+  SHA512 = 6
+};
+
+// http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-16
+enum class SignatureAlgorithm : uint8_t {
+  ANONYMOUS = 0,
+  RSA = 1,
+  DSA = 2,
+  ECDSA = 3
+};
+
+struct ClientHelloInfo {
+  folly::IOBufQueue clientHelloBuf_;
+  uint8_t clientHelloMajorVersion_;
+  uint8_t clientHelloMinorVersion_;
+  std::vector<uint16_t> clientHelloCipherSuites_;
+  std::vector<uint8_t> clientHelloCompressionMethods_;
+  std::vector<TLSExtension> clientHelloExtensions_;
+  std::vector<std::pair<HashAlgorithm, SignatureAlgorithm>> clientHelloSigAlgs_;
+};
+
+} // ssl
+} // folly
index 87909f0..038f4f3 100644 (file)
@@ -57,9 +57,6 @@ const char* testCA = "folly/io/async/test/certs/ca-cert.pem";
 constexpr size_t SSLClient::kMaxReadBufferSz;
 constexpr size_t SSLClient::kMaxReadsPerEvent;
 
-inline void BIO_free_fb(BIO* bio) { CHECK_EQ(1, BIO_free(bio)); }
-using BIO_deleter = folly::static_function_deleter<BIO, &BIO_free_fb>;
-
 TestSSLServer::TestSSLServer(SSLServerAcceptCallbackBase* acb)
     : ctx_(new folly::SSLContext),
       acb_(acb),
@@ -1384,15 +1381,15 @@ TEST(AsyncSSLSocketTest, LoadCertFromMemory) {
   auto cert = getFileAsBuf(testCert);
   auto key = getFileAsBuf(testKey);
 
-  std::unique_ptr<BIO, BIO_deleter> certBio(BIO_new(BIO_s_mem()));
+  ssl::BioUniquePtr certBio(BIO_new(BIO_s_mem()));
   BIO_write(certBio.get(), cert.data(), cert.size());
-  std::unique_ptr<BIO, BIO_deleter> keyBio(BIO_new(BIO_s_mem()));
+  ssl::BioUniquePtr keyBio(BIO_new(BIO_s_mem()));
   BIO_write(keyBio.get(), key.data(), key.size());
 
   // Create SSL structs from buffers to get properties
-  X509_UniquePtr certStruct(
+  ssl::X509UniquePtr certStruct(
       PEM_read_bio_X509(certBio.get(), nullptr, nullptr, nullptr));
-  EVP_PKEY_UniquePtr keyStruct(
+  ssl::EvpPkeyUniquePtr keyStruct(
       PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr));
   certBio = nullptr;
   keyBio = nullptr;
@@ -1407,7 +1404,7 @@ TEST(AsyncSSLSocketTest, LoadCertFromMemory) {
   ctx->loadCertificateFromBufferPEM(cert);
   ctx->loadTrustedCertificates(testCA);
 
-  SSL_UniquePtr ssl(ctx->createSSL());
+  ssl::SSLUniquePtr ssl(ctx->createSSL());
 
   auto newCert = SSL_get_certificate(ssl.get());
   auto newKey = SSL_get_privatekey(ssl.get());