Newer
Older
XinYang_IOS / Pods / OpenVPNAdapter / Sources / OpenVPN3 / openvpn / openssl / crypto / cipheraead.hpp
@zhangfeng zhangfeng on 7 Dec 2023 6 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/>.

// Wrap the OpenSSL GCM API.

#pragma once

#include <string>

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

#include <openvpn/common/size.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/likely.hpp>
#include <openvpn/crypto/static_key.hpp>
#include <openvpn/crypto/cryptoalgs.hpp>
#include <openvpn/openssl/util/error.hpp>

namespace openvpn {
  namespace OpenSSLCrypto {
    class CipherContextAEAD
    {
    public:
      CipherContextAEAD(const CipherContextAEAD&) = delete;
      CipherContextAEAD& operator=(const CipherContextAEAD&) = delete;

      OPENVPN_EXCEPTION(openssl_gcm_error);

      // mode parameter for constructor
      enum {
	MODE_UNDEF = -1,
	ENCRYPT = 1,
	DECRYPT = 0
      };

      // OpenSSL cipher constants
      enum {
	IV_LEN = 12,
	AUTH_TAG_LEN = 16,
	SUPPORTS_IN_PLACE_ENCRYPT = 0,
      };

      CipherContextAEAD() = default;

      ~CipherContextAEAD() { free_cipher_context(); }

      void init(const CryptoAlgs::Type alg,
		const unsigned char *key,
		const unsigned int keysize,
		const int mode)
      {
	free_cipher_context();
	unsigned int ckeysz = 0;
	const EVP_CIPHER *ciph = cipher_type(alg, ckeysz);

	if (ciph == nullptr)
	  OPENVPN_THROW(openssl_gcm_error, CryptoAlgs::name(alg) << ": not usable");

	if (ckeysz > keysize)
	  throw openssl_gcm_error("insufficient key material");
	ctx = EVP_CIPHER_CTX_new();
	EVP_CIPHER_CTX_reset(ctx);
	switch (mode)
	  {
	  case ENCRYPT:
	    if (!EVP_EncryptInit_ex(ctx, ciph, nullptr, key, nullptr))
	      {
		openssl_clear_error_stack();
		free_cipher_context();
		throw openssl_gcm_error("EVP_EncryptInit_ex (init)");
	      }
	    break;
	  case DECRYPT:
	    if (!EVP_DecryptInit_ex(ctx, ciph, nullptr, key, nullptr))
	      {
		openssl_clear_error_stack();
		free_cipher_context();
		throw openssl_gcm_error("EVP_DecryptInit_ex (init)");
	      }
	    break;
	  default:
	    throw openssl_gcm_error("bad mode");
	  }
	if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, IV_LEN, nullptr) != 1)
	  {
	    openssl_clear_error_stack();
	    free_cipher_context();
	    throw openssl_gcm_error("EVP_CIPHER_CTX_ctrl set IV len");
	  }
      }

      void encrypt(const unsigned char *input,
		   unsigned char *output,
		   size_t length,
		   const unsigned char *iv,
		   unsigned char *tag,
		   const unsigned char *ad,
		   size_t ad_len)
      {
	int len;
	int ciphertext_len;

	check_initialized();
	if (!EVP_EncryptInit_ex(ctx, nullptr, nullptr, nullptr, iv))
	  {
	    openssl_clear_error_stack();
	    throw openssl_gcm_error("EVP_EncryptInit_ex (reset)");
	  }
	if (!EVP_EncryptUpdate(ctx, nullptr, &len, ad, int(ad_len)))
	  {
	    openssl_clear_error_stack();
	    throw openssl_gcm_error("EVP_EncryptUpdate AD");
	  }
	if (!EVP_EncryptUpdate(ctx, output, &len, input, int(length)))
	  {
	    openssl_clear_error_stack();
	    throw openssl_gcm_error("EVP_EncryptUpdate data");
	  }
	ciphertext_len = len;
	if (!EVP_EncryptFinal_ex(ctx, output+len, &len))
	  {
	    openssl_clear_error_stack();
	    throw openssl_gcm_error("EVP_EncryptFinal_ex");
	  }
	ciphertext_len += len;
	if ((size_t) ciphertext_len != length)
	  {
	    throw openssl_gcm_error("encrypt size inconsistency");
	  }
	if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, AUTH_TAG_LEN, tag))
	  {
	    openssl_clear_error_stack();
	    throw openssl_gcm_error("EVP_CIPHER_CTX_ctrl get tag");
	  }
      }

      bool decrypt(const unsigned char *input,
		  unsigned char *output,
		  size_t length,
		  const unsigned char *iv,
		  unsigned char *tag,
		  const unsigned char *ad,
		  size_t ad_len)
      {
	int len;
	int plaintext_len;

	check_initialized();
	if (!EVP_DecryptInit_ex(ctx, nullptr, nullptr, nullptr, iv))
	  {
	    openssl_clear_error_stack();
	    throw openssl_gcm_error("EVP_DecryptInit_ex (reset)");
	  }
	if (!EVP_DecryptUpdate(ctx, nullptr, &len, ad, int(ad_len)))
	  {
	    openssl_clear_error_stack();
	    throw openssl_gcm_error("EVP_DecryptUpdate AD");
	  }
	if (!EVP_DecryptUpdate(ctx, output, &len, input, int(length)))
	  {
	    openssl_clear_error_stack();
	    throw openssl_gcm_error("EVP_DecryptUpdate data");
	  }
	plaintext_len = len;
	if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, AUTH_TAG_LEN, tag))
	  {
	    openssl_clear_error_stack();
	    throw openssl_gcm_error("EVP_CIPHER_CTX_ctrl set tag");
	  }
	if (!EVP_DecryptFinal_ex(ctx, output+len, &len))
	  {
	    openssl_clear_error_stack();
	    return false;
	  }
	plaintext_len += len;
	if (static_cast<size_t>(plaintext_len) != length)
	  {
	    throw openssl_gcm_error("decrypt size inconsistency");
	  }
	return true;
      }

      bool is_initialized() const { return ctx != nullptr; }

      static bool is_supported(const CryptoAlgs::Type alg)
      {
	unsigned int keysize;
	return (cipher_type(alg, keysize) != nullptr);
      }


    private:
      static const EVP_CIPHER *cipher_type(const CryptoAlgs::Type alg,
					   unsigned int& keysize)
      {
	switch (alg)
	  {
	  case CryptoAlgs::AES_128_GCM:
	    keysize = 16;
	    return EVP_aes_128_gcm();
	  case CryptoAlgs::AES_192_GCM:
	    keysize = 24;
	    return EVP_aes_192_gcm();
	  case CryptoAlgs::AES_256_GCM:
	      keysize = 32;
	    return EVP_aes_256_gcm();
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(OPENSSL_NO_POLY1305) && !defined(OPENSSL_NO_CHACHA)
	  case CryptoAlgs::CHACHA20_POLY1305:
	      keysize = 32;
	      return EVP_chacha20_poly1305();
#endif
	  default:
	       keysize = 0;
	       return nullptr;
	  }
      }

      void free_cipher_context()
      {
	EVP_CIPHER_CTX_free(ctx);
	ctx = nullptr;
      }

      void check_initialized() const
      {
#ifdef OPENVPN_ENABLE_ASSERT
	if (!ctx)
	  throw openssl_gcm_error("uninitialized");
#endif
      }

      EVP_CIPHER_CTX *ctx = nullptr;
    };
  }
}