From 9e704f24eb6ff8101986da0b261e9ef41e1c22c6 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Mon, 25 May 2020 16:45:07 +0900
Subject: [PATCH v11 1/7] Add encryption functions for both frontend and
 backend.

---
 configure                           |   2 +-
 configure.in                        |   2 +-
 src/common/Makefile                 |   2 +
 src/common/cipher.c                 |  87 +++++++++++++
 src/common/cipher_openssl.c         | 188 ++++++++++++++++++++++++++++
 src/include/common/cipher.h         |  78 ++++++++++++
 src/include/common/cipher_openssl.h |  37 ++++++
 src/include/pg_config.h.in          |   3 +
 8 files changed, 397 insertions(+), 2 deletions(-)
 create mode 100644 src/common/cipher.c
 create mode 100644 src/common/cipher_openssl.c
 create mode 100644 src/include/common/cipher.h
 create mode 100644 src/include/common/cipher_openssl.h

diff --git a/configure b/configure
index e147a98809..7b737f648f 100755
--- a/configure
+++ b/configure
@@ -12313,7 +12313,7 @@ done
   # defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it
   # doesn't have these OpenSSL 1.1.0 functions. So check for individual
   # functions.
-  for ac_func in OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data
+  for ac_func in OPENSSL_init_ssl OPENSSL_init_crypto BIO_get_data BIO_meth_new ASN1_STRING_get0_data
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
diff --git a/configure.in b/configure.in
index 1a9891c778..69ffacb379 100644
--- a/configure.in
+++ b/configure.in
@@ -1218,7 +1218,7 @@ if test "$with_openssl" = yes ; then
   # defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it
   # doesn't have these OpenSSL 1.1.0 functions. So check for individual
   # functions.
-  AC_CHECK_FUNCS([OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data])
+  AC_CHECK_FUNCS([OPENSSL_init_ssl OPENSSL_init_crypto BIO_get_data BIO_meth_new ASN1_STRING_get0_data])
   # OpenSSL versions before 1.1.0 required setting callback functions, for
   # thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
   # function was removed.
diff --git a/src/common/Makefile b/src/common/Makefile
index d0be882cca..6429fd6af2 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -49,6 +49,7 @@ OBJS_COMMON = \
 	archive.o \
 	base64.o \
 	checksum_helper.o \
+	cipher.o \
 	config_info.o \
 	controldata_utils.o \
 	d2s.o \
@@ -79,6 +80,7 @@ OBJS_COMMON = \
 
 ifeq ($(with_openssl),yes)
 OBJS_COMMON += \
+	cipher_openssl.o \
 	protocol_openssl.o \
 	sha2_openssl.o
 else
diff --git a/src/common/cipher.c b/src/common/cipher.c
new file mode 100644
index 0000000000..908afe8446
--- /dev/null
+++ b/src/common/cipher.c
@@ -0,0 +1,87 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher.c
+ *	  Shared frontend/backend for cryptographic functions
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/cipher.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/cipher.h"
+#ifdef USE_OPENSSL
+#include "common/cipher_openssl.h"
+#endif
+
+/*
+ * Return a newly created cipher context.  'cipher' specifies cipher algorithm
+ * by identifer like PG_CIPHER_XXX.
+ */
+PgCipherCtx *
+pg_cipher_ctx_create(int cipher, uint8 *key, int klen)
+{
+	PgCipherCtx *ctx = NULL;
+
+	if (cipher >= PG_MAX_CIPHER_ID)
+		return NULL;
+
+#ifdef USE_OPENSSL
+	ctx = (PgCipherCtx *) palloc0(sizeof(PgCipherCtx));
+
+	ctx->encctx = ossl_cipher_ctx_create(cipher, key, klen, true);
+	ctx->decctx = ossl_cipher_ctx_create(cipher, key, klen, false);
+#endif
+
+	return ctx;
+}
+
+void
+pg_cipher_ctx_free(PgCipherCtx *ctx)
+{
+#ifdef USE_OPENSSL
+	ossl_cipher_ctx_free(ctx->encctx);
+	ossl_cipher_ctx_free(ctx->decctx);
+#endif
+}
+
+bool
+pg_cipher_encrypt(PgCipherCtx *ctx, const uint8 *in, int inlen,
+				  uint8 *out, int *outlen, const uint8 *iv)
+{
+	bool		r = false;
+#ifdef USE_OPENSSL
+	r = ossl_cipher_encrypt(ctx->encctx, in, inlen, out, outlen, iv);
+#endif
+	return r;
+}
+
+bool
+pg_cipher_decrypt(PgCipherCtx *ctx, const uint8 *in, int inlen,
+				  uint8 *out, int *outlen, const uint8 *iv)
+{
+	bool		r = false;
+#ifdef USE_OPENSSL
+	r = ossl_cipher_decrypt(ctx->decctx, in, inlen, out, outlen, iv);
+#endif
+	return r;
+}
+
+bool
+pg_HMAC_SHA512(const uint8 *key, const uint8 *in, int inlen,
+			   uint8 *out)
+{
+	bool		r = false;
+#ifdef USE_OPENSSL
+	r = ossl_HMAC_SHA512(key, in, inlen, out);
+#endif
+	return r;
+}
diff --git a/src/common/cipher_openssl.c b/src/common/cipher_openssl.c
new file mode 100644
index 0000000000..a1d6d59bbd
--- /dev/null
+++ b/src/common/cipher_openssl.c
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ * cipher_openssl.c
+ *		Cryptographic function using OpenSSL
+ *
+ * This contains the common low-level functions needed in both frontend and
+ * backend, for implement the database encryption.
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/common/cipher_openssl.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/sha2.h"
+#include "common/cipher_openssl.h"
+
+#include <openssl/conf.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/hmac.h>
+
+/*
+ * prototype for the EVP functions that return an algorithm, e.g.
+ * EVP_aes_128_cbc().
+ */
+typedef const EVP_CIPHER *(*ossl_EVP_cipher_func) (void);
+
+static bool ossl_initialized = false;
+
+static bool ossl_cipher_setup(void);
+static ossl_EVP_cipher_func get_evp_aes_cbc(int klen);
+
+static bool
+ossl_cipher_setup(void)
+{
+#ifdef HAVE_OPENSSL_INIT_CRYPTO
+	/* Setup OpenSSL */
+	if (!OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, NULL))
+		return false;
+#else
+	OPENSSL_config(NULL);
+#endif
+	return false;
+}
+
+static ossl_EVP_cipher_func
+get_evp_aes_cbc(int klen)
+{
+	switch (klen)
+	{
+		case PG_AES128_KEY_LEN:
+			return EVP_aes_128_cbc;
+		case PG_AES192_KEY_LEN:
+			return EVP_aes_192_cbc;
+		case PG_AES256_KEY_LEN:
+			return EVP_aes_256_cbc;
+		default:
+			return NULL;
+	}
+}
+
+/*
+ * Initialize and return an EVP_CIPHER_CTX. Return NULL if the given
+ * cipher algorithm is not supported or on failure..
+ */
+EVP_CIPHER_CTX *
+ossl_cipher_ctx_create(int cipher, uint8 *key, int klen, bool enc)
+{
+	EVP_CIPHER_CTX			*ctx;
+	ossl_EVP_cipher_func	func;
+	int	ret;
+
+	if (!ossl_initialized)
+	{
+		ossl_cipher_setup();
+		ossl_initialized = true;
+	}
+
+	ctx = EVP_CIPHER_CTX_new();
+
+	switch (cipher)
+	{
+		case PG_CIPHER_AES_CBC:
+			func = get_evp_aes_cbc(klen);
+			if (!func)
+				goto failed;
+			break;
+		default:
+			goto failed;
+	}
+
+
+	if (enc)
+		ret = EVP_EncryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
+	else
+		ret = EVP_DecryptInit_ex(ctx, (const EVP_CIPHER *) func(), NULL, key, NULL);
+
+	if (!ret)
+		goto failed;
+
+	if (!EVP_CIPHER_CTX_set_key_length(ctx, PG_AES256_KEY_LEN))
+		goto failed;
+
+	/*
+	 * Always enable padding. We don't need to check the return value as
+	 * EVP_CIPHER_CTX_set_padding always returns 1.
+	 */
+	EVP_CIPHER_CTX_set_padding(ctx, 1);
+
+	return ctx;
+
+failed:
+	EVP_CIPHER_CTX_free(ctx);
+	return NULL;
+}
+
+void
+ossl_cipher_ctx_free(EVP_CIPHER_CTX *ctx)
+{
+	return EVP_CIPHER_CTX_free(ctx);
+}
+
+bool
+ossl_cipher_encrypt(EVP_CIPHER_CTX *ctx,
+					const uint8 *in, int inlen,
+					uint8 *out, int *outlen,
+					const uint8 *iv)
+{
+	int			len;
+	int			enclen;
+
+	if (!EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv))
+		return false;
+
+	if (!EVP_EncryptUpdate(ctx, out, &len, in, inlen))
+		return false;
+
+	enclen = len;
+
+	if (!EVP_EncryptFinal_ex(ctx, (uint8 *) ((char *) out + enclen),
+							 &len))
+		return false;
+
+	*outlen = enclen + len;
+
+	return true;
+}
+
+bool
+ossl_cipher_decrypt(EVP_CIPHER_CTX *ctx,
+					const uint8 *in, int inlen,
+					uint8 *out, int *outlen,
+					const uint8 *iv)
+{
+	int			declen;
+	int			len;
+
+	if (!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv))
+		return false;
+
+	if (!EVP_DecryptUpdate(ctx, out, &len, in, inlen))
+		return false;
+
+	declen = len;
+
+	if (!EVP_DecryptFinal_ex(ctx, (uint8 *) ((char *) out + declen),
+							 &len))
+		return false;
+
+	*outlen = declen + len;
+
+	return true;
+}
+
+bool
+ossl_HMAC_SHA512(const uint8 *key, const uint8 *in, int inlen,
+				 uint8 *out)
+{
+	return HMAC(EVP_sha512(), key, PG_SHA512_DIGEST_LENGTH,
+				in, (uint32) inlen, out, NULL);
+}
diff --git a/src/include/common/cipher.h b/src/include/common/cipher.h
new file mode 100644
index 0000000000..f78279115f
--- /dev/null
+++ b/src/include/common/cipher.h
@@ -0,0 +1,78 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher.h
+ *		Declarations for cryptographic functions
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/cipher.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef CIPHER_H
+#define CIPHER_H
+
+#ifdef USE_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/conf.h>
+#include <openssl/err.h>
+#endif
+
+/*
+ * Supported symmetric encryption algorithm. These identifiers are passed
+ * to pg_cipher_ctx_create() function, and then actual encryption
+ * implementations need to initialize their context of the given encryption
+ * algorithm.
+ */
+#define PG_CIPHER_AES_CBC			0
+#define PG_MAX_CIPHER_ID			1
+
+/* AES128/192/256 various length definitions */
+#define PG_AES128_KEY_LEN			(128 / 8)
+#define PG_AES192_KEY_LEN			(192 / 8)
+#define PG_AES256_KEY_LEN			(256 / 8)
+
+/*
+ * The encrypted data is a series of blocks of size. Initialization
+ * vector(IV) is the same size of cipher block.
+ */
+#define PG_AES_BLOCK_SIZE			16
+#define PG_AES_IV_SIZE				(PG_AES_BLOCK_SIZE)
+
+/* HMAC key and HMAC length. We use HMAC-SHA256 */
+#define PG_HMAC_SHA512_KEY_LEN		64
+#define PG_HMAC_SHA512_LEN			64
+
+#ifdef USE_OPENSSL
+typedef EVP_CIPHER_CTX cipher_private_ctx;
+#else
+typedef void cipher_private_ctx;
+#endif
+
+/*
+ * This struct has two implementation-private context for
+ * encryption and decryption. The caller must create the encryption
+ * context using by pg_cipher_ctx_create() and pass the context  to
+ * pg_cipher_encrypt() or pg_cipher_decrypt().
+ */
+typedef struct PgCipherCtx
+{
+	cipher_private_ctx *encctx;
+	cipher_private_ctx *decctx;
+} PgCipherCtx;
+
+extern PgCipherCtx *pg_cipher_ctx_create(int cipher, uint8 *key, int klen);
+extern void pg_cipher_ctx_free(PgCipherCtx *ctx);
+extern bool pg_cipher_encrypt(PgCipherCtx *ctx,
+							  const uint8 *in, int inlen,
+							  uint8 *out, int *outlen,
+							  const uint8 *iv);
+extern bool pg_cipher_decrypt(PgCipherCtx *ctx,
+							  const uint8 *in, int inlen,
+							  uint8 *out, int *outlen,
+							  const uint8 *iv);
+extern bool pg_HMAC_SHA512(const uint8 *key,
+						   const uint8 *in, int inlen,
+						   uint8 *out);
+
+#endif							/* CIPHER_H */
diff --git a/src/include/common/cipher_openssl.h b/src/include/common/cipher_openssl.h
new file mode 100644
index 0000000000..0fd1308bc9
--- /dev/null
+++ b/src/include/common/cipher_openssl.h
@@ -0,0 +1,37 @@
+/*-------------------------------------------------------------------------
+ *
+ * cipher_openssl.h
+ *		Declarations for helper functions using OpenSSL
+ *
+ * Portions Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * src/include/common/cipher_openssl.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef CIPHER_OPENSSL_H
+#define CIPHER_OPENSSL_H
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include "common/cipher.h"
+
+extern EVP_CIPHER_CTX *ossl_cipher_ctx_create(int cipher, uint8 *key, int klen,
+											  bool enc);
+extern void ossl_cipher_ctx_free(EVP_CIPHER_CTX *ctx);
+extern bool ossl_cipher_encrypt(EVP_CIPHER_CTX *ctx,
+								const uint8 *in, int inlen,
+								uint8 *out, int *outlen,
+								const uint8 *iv);
+extern bool ossl_cipher_decrypt(EVP_CIPHER_CTX *ctx,
+								const uint8 *in, int inlen,
+								uint8 *out,	int *outlen,
+								const uint8 *iv);
+extern bool ossl_HMAC_SHA512(const uint8 *key,
+							 const uint8 *in, int inlen,
+							 uint8 *out);
+#endif
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c199cd46d2..ceb39da1ca 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -385,6 +385,9 @@
 /* Define to 1 if you have the `OPENSSL_init_ssl' function. */
 #undef HAVE_OPENSSL_INIT_SSL
 
+/* Define to 1 if you have the `OPENSSL_init_crypto' function. */
+#undef HAVE_OPENSSL_INIT_CRYPTO
+
 /* Define to 1 if you have the <ossp/uuid.h> header file. */
 #undef HAVE_OSSP_UUID_H
 
-- 
2.23.0

