/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * 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/Range.h>
#include <folly/io/async/AsyncSocketException.h>
#include <folly/net/NetworkSocket.h>
#include <folly/portability/OpenSSL.h>
#include <folly/portability/Sockets.h>
#include <folly/ssl/OpenSSLPtrTypes.h>
#include <folly/ssl/SSLSession.h>

namespace folly {
namespace ssl {

class OpenSSLUtils {
 public:
  /*
   * Get the TLS Session Master Key used to generate the TLS key material
   *
   * @param session ssl session
   * @param keyOut destination for the master key, the buffer must be at least
   * 48 bytes
   * @return true if the master key is available (>= TLS1) and the output buffer
   * large enough
   */
  static bool getTLSMasterKey(
      const SSL_SESSION* session, MutableByteRange keyOut);
  static bool getTLSMasterKey(
      const std::shared_ptr<SSLSession> session, MutableByteRange keyOut);

  /*
   * Get the TLS Client Random used to generate the TLS key material
   *
   * @param ssl
   * @param randomOut destination for the client random, the buffer must be at
   * least 32 bytes
   * @return true if the client random is available (>= TLS1) and the output
   * buffer large enough
   */
  static bool getTLSClientRandom(const SSL* ssl, MutableByteRange randomOut);

  /**
   * 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);

  /**
   * Get a stringified cipher name (e.g., ECDHE-ECDSA-CHACHA20-POLY1305) given
   * the 2-byte code (e.g., 0xcca9) for the cipher. The name conversion only
   * works for the ciphers built into the linked OpenSSL library
   *
   * @param cipherCode      A 16-bit IANA cipher code (machine endianness)
   * @return Cipher name, or empty if the code is not found
   */
  static const std::string& getCipherName(uint16_t cipherCode);

  /**
   * Set the 'initial_ctx' SSL_CTX* inside an SSL. The initial_ctx is used to
   * point to the SSL_CTX on which servername callback and session callbacks,
   * as well as session caching stats are set. If we want to enforce SSL_CTX
   * thread-based ownership (e.g., thread-local SSL_CTX) in the application, we
   * need to also set/reset the initial_ctx when we call SSL_set_SSL_CTX.
   *
   * @param ssl      SSL pointer
   * @param ctx      SSL_CTX pointer
   * @return Cipher name, or empty if the code is not found
   */
  static void setSSLInitialCtx(SSL* ssl, SSL_CTX* ctx);
  static SSL_CTX* getSSLInitialCtx(SSL* ssl);

  /**
   * Get the common name out of a cert.  Return empty if x509 is null.
   */
  static std::string getCommonName(X509* x509);

  /**
   * Get a list of subject names corresponding to each certificate in a
   * PEM encoded file.
   *
   * @param filename   Path to a file containing PEM encoded X509 certificates.
   * @return A vector of X509_NAMEs corresponding to the subject of each
   *         certificate.
   * @throws std::exception For any errors encountered while reading in
   *                        certificates from the file.
   */
  static std::vector<X509NameUniquePtr> subjectNamesInPEMFile(
      const char* filename);

  /**
   * Get a list of subject names corresponding to each certificate in a
   * buffer containing PEM data.
   *
   * @param buffer  A contiguous region of memory containing PEM encoded
   *                certificates.
   * @return A vector of X509_NAMEs corresponding to the subject of each
   *         certificate.
   * @throws std::exception For any errors encountered while reading in
   *                        certificates from the file.
   */
  static std::vector<X509NameUniquePtr> subjectNamesInPEMBuffer(
      folly::ByteRange buffer);

  /**
   * Wrappers for BIO operations that may be different across different
   * versions/flavors of OpenSSL (including forks like BoringSSL)
   */
  static BioMethodUniquePtr newSocketBioMethod();
  static bool setCustomBioReadMethod(
      BIO_METHOD* bioMeth, int (*meth)(BIO*, char*, int));
  static bool setCustomBioWriteMethod(
      BIO_METHOD* bioMeth, int (*meth)(BIO*, const char*, int));
  static int getBioShouldRetryWrite(int ret);
  static void setBioAppData(BIO* b, void* ptr);
  static void* getBioAppData(BIO* b);
  static NetworkSocket getBioFd(BIO* b);
  static void setBioFd(BIO* b, NetworkSocket fd, int flags);
  static std::string encodeALPNString(
      const std::vector<std::string>& supported_protocols);
};
} // namespace ssl
} // namespace folly
