text: // 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.
  
  #pragma once
  
  #include <openssl/err.h>
  #include <openssl/pem.h>
  #include <openssl/ssl.h>
  #include <openssl/x509.h>
  
  #include <functional>
  #include <memory>
  #include <ostream>
  #include <string>
  
  #include <glog/logging.h>
  
  #include "kudu/gutil/port.h"
  #include "kudu/util/status.h"
  
  namespace kudu {
  namespace security {
  namespace internal {
  struct ScopedCheckNoPendingSSLErrors;
  }  // namespace internal
  }  // namespace security
  }  // namespace kudu
  
  // Forward declarations for the OpenSSL typedefs.
  typedef struct X509_req_st X509_REQ;
  typedef struct bio_st BIO;
  typedef struct evp_pkey_st EVP_PKEY;
  typedef struct ssl_ctx_st SSL_CTX;
  typedef struct ssl_st SSL;
  typedef struct x509_st X509;
  
  #define OPENSSL_CHECK_OK(call) \
    CHECK_GT((call), 0)
  
  #define OPENSSL_RET_NOT_OK(call, msg) \
    if ((call) <= 0) { \
      return Status::RuntimeError((msg), security::GetOpenSSLErrors()); \
    }
  
  #define OPENSSL_RET_IF_NULL(call, msg) \
    if ((call) == nullptr) { \
      return Status::RuntimeError((msg), security::GetOpenSSLErrors()); \
    }
  
  // Scoped helper which DCHECKs that on both scope entry and exit, there are no
  // pending OpenSSL errors for the current thread.
  //
  // This allows us to avoid calling ERR_clear_error() defensively before every
  // OpenSSL call, but rather call it only when we get an error code indicating
  // there may be some pending error.
  //
  // Example usage:
  //
  //    void MyFunc() {
  //      SCOPED_OPENSSL_NO_PENDING_ERRORS;
  //      ... use OpenSSL APIs ...
  //    }
  #define SCOPED_OPENSSL_NO_PENDING_ERRORS \
    kudu::security::internal::ScopedCheckNoPendingSSLErrors _no_ssl_errors(__PRETTY_FUNCTION__)
  
  namespace kudu {
  namespace security {
  
  using PasswordCallback = std::function<std::string(void)>;
  
  // Disable initialization of OpenSSL. Must be called before
  // any call to InitializeOpenSSL().
  Status DisableOpenSSLInitialization() WARN_UNUSED_RESULT;
  
  // Initializes static state required by the OpenSSL library.
  // This is a no-op if DisableOpenSSLInitialization() has been called.
  //
  // Safe to call multiple times.
  void InitializeOpenSSL();
  
  // Fetches errors from the OpenSSL error error queue, and stringifies them.
  //
  // The error queue will be empty after this method returns.
  //
  // See man(3) ERR_get_err for more discussion.
  std::string GetOpenSSLErrors();
  
  // Returns a string representation of the provided error code, which must be
  // from a prior call to the SSL_get_error function.
  //
  // If necessary, the OpenSSL error queue may be inspected and emptied as part of
  // this call, and/or 'errno' may be inspected. As a result, this method should
  // only be used directly after the error occurs, and from the same thread.
  //
  // See man(3) SSL_get_error for more discussion.
  std::string GetSSLErrorDescription(int error_code);
  
  // Runs the shell command 'cmd' which should give a password to a private key file
  // as the output.
  //
  // 'password' is populated with the password string if the command was a success.
  // An error Status object is returned otherwise.
  Status GetPasswordFromShellCommand(const std::string& cmd, std::string* password);
  
  // Retrieve the negotiated TLS protocol version. Only valid after the
  // TLS handshake is complete.
  std::string GetProtocolName(const SSL* ssl);
  
  // Retrive the description of the negotiated TLS cipher.
  // Only valid to call after the handshake is complete.
  std::string GetCipherDescription(const SSL* ssl);
  
  // A generic wrapper for OpenSSL structures.
  template <typename T>
  using c_unique_ptr = std::unique_ptr<T, std::function<void(T*)>>;
  
  // For each SSL type, the Traits class provides the important OpenSSL
  // API functions.
  template<typename SSL_TYPE>
  struct SslTypeTraits {};
  
  template<> struct SslTypeTraits<X509> {
    static constexpr auto kFreeFunc = &X509_free;
    static constexpr auto kReadPemFunc = &PEM_read_bio_X509;
    static constexpr auto kReadDerFunc = &d2i_X509_bio;
    static constexpr auto kWritePemFunc = &PEM_write_bio_X509;
    static constexpr auto kWriteDerFunc = &i2d_X509_bio;
  };
  
  // SslTypeTraits functions for Type STACK_OF(X509)
  STACK_OF(X509)* PEM_read_STACK_OF_X509(BIO* bio, void* /* unused */,
      pem_password_cb* /* unused */, void* /* unused */);
  int PEM_write_STACK_OF_X509(BIO* bio, STACK_OF(X509)* obj);
  STACK_OF(X509)* DER_read_STACK_OF_X509(BIO* bio, void* /* unused */);
  int DER_write_STACK_OF_X509(BIO* bio, STACK_OF(X509)* obj);
  void free_STACK_OF_X509(STACK_OF(X509)* sk);
  
  template<> struct SslTypeTraits<STACK_OF(X509)> {
    static constexpr auto kFreeFunc = &free_STACK_OF_X509;
    static constexpr auto kReadPemFunc = &PEM_read_STACK_OF_X509;
    static constexpr auto kReadDerFunc = &DER_read_STACK_OF_X509;
    static constexpr auto kWritePemFunc = &PEM_write_STACK_OF_X509;
    static constexpr auto kWriteDerFunc = &DER_write_STACK_OF_X509;
  };
  template<> struct SslTypeTraits<X509_EXTENSION> {
    static constexpr auto kFreeFunc = &X509_EXTENSION_free;
  };
  template<> struct SslTypeTraits<X509_REQ> {
    static constexpr auto kFreeFunc = &X509_REQ_free;
    static constexpr auto kReadPemFunc = &PEM_read_bio_X509_REQ;
    static constexpr auto kReadDerFunc = &d2i_X509_REQ_bio;
    static constexpr auto kWritePemFunc = &PEM_write_bio_X509_REQ;
    static constexpr auto kWriteDerFunc = &i2d_X509_REQ_bio;
  };
  template<> struct SslTypeTraits<EVP_PKEY> {
    static constexpr auto kFreeFunc = &EVP_PKEY_free;
  };
  template<> struct SslTypeTraits<SSL_CTX> {
    static constexpr auto kFreeFunc = &SSL_CTX_free;
  };
  template<> struct SslTypeTraits<BIO> {
    static constexpr auto kFreeFunc = &BIO_free;
  };
  
  template<typename SSL_TYPE, typename Traits = SslTypeTraits<SSL_TYPE>>
  c_unique_ptr<SSL_TYPE> ssl_make_unique(SSL_TYPE* d) {
    return {d, Traits::kFreeFunc};
  }
  
  // Acceptable formats for keys, X509 certificates and X509 CSRs.
  enum class DataFormat {
    DER = 0,    // DER/ASN1 format (binary): for representing object on the wire
    PEM = 1,    // PEM format (ASCII): for storing on filesystem, printing, etc.
  };
  
  // Data format representation as a string.
  const std::string& DataFormatToString(DataFormat fmt);
  
  // Template wrapper for dynamically allocated entities with custom deleter.
  // Mostly, using it for xxx_st types from the OpenSSL crypto library.
  template<typename Type>
  class RawDataWrapper {
   public:
    typedef Type RawDataType;
  
    RawDataType* GetRawData() const {
      return data_.get();
    }
  
    void AdoptRawData(RawDataType* d) {
      data_ = ssl_make_unique(d);
    }
  
   protected:
    c_unique_ptr<RawDataType> data_;
  };
  
  
  namespace internal {
  
  // Implementation of SCOPED_OPENSSL_NO_PENDING_ERRORS. Use the macro form
  // instead of directly instantiating the implementation class.
  struct ScopedCheckNoPendingSSLErrors {
   public:
    explicit ScopedCheckNoPendingSSLErrors(const char* func)
        : func_(func) {
      DCHECK_EQ(ERR_peek_error(), 0)
          << "Expected no pending OpenSSL errors on " << func_
          << " entry, but had: " << GetOpenSSLErrors();
    }
    ~ScopedCheckNoPendingSSLErrors() {
      DCHECK_EQ(ERR_peek_error(), 0)
          << "Expected no pending OpenSSL errors on " << func_
          << " exit, but had: " << GetOpenSSLErrors();
    }
  
   private:
    const char* const func_;
  };
  
  } // namespace internal
  } // namespace security
  } // namespace kudu
  