From 14f225e8e7a8e996f6697b8c98c0d051d20fb6c8 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada Date: Mon, 25 May 2020 16:45:07 +0900 Subject: [PATCH v13 1/3] Add encryption functions for both frontend and backend. --- configure | 2 +- configure.ac | 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 cb8fbe1051..be52d2bed9 100755 --- a/configure +++ b/configure @@ -12408,7 +12408,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.ac b/configure.ac index eb2c731b58..6b20ffa983 100644 --- a/configure.ac +++ b/configure.ac @@ -1223,7 +1223,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 16619e4ba8..1a6355c0c1 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 +#include +#include +#include + +/* + * 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 +#include +#include +#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 fb270df678..d50a7c9d5c 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 header file. */ #undef HAVE_OSSP_UUID_H -- 2.23.0