blob: a03fbe435f2369693f8ede1aef65d90e4cd08f9f [file] [log] [blame]
/*-------------------------------------------------------------------------
* 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) 2021, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/common/cipher_openssl.c
*
*-------------------------------------------------------------------------
*/
#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif
#include "common/cipher.h"
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
/*
* prototype for the EVP functions that return an algorithm, e.g.
* EVP_aes_128_gcm().
*/
typedef const EVP_CIPHER *(*ossl_EVP_cipher_func) (void);
static ossl_EVP_cipher_func get_evp_aes_gcm(int klen);
static EVP_CIPHER_CTX *ossl_cipher_ctx_create(int cipher, unsigned char *key,
int klen, bool enc);
/*
* Return a newly created cipher context. 'cipher' specifies cipher algorithm
* by identifier like PG_CIPHER_XXX.
*/
PgCipherCtx *
pg_cipher_ctx_create(int cipher, unsigned char *key, int klen, bool enc)
{
PgCipherCtx *ctx = NULL;
if (cipher > PG_MAX_CIPHER_ID)
return NULL;
ctx = ossl_cipher_ctx_create(cipher, key, klen, enc);
return ctx;
}
void
pg_cipher_ctx_free(PgCipherCtx *ctx)
{
EVP_CIPHER_CTX_free(ctx);
}
int
pg_cipher_blocksize(PgCipherCtx *ctx)
{
Assert(ctx);
return EVP_CIPHER_CTX_block_size(ctx);
}
/*
* Encryption routine to encrypt data provided.
*
* ctx is the encryption context which must have been created previously.
*
* plaintext is the data we are going to encrypt
* inlen is the length of the data to encrypt
*
* ciphertext is the encrypted result
* outlen is the encrypted length
*
* iv is the IV to use.
* ivlen is the IV length to use.
*
* outtag is the resulting tag.
* taglen is the length of the tag.
*/
bool
pg_cipher_encrypt(PgCipherCtx *ctx, int cipher,
const unsigned char *plaintext, const int inlen,
unsigned char *ciphertext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *outtag, const int taglen)
{
int len;
int enclen;
Assert(ctx != NULL);
/*
* Here we are setting the IV for the context which was passed in. Note
* that we signal to OpenSSL that we are configuring a new value for the
* context by passing in 'NULL' for the 2nd ('type') parameter.
*/
/*
* We don't use GCM mode, but it has a MAC, so we support it and test it
* in case we need it later. XXX is this correct for GCM and CTR?
*/
/* Set the GCM IV length first */
if (cipher == PG_CIPHER_AES_GCM &&
!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivlen, NULL))
return false;
/* Set the IV for this encryption. */
if (!EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, iv))
return false;
/*
* This is the function which is actually performing the encryption for
* us.
*/
if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, inlen))
return false;
enclen = len;
/* Finalize the encryption, which could add more to output. */
if (!EVP_EncryptFinal_ex(ctx, ciphertext + enclen, &len))
return false;
*outlen = enclen + len;
/*
* Once all of the encryption has been completed we grab the tag.
*/
if (cipher == PG_CIPHER_AES_GCM &&
!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, taglen, outtag))
return false;
return true;
}
/*
* Decryption routine
*
* ctx is the encryption context which must have been created previously.
*
* ciphertext is the data we are going to decrypt
* inlen is the length of the data to decrypt
*
* plaintext is the decrypted result
* outlen is the decrypted length
*
* iv is the IV to use.
* ivlen is the length of the IV.
*
* intag is the tag to use to verify.
* taglen is the length of the tag.
*/
bool
pg_cipher_decrypt(PgCipherCtx *ctx, const int cipher,
const unsigned char *ciphertext, const int inlen,
unsigned char *plaintext, int *outlen,
const unsigned char *iv, const int ivlen,
unsigned char *intag, const int taglen)
{
int declen;
int len;
/*
* Here we are setting the IV for the context which was passed in. Note
* that we signal to OpenSSL that we are configuring a new value for the
* context by passing in 'NULL' for the 2nd ('type') parameter.
*/
/* XXX is this correct for GCM and CTR? */
/* Set the GCM IV length first */
if (cipher == PG_CIPHER_AES_GCM &&
!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, ivlen, NULL))
return false;
/* Set the IV for this decryption. */
if (!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, iv))
return false;
/*
* This is the function which is actually performing the decryption for
* us.
*/
if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, inlen))
return false;
declen = len;
/* Set the expected tag value. */
if (cipher == PG_CIPHER_AES_GCM &&
!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, taglen, intag))
return false;
/*
* Finalize the decryption, which could add more to output, this is also
* the step which checks the tag and we MUST fail if this indicates an
* invalid tag!
*/
if (!EVP_DecryptFinal_ex(ctx, plaintext + declen, &len))
return false;
*outlen = declen + len;
return true;
}
/*
* Routine to perform key wrapping for data provided.
*
* ctx is the encryption context which must have been created previously.
*
* plaintext is the data/key we are going to encrypt/wrap
* inlen is the length of the data
*
* ciphertext is the wrapped result
* outlen is the encrypted length (will be larger than input!)
*/
bool
pg_cipher_keywrap(PgCipherCtx *ctx,
const unsigned char *plaintext, const int inlen,
unsigned char *ciphertext, int *outlen)
{
int len;
int enclen;
Assert(ctx != NULL);
/*
* This is the function which is actually performing the encryption for
* us.
*/
if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, inlen))
return false;
enclen = len;
/* Finalize the encryption, which could add more to output. */
if (!EVP_EncryptFinal_ex(ctx, ciphertext + enclen, &len))
return false;
*outlen = enclen + len;
return true;
}
/*
* Routine to perform key unwrapping of the data provided.
*
* ctx is the encryption context which must have been created previously.
*
* ciphertext is the wrapped key we are going to unwrap
* inlen is the length of the data to decrypt/unwrap
*
* plaintext is the decrypted result
* outlen is the decrypted length (will be smaller than input!)
*/
bool
pg_cipher_keyunwrap(PgCipherCtx *ctx,
const unsigned char *ciphertext, const int inlen,
unsigned char *plaintext, int *outlen)
{
int declen;
int len;
/*
* This is the function which is actually performing the decryption for
* us.
*/
if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, inlen))
return false;
declen = len;
/*
* Finalize the decryption, which could add more to output, this is also
* the step which checks the tag and we MUST fail if this indicates an
* invalid result!
*/
if (!EVP_DecryptFinal_ex(ctx, plaintext + declen, &len))
return false;
*outlen = declen + len;
return true;
}
/*
* Returns the correct GCM cipher functions for OpenSSL based
* on the key length requested.
*/
static ossl_EVP_cipher_func
get_evp_aes_gcm(int klen)
{
switch (klen)
{
case PG_AES128_KEY_LEN:
return EVP_aes_128_gcm;
case PG_AES192_KEY_LEN:
return EVP_aes_192_gcm;
case PG_AES256_KEY_LEN:
return EVP_aes_256_gcm;
default:
return NULL;
}
}
/*
* Returns the correct KWP cipher functions for OpenSSL based
* on the key length requested.
*/
static ossl_EVP_cipher_func
get_evp_aes_kwp(int klen)
{
switch (klen)
{
case PG_AES128_KEY_LEN:
return EVP_aes_128_wrap_pad;
case PG_AES192_KEY_LEN:
return EVP_aes_192_wrap_pad;
case PG_AES256_KEY_LEN:
return EVP_aes_256_wrap_pad;
default:
return NULL;
}
}
/*
* Returns the correct CTR cipher functions for OpenSSL based
* on the key length requested.
*/
static ossl_EVP_cipher_func
get_evp_aes_ctr(int klen)
{
switch (klen)
{
case PG_AES128_KEY_LEN:
return EVP_aes_128_ctr;
case PG_AES192_KEY_LEN:
return EVP_aes_192_ctr;
case PG_AES256_KEY_LEN:
return EVP_aes_256_ctr;
default:
return NULL;
}
}
/*
* Initialize and return an EVP_CIPHER_CTX. Returns NULL if the given
* cipher algorithm is not supported or on failure.
*/
static EVP_CIPHER_CTX *
ossl_cipher_ctx_create(int cipher, unsigned char *key, int klen, bool enc)
{
EVP_CIPHER_CTX *ctx;
ossl_EVP_cipher_func func;
int ret;
ctx = EVP_CIPHER_CTX_new();
/*
* We currently only support AES GCM but others could be added in the
* future.
*/
switch (cipher)
{
case PG_CIPHER_AES_GCM:
func = get_evp_aes_gcm(klen);
if (!func)
goto failed;
break;
case PG_CIPHER_AES_KWP:
func = get_evp_aes_kwp(klen);
/*
* Since wrapping will produce more output then input, and we have
* to be ready for that, OpenSSL requires that we explicitly
* enable wrapping for the context.
*/
EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
if (!func)
goto failed;
break;
case PG_CIPHER_AES_CTR:
func = get_evp_aes_ctr(klen);
if (!func)
goto failed;
break;
default:
goto failed;
}
/*
* We create the context here based on the cipher requested and the
* provided key. Note that the IV will be provided in the actual
* encryption call through another EVP_EncryptInit_ex call- this is fine
* as long as 'type' is passed in as NULL!
*/
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;
/* Set the key length based on the key length requested. */
if (!EVP_CIPHER_CTX_set_key_length(ctx, klen))
goto failed;
return ctx;
failed:
EVP_CIPHER_CTX_free(ctx);
return NULL;
}