Newer
Older
XinYang_IOS / Pods / OpenVPNAdapter / Sources / OpenVPN3 / openvpn / openssl / pki / extpki.hpp
@zhangfeng zhangfeng on 7 Dec 2023 11 KB 1.8.0
//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2020 OpenVPN Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License Version 3
//    as published by the Free Software Foundation.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

#pragma once

#include <openssl/rsa.h>
#include <openssl/evp.h>

#include <openssl/ec.h>
#include <openssl/ecdsa.h>

#include <openvpn/pki/epkibase.hpp>
#include <openvpn/ssl/sslapi.hpp>

#include <openvpn/openssl/compat.hpp>

namespace openvpn {
  using ssl_external_pki = SSLFactoryAPI::ssl_external_pki;

  class ExternalPKIImpl
  {
  public:
    virtual ~ExternalPKIImpl() = default;
  };

  class ExternalPKIRsaImpl : public ExternalPKIImpl
  {
  public:
    ExternalPKIRsaImpl(SSL_CTX* ssl_ctx, ::X509* cert, ExternalPKIBase* external_pki_arg)
      : external_pki(external_pki_arg), n_errors(0)
    {
      RSA* rsa = nullptr;
      RSA* pub_rsa = nullptr;
      RSA_METHOD* rsa_meth = nullptr;
      const char* errtext = "";

      /* allocate custom RSA method object */
      rsa_meth = RSA_meth_new("OpenSSLContext::ExternalPKIRsaImpl private key RSA Method", RSA_METHOD_FLAG_NO_CHECK);

      RSA_meth_set_pub_enc(rsa_meth, rsa_pub_enc);
      RSA_meth_set_pub_dec(rsa_meth, rsa_pub_dec);
      RSA_meth_set_priv_enc(rsa_meth, rsa_priv_enc);
      RSA_meth_set_priv_dec(rsa_meth, rsa_priv_dec);
      RSA_meth_set_init(rsa_meth, nullptr);
      RSA_meth_set_finish(rsa_meth, rsa_finish);
      RSA_meth_set0_app_data(rsa_meth, this);


      /* get the public key */
      if (X509_get0_pubkey(cert) == nullptr) /* nullptr before SSL_CTX_use_certificate() is called */
	{
	  errtext = "pkey is NULL";
	  goto err;
	}

      if (EVP_PKEY_id(X509_get0_pubkey(cert)) != EVP_PKEY_RSA)
	{
	  errtext = "pkey is not RSA";
	  goto err;
	}
      pub_rsa = EVP_PKEY_get0_RSA(X509_get0_pubkey(cert));

      /* allocate RSA object */
      rsa = RSA_new();
      if (rsa == nullptr)
	{
	  SSLerr(SSL_F_SSL_USE_PRIVATEKEY, ERR_R_MALLOC_FAILURE);
	  errtext = "RSA_new";
	  goto err;
	}

      /* only set e and n as d (private key) is outside our control */
      RSA_set0_key(rsa, BN_dup(RSA_get0_n(pub_rsa)), BN_dup(RSA_get0_e(pub_rsa)), nullptr);
      RSA_set_flags(rsa, RSA_FLAG_EXT_PKEY);

      if (!RSA_set_method(rsa, rsa_meth))
	{
	  errtext = "RSA_set_method";
	  goto err;
	}
      /* rsa_meth will be freed when rsa is freed from this point,
       * set pointer to nullptr so the err does not try to free it
       */
      rsa_meth = nullptr;

      /* bind our custom RSA object to ssl_ctx */
      if (!SSL_CTX_use_RSAPrivateKey(ssl_ctx, rsa))
	{
	  errtext = "SSL_CTX_use_RSAPrivateKey";
	  goto err;
	}

      RSA_free(rsa); /* doesn't necessarily free, just decrements refcount */
      return;

      err:
	RSA_free(rsa);
	RSA_meth_free(rsa_meth);

      OPENVPN_THROW(OpenSSLException, "OpenSSLContext::ExternalPKIRsaImpl: " << errtext);
    }

    ~ExternalPKIRsaImpl() override = default;

    unsigned int get_n_errors() const
    { return n_errors; }

  private:
    /* called at RSA_free */
    static int rsa_finish(RSA* rsa)
    {
      RSA_meth_free(const_cast<RSA_METHOD*>(RSA_get_method(rsa)));
      return 1;
    }

    /* sign arbitrary data */
    static int
    rsa_priv_enc(int flen, const unsigned char* from, unsigned char* to, RSA* rsa, int padding)
    {
      ExternalPKIRsaImpl* self = (ExternalPKIRsaImpl*) (RSA_meth_get0_app_data(RSA_get_method(rsa)));

      try
	{
	  if (padding != RSA_PKCS1_PADDING && padding != RSA_NO_PADDING)
	    {
	      RSAerr (RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
	      throw ssl_external_pki("OpenSSL: bad padding type");
	    }
	  std::string padding_algo;
	  if (padding == RSA_PKCS1_PADDING)
	    {
	      padding_algo = "RSA_PKCS1_PADDING";
	    }
	  else if (padding == RSA_NO_PADDING)
	    {
	      padding_algo = "RSA_NO_PADDING";
	    }

	  /* convert 'from' to base64 */
	  ConstBuffer from_buf(from, flen, true);
	  const std::string from_b64 = base64->encode(from_buf);

	  /* get signature */
	  std::string sig_b64;
	  const bool status = self->external_pki->sign(from_b64, sig_b64, padding_algo);
	  if (!status)
	    throw ssl_external_pki("OpenSSL: could not obtain signature");

	  /* decode base64 signature to binary */
	  const int len = RSA_size(rsa);
	  Buffer sig(to, len, false);
	  base64->decode(sig, sig_b64);

	  /* verify length */
	  if (sig.size() != static_cast<size_t>(len))
	    throw ssl_external_pki("OpenSSL: incorrect signature length");

	  /* return length of signature */
	  return len;
	}
      catch (const std::exception& e)
	{
	  OPENVPN_LOG("OpenSSLContext::ExternalPKIRsaImpl::rsa_priv_enc exception: " << e.what());
	  ++self->n_errors;
	  return -1;
	}
    }

    static void not_implemented(RSA* rsa)
    {
      ExternalPKIRsaImpl* self = (ExternalPKIRsaImpl*) (RSA_meth_get0_app_data(RSA_get_method(rsa)));
      ++self->n_errors;
    }

    /* encrypt */
    static int
    rsa_pub_enc(int flen, const unsigned char* from, unsigned char* to, RSA* rsa, int padding)
    {
      not_implemented(rsa);
      return -1;
    }

    /* verify arbitrary data */
    static int
    rsa_pub_dec(int flen, const unsigned char* from, unsigned char* to, RSA* rsa, int padding)
    {
      not_implemented(rsa);
      return -1;
    }

    /* decrypt */
    static int
    rsa_priv_dec(int flen, const unsigned char* from, unsigned char* to, RSA* rsa, int padding)
    {
      not_implemented(rsa);
      return -1;
    }

    ExternalPKIBase* external_pki;
    unsigned int n_errors;
  };

  /* The OpenSSL EC_* methods we are using here are only available for OpennSSL 1.1.0 and later */
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(OPENSSL_NO_EC)
  class ExternalPKIECImpl : public ExternalPKIImpl
  {

  public:
    ExternalPKIECImpl(SSL_CTX* ssl_ctx, ::X509* cert, ExternalPKIBase* external_pki_arg)
      : external_pki(external_pki_arg)
    {

      if (ec_self_data_index < 0)
	throw ssl_external_pki("ExternalPKIECImpl::ec_self_data_index is uninitialized");

      std::string errtext;

      EVP_PKEY* privkey = nullptr;
      EC_KEY* ec = nullptr;
      EC_KEY_METHOD* ec_method = EC_KEY_METHOD_new(EC_KEY_OpenSSL());

      /* we only need to override a small number of methods */
      EC_KEY_METHOD_set_init(ec_method, NULL, ec_finish, NULL, NULL, NULL, NULL);
      EC_KEY_METHOD_set_sign(ec_method, ecdsa_sign, ecdsa_sign_setup, ecdsa_sign_sig);

      /* get the public key */
      EVP_PKEY* pubkey = X509_get0_pubkey(cert);

      if (pubkey == nullptr) /* nullptr before SSL_CTX_use_certificate() is called */
	{
	  errtext = "public key is NULL";
	  goto err;
	}

      if (EVP_PKEY_id(pubkey) != EVP_PKEY_EC)
	{
	  errtext = "public key is not EC";
	  goto err;
	}

      ec = EC_KEY_dup(static_cast<const EC_KEY*>(EVP_PKEY_get0(pubkey)));

      /* This will move responsibility to free ec_method to ec */
      if (!EC_KEY_set_method(ec, ec_method))
	{
	  errtext = "Could not set EC method";
	  EC_KEY_METHOD_free(ec_method);
	  goto err;
	}

      if (!EC_KEY_set_ex_data(ec, ec_self_data_index, this))
	{
	  errtext = "Could not set EC Key ex data";
	  EC_KEY_METHOD_free(ec_method);
	  goto err;
	}

      privkey = EVP_PKEY_new();
      if (!EVP_PKEY_assign_EC_KEY(privkey, ec))
	{
	  errtext = "assigning EC key methods failed";
	  goto err;
	}

      if (!SSL_CTX_use_PrivateKey(ssl_ctx, privkey))
	{
	  errtext = "assigning EC private key to SSL context failed";
	  goto err;
	}

      EVP_PKEY_free(privkey); /* release ref to privkey and ec */

      return;

      err:
      if (privkey)
	{
	  EVP_PKEY_free(privkey);
	}
      else
	{
	  EC_KEY_free(ec);
	}
      OPENVPN_THROW(OpenSSLException, "OpenSSLContext::ExternalPKIECImpl: " << errtext);
    }

    ~ExternalPKIECImpl() override = default;

    static void init_static()
    {
      ec_self_data_index = EC_KEY_get_ex_new_index(0, (char*) "ExternalPKIECImpl", nullptr, nullptr, nullptr);
    }

  private:
    static void ec_finish(EC_KEY* ec)
    {
      EC_KEY_METHOD_free(const_cast<EC_KEY_METHOD*>(EC_KEY_get_method(ec)));
    }

    /* sign arbitrary data */
    static int
    ecdsa_sign(int type, const unsigned char* dgst,
	       int dlen, unsigned char* sig,
	       unsigned int* siglen,
	       const BIGNUM* kinv, const BIGNUM* r,
	       EC_KEY* eckey)
    {
      ExternalPKIECImpl* self = (ExternalPKIECImpl*) (EC_KEY_get_ex_data(eckey, ec_self_data_index));

      try
	{
	  *siglen = ECDSA_size(eckey);
	  self->do_sign(dgst, dlen, sig, *siglen);
	  /* No error */
	  return 1;
	}
      catch (const std::exception& e)
	{
	  OPENVPN_LOG("OpenSSLContext::ExternalPKIECImpl::ecdsa_sign exception: " << e.what());
	  return 0;
	}
    }

    static int ecdsa_sign_setup(EC_KEY* eckey, BN_CTX* ctx_in, BIGNUM** kinvp, BIGNUM** rp)
    {
      /* No precomputation, return success */
      return 1;
    }

    static ECDSA_SIG*
    ecdsa_sign_sig(const unsigned char* dgst, int dgstlen, const BIGNUM* kinvp,
		   const BIGNUM* rp, EC_KEY* eckey)
    {
      ExternalPKIECImpl* self = (ExternalPKIECImpl*) (EC_KEY_get_ex_data(eckey, ec_self_data_index));

      unsigned len = ECDSA_size(eckey);

      auto sig = new unsigned char[len];

      ECDSA_SIG* ecsig = nullptr;
      try
	{
	  unsigned int siglen = len;
	  self->do_sign(dgst, dgstlen, sig, siglen);

	  ecsig = d2i_ECDSA_SIG(NULL, (const unsigned char**) &sig, siglen);
	}
      catch (const std::exception& e)
	{
	  OPENVPN_LOG("OpenSSLContext::ExternalPKIECImpl::ecdsa_sign_sig exception: " << e.what());
	}

      delete[] sig;
      return ecsig;
    }

    /**
     * Sign the input via external pki callback
     *
     * @param dgst 	digest to be signed
     * @param dlen 	length of the digest to be signed
     * @param sig 	buffer backing the signature
     * @param siglen 	maximum size for the signature, and length of the signature
     * 	   		returned in sig
     * @return
     */
    void do_sign(const unsigned char* dgst, int dlen,
		   unsigned char* sig, unsigned int& siglen)
    {
      /* convert 'dgst' to base64 */
      ConstBuffer dgst_buf(dgst, dlen, true);
      const std::string dgst_b64 = base64->encode(dgst_buf);

      /* get signature */
      std::string sig_b64;
      const bool status = external_pki->sign(dgst_b64, sig_b64, "ECDSA");
      if (!status)
	throw ssl_external_pki("OpenSSL: could not obtain signature");

      /* decode base64 signature to binary */
      Buffer sigout(sig, siglen, false);
      base64->decode(sigout, sig_b64);

      siglen = sigout.size();
    }

    ExternalPKIBase* external_pki;
    static int ec_self_data_index;
  };

#ifdef OPENVPN_NO_EXTERN
  int ExternalPKIECImpl::ec_self_data_index = -1;
#endif
#endif /* OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(OPENSSL_NO_EC) */
}