From 752805f50dd7c63b8784cd15336ad3056d98adf9 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Wed, 28 Oct 2020 11:34:37 +0100 Subject: [PATCH v15 5/6] NSS contrib modules --- contrib/Makefile | 2 +- contrib/pgcrypto/Makefile | 7 +- contrib/pgcrypto/nss.c | 773 ++++++++++++++++++++++++++++++++++++ contrib/pgcrypto/px.c | 1 + contrib/pgcrypto/px.h | 1 + contrib/sslinfo/sslinfo.c | 164 ++++---- doc/src/sgml/pgcrypto.sgml | 38 +- doc/src/sgml/sslinfo.sgml | 12 +- src/tools/msvc/Install.pm | 3 +- src/tools/msvc/Mkvcbuild.pm | 12 +- 10 files changed, 910 insertions(+), 103 deletions(-) create mode 100644 contrib/pgcrypto/nss.c diff --git a/contrib/Makefile b/contrib/Makefile index 7a4866e338..c2232e5a4d 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -52,7 +52,7 @@ SUBDIRS = \ unaccent \ vacuumlo -ifeq ($(with_openssl),yes) +ifneq ($(with_ssl),no) SUBDIRS += sslinfo else ALWAYS_SUBDIRS += sslinfo diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile index 61eabd2fc0..28b54d321b 100644 --- a/contrib/pgcrypto/Makefile +++ b/contrib/pgcrypto/Makefile @@ -7,11 +7,14 @@ INT_TESTS = sha2 OSSL_SRCS = openssl.c pgp-mpi-openssl.c OSSL_TESTS = sha2 des 3des cast5 +NSS_SRCS = nss.c pgp-mpi-internal.c imath.c blf.c +NSS_TESTS = sha2 des 3des + ZLIB_TST = pgp-compression ZLIB_OFF_TST = pgp-zlib-DISABLED -CF_SRCS = $(if $(subst no,,$(with_openssl)), $(OSSL_SRCS), $(INT_SRCS)) -CF_TESTS = $(if $(subst no,,$(with_openssl)), $(OSSL_TESTS), $(INT_TESTS)) +CF_SRCS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_SRCS), $(if $(findstring nss, $(with_ssl)), $(NSS_SRCS), $(INT_SRCS))) +CF_TESTS = $(if $(findstring openssl, $(with_ssl)), $(OSSL_TESTS), $(if $(findstring nss, $(with_ssl)), $(NSS_TESTS), $(INT_TESTS))) CF_PGP_TESTS = $(if $(subst no,,$(with_zlib)), $(ZLIB_TST), $(ZLIB_OFF_TST)) SRCS = \ diff --git a/contrib/pgcrypto/nss.c b/contrib/pgcrypto/nss.c new file mode 100644 index 0000000000..e1291ed403 --- /dev/null +++ b/contrib/pgcrypto/nss.c @@ -0,0 +1,773 @@ +/*------------------------------------------------------------------------- + * + * nss.c + * Wrapper for using NSS as a backend for pgcrypto PX + * + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * contrib/pgcrypto/nss.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "px.h" +#include "blf.h" +#include "utils/memutils.h" + +/* + * BITS_PER_BYTE is also defined in the NSPR header files, so we need to undef + * our version to avoid compiler warnings on redefinition. + */ +#define pg_BITS_PER_BYTE BITS_PER_BYTE +#undef BITS_PER_BYTE + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Ensure that the colliding definitions match, else throw an error. In case + * NSPR has removed the definition for some reason, make sure to put ours + * back again. + */ +#if defined(BITS_PER_BYTE) +#if BITS_PER_BYTE != pg_BITS_PER_BYTE +#error "incompatible byte widths between NSPR and postgres" +#endif +#else +#define BITS_PER_BYTE pg_BITS_PER_BYTE +#endif +#undef pg_BITS_PER_BYTE + +/* + * Define our own mechanisms for Blowfish as it's not implemented by NSS. + */ +#define BLOWFISH_CBC (1) +#define BLOWFISH_ECB (2) + +/* + * Data structures for recording cipher implementations as well as ongoing + * cipher operations. + */ +typedef struct nss_digest +{ + NSSInitContext *context; + PK11Context *hash_context; + HASH_HashType hash_type; +} nss_digest; + +typedef struct cipher_implementation +{ + /* Function pointers to cipher operations */ + int (*init) (PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv); + unsigned (*get_block_size) (PX_Cipher *pxc); + unsigned (*get_key_size) (PX_Cipher *pxc); + unsigned (*get_iv_size) (PX_Cipher *pxc); + int (*encrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res); + int (*decrypt) (PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res); + void (*free) (PX_Cipher *pxc); + + /* The mechanism describing the cipher used */ + union + { + CK_MECHANISM_TYPE nss; + CK_ULONG internal; + } mechanism; + int keylen; + bool is_nss; +} cipher_implementation; + +typedef struct nss_cipher +{ + const cipher_implementation *impl; + NSSInitContext *context; + PK11Context *crypt_context; + SECItem *params; + + PK11SymKey *encrypt_key; + PK11SymKey *decrypt_key; +} nss_cipher; + +typedef struct internal_cipher +{ + const cipher_implementation *impl; + BlowfishContext context; +} internal_cipher; + +typedef struct nss_cipher_ref +{ + const char *name; + const cipher_implementation *impl; +} nss_cipher_ref; + +/* + * Prototypes + */ +static unsigned nss_get_iv_size(PX_Cipher *pxc); +static unsigned nss_get_block_size(PX_Cipher *pxc); + +/* + * nss_GetHashOidTagByHashType + * + * Returns the corresponding SECOidTag for the passed hash type. NSS 3.43 + * includes HASH_GetHashOidTagByHashType for this purpose, but at the time of + * writing is not commonly available so we need our own version till then. + */ +static SECOidTag +nss_GetHashOidTagByHashType(HASH_HashType type) +{ + if (type == HASH_AlgMD2) + return SEC_OID_MD2; + if (type == HASH_AlgMD5) + return SEC_OID_MD5; + if (type == HASH_AlgSHA1) + return SEC_OID_SHA1; + if (type == HASH_AlgSHA224) + return SEC_OID_SHA224; + if (type == HASH_AlgSHA256) + return SEC_OID_SHA256; + if (type == HASH_AlgSHA384) + return SEC_OID_SHA384; + if (type == HASH_AlgSHA512) + return SEC_OID_SHA512; + + return SEC_OID_UNKNOWN; +} + +static unsigned +nss_digest_block_size(PX_MD *pxmd) +{ + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + const SECHashObject *object; + + object = HASH_GetHashObject(digest->hash_type); + return object->blocklength; +} + +static unsigned +nss_digest_result_size(PX_MD *pxmd) +{ + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + const SECHashObject *object; + + object = HASH_GetHashObject(digest->hash_type); + return object->length; +} + +static void +nss_digest_free(PX_MD *pxmd) +{ + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + PRBool free_ctx = PR_TRUE; + + PK11_DestroyContext(digest->hash_context, free_ctx); + NSS_ShutdownContext(digest->context); +} + +static void +nss_digest_update(PX_MD *pxmd, const uint8 *data, unsigned dlen) +{ + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + + PK11_DigestOp(digest->hash_context, data, dlen); +} + +static void +nss_digest_reset(PX_MD *pxmd) +{ + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + + PK11_DigestBegin(digest->hash_context); +} + +static void +nss_digest_finish(PX_MD *pxmd, uint8 *dst) +{ + unsigned int outlen; + nss_digest *digest = (nss_digest *) pxmd->p.ptr; + const SECHashObject *object; + + object = HASH_GetHashObject(digest->hash_type); + PK11_DigestFinal(digest->hash_context, dst, &outlen, object->length); +} + +int +px_find_digest(const char *name, PX_MD **res) +{ + PX_MD *pxmd; + NSSInitParameters params; + NSSInitContext *nss_context; + bool found = false; + nss_digest *digest; + SECStatus status; + SECOidData *hash; + HASH_HashType t; + + /* + * Initialize our own NSS context without a database backing it. + */ + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + nss_context = NSS_InitContext("", "", "", "", ¶ms, + NSS_INIT_READONLY | NSS_INIT_NOCERTDB | + NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | + NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD); + + /* + * There is no API function for looking up a digest algorithm from a name + * string, but there is a publically accessible array which can be scanned + * for the name. + */ + for (t = HASH_AlgNULL + 1; t < HASH_AlgTOTAL; t++) + { + SECOidTag hash_oid; + char *p; + + hash_oid = nss_GetHashOidTagByHashType(t); + + if (hash_oid == SEC_OID_UNKNOWN) + return PXE_NO_HASH; + + hash = SECOID_FindOIDByTag(hash_oid); + if (pg_strcasecmp(hash->desc, name) == 0) + { + found = true; + break; + } + + /* + * NSS saves the algorithm names using SHA-xxx notation whereas + * OpenSSL use SHAxxx. To make sure the user finds the requested + * algorithm let's remove the dash and compare that spelling as well. + */ + if ((p = strchr(hash->desc, '-')) != NULL) + { + char tmp[12]; + + memcpy(tmp, hash->desc, p - hash->desc); + memcpy(tmp + (p - hash->desc), p + 1, strlen(hash->desc) - (p - hash->desc) + 1); + if (pg_strcasecmp(tmp, name) == 0) + { + found = true; + break; + } + } + } + + if (!found) + return PXE_NO_HASH; + + digest = palloc(sizeof(*digest)); + + digest->context = nss_context; + digest->hash_context = PK11_CreateDigestContext(hash->offset); + digest->hash_type = t; + if (digest->hash_context == NULL) + { + pfree(digest); + return -1; + } + + status = PK11_DigestBegin(digest->hash_context); + if (status != SECSuccess) + { + PK11_DestroyContext(digest->hash_context, PR_TRUE); + pfree(digest); + return -1; + } + + pxmd = palloc(sizeof(*pxmd)); + pxmd->result_size = nss_digest_result_size; + pxmd->block_size = nss_digest_block_size; + pxmd->reset = nss_digest_reset; + pxmd->update = nss_digest_update; + pxmd->finish = nss_digest_finish; + pxmd->free = nss_digest_free; + pxmd->p.ptr = (void *) digest; + + *res = pxmd; + + return 0; +} + +static int +bf_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, const uint8 *iv) +{ + internal_cipher *cipher = (internal_cipher *) pxc->ptr; + + blowfish_setkey(&cipher->context, key, klen); + if (iv) + blowfish_setiv(&cipher->context, iv); + + return 0; +} + +static int +bf_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res) +{ + internal_cipher *cipher = (internal_cipher *) pxc->ptr; + uint8 *buf; + + if (dlen == 0) + return 0; + + if (dlen & 7) + return PXE_NOTBLOCKSIZE; + + buf = palloc(dlen); + memcpy(buf, data, dlen); + + if (cipher->impl->mechanism.internal == BLOWFISH_ECB) + blowfish_encrypt_ecb(buf, dlen, &cipher->context); + else + blowfish_encrypt_cbc(buf, dlen, &cipher->context); + + memcpy(res, buf, dlen); + pfree(buf); + + return 0; +} + +static int +bf_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res) +{ + internal_cipher *cipher = (internal_cipher *) pxc->ptr; + + if (dlen == 0) + return 0; + + if (dlen & 7) + return PXE_NOTBLOCKSIZE; + + memcpy(res, data, dlen); + if (cipher->impl->mechanism.internal == BLOWFISH_ECB) + blowfish_decrypt_ecb(res, dlen, &cipher->context); + else + blowfish_decrypt_cbc(res, dlen, &cipher->context); + + return 0; +} + +static void +bf_free(PX_Cipher *pxc) +{ + internal_cipher *cipher = (internal_cipher *) pxc->ptr; + + pfree(cipher); + pfree(pxc); +} + +static int +nss_symkey_blockcipher_init(PX_Cipher *pxc, const uint8 *key, unsigned klen, + const uint8 *iv) +{ + nss_cipher *cipher = (nss_cipher *) pxc->ptr; + SECItem iv_item; + unsigned char *iv_item_data; + SECItem key_item; + int keylen; + PK11SlotInfo *slot; + + if (cipher->impl->mechanism.nss == CKM_AES_CBC || + cipher->impl->mechanism.nss == CKM_AES_ECB) + { + if (klen <= 128 / 8) + keylen = 128 / 8; + else if (klen <= 192 / 8) + keylen = 192 / 8; + else if (klen <= 256 / 8) + keylen = 256 / 8; + else + return PXE_CIPHER_INIT; + } + else + keylen = cipher->impl->keylen; + + key_item.type = siBuffer; + key_item.data = (unsigned char *) key; + key_item.len = keylen; + + /* + * If hardware acceleration is configured in NSS one can theoretically get + * a better slot by calling PK11_GetBestSlot() with the mechanism passed + * to it. Unless there are complaints, using the simpler API will make + * error handling easier though. + */ + slot = PK11_GetInternalSlot(); + if (!slot) + return PXE_CIPHER_INIT; + + /* + * The key must be set up for the operation, and since we don't know at + * this point whether we are asked to encrypt or decrypt we need to store + * both versions. + */ + cipher->decrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss, + PK11_OriginUnwrap, + CKA_DECRYPT, &key_item, + NULL); + cipher->encrypt_key = PK11_ImportSymKey(slot, cipher->impl->mechanism.nss, + PK11_OriginUnwrap, + CKA_ENCRYPT, &key_item, + NULL); + PK11_FreeSlot(slot); + + if (!cipher->decrypt_key || !cipher->encrypt_key) + return PXE_CIPHER_INIT; + + if (iv) + { + iv_item.type = siBuffer; + iv_item.data = (unsigned char *) iv; + iv_item.len = nss_get_iv_size(pxc); + } + else + { + /* + * The documentation states that either passing .data = 0; .len = 0; + * in the iv_item, or NULL as iv_item, to PK11_ParamFromIV should be + * done when IV is missing. That however leads to segfaults in the + * library, the workaround that works in modern library versions is + * to pass in a keysized zeroed out IV. + */ + iv_item_data = palloc0(nss_get_iv_size(pxc)); + iv_item.type = siBuffer; + iv_item.data = iv_item_data; + iv_item.len = nss_get_iv_size(pxc); + } + + cipher->params = PK11_ParamFromIV(cipher->impl->mechanism.nss, &iv_item); + + /* If we had to make a mock IV, free it once made into a param */ + if (!iv) + pfree(iv_item_data); + + if (cipher->params == NULL) + return PXE_CIPHER_INIT; + + return 0; +} + +static int +nss_decrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res) +{ + nss_cipher *cipher = (nss_cipher *) pxc->ptr; + SECStatus status; + int outlen; + + if (!cipher->crypt_context) + { + cipher->crypt_context = + PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_DECRYPT, + cipher->decrypt_key, cipher->params); + } + + status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen); + if (status != SECSuccess) + return PXE_DECRYPT_FAILED; + + return 0; +} + +static int +nss_encrypt(PX_Cipher *pxc, const uint8 *data, unsigned dlen, uint8 *res) +{ + nss_cipher *cipher = (nss_cipher *) pxc->ptr; + SECStatus status; + int outlen; + + if (!cipher->crypt_context) + { + cipher->crypt_context = + PK11_CreateContextBySymKey(cipher->impl->mechanism.nss, CKA_ENCRYPT, + cipher->decrypt_key, cipher->params); + } + + status = PK11_CipherOp(cipher->crypt_context, res, &outlen, dlen, data, dlen); + + if (status != SECSuccess) + return PXE_DECRYPT_FAILED; + + return 0; +} + +static void +nss_free(PX_Cipher *pxc) +{ + nss_cipher *cipher = pxc->ptr; + PRBool free_ctx = PR_TRUE; + + PK11_FreeSymKey(cipher->encrypt_key); + PK11_FreeSymKey(cipher->decrypt_key); + PK11_DestroyContext(cipher->crypt_context, free_ctx); + NSS_ShutdownContext(cipher->context); + pfree(cipher); + + pfree(pxc); +} + +static unsigned +nss_get_block_size(PX_Cipher *pxc) +{ + nss_cipher *cipher = pxc->ptr; + + return PK11_GetBlockSize(cipher->impl->mechanism.nss, NULL); +} + +static unsigned +nss_get_key_size(PX_Cipher *pxc) +{ + nss_cipher *cipher = pxc->ptr; + + return cipher->impl->keylen; +} + +static unsigned +nss_get_iv_size(PX_Cipher *pxc) +{ + nss_cipher *cipher = pxc->ptr; + + return PK11_GetIVLength(cipher->impl->mechanism.nss); +} + +static unsigned +bf_get_block_size(PX_Cipher *pxc) +{ + return 8; +} + +static unsigned +bf_get_key_size(PX_Cipher *pxc) +{ + return 448 / 8; +} + +static unsigned +bf_get_iv_size(PX_Cipher *pxc) +{ + return 8; +} + +/* + * Cipher Implementations + */ +static const cipher_implementation nss_des_cbc = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_DES_CBC, + .keylen = 8, + .is_nss = true +}; + +static const cipher_implementation nss_des_ecb = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_DES_ECB, + .keylen = 8, + .is_nss = true +}; + +static const cipher_implementation nss_des3_cbc = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_DES3_CBC, + .keylen = 24, + .is_nss = true +}; + +static const cipher_implementation nss_des3_ecb = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_DES3_ECB, + .keylen = 24, + .is_nss = true +}; + +static const cipher_implementation nss_aes_cbc = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_AES_CBC, + .keylen = 32, + .is_nss = true +}; + +static const cipher_implementation nss_aes_ecb = { + .init = nss_symkey_blockcipher_init, + .get_block_size = nss_get_block_size, + .get_key_size = nss_get_key_size, + .get_iv_size = nss_get_iv_size, + .encrypt = nss_encrypt, + .decrypt = nss_decrypt, + .free = nss_free, + .mechanism.nss = CKM_AES_ECB, + .keylen = 32, + .is_nss = true +}; + +static const cipher_implementation nss_bf_ecb = { + .init = bf_init, + .get_block_size = bf_get_block_size, + .get_key_size = bf_get_key_size, + .get_iv_size = bf_get_iv_size, + .encrypt = bf_encrypt, + .decrypt = bf_decrypt, + .free = bf_free, + .mechanism.internal = BLOWFISH_ECB, + .keylen = 56, + .is_nss = false +}; + +static const cipher_implementation nss_bf_cbc = { + .init = bf_init, + .get_block_size = bf_get_block_size, + .get_key_size = bf_get_key_size, + .get_iv_size = bf_get_iv_size, + .encrypt = bf_encrypt, + .decrypt = bf_decrypt, + .free = bf_free, + .mechanism.internal = BLOWFISH_CBC, + .keylen = 56, + .is_nss = false +}; + +/* + * Lookup table for finding the implementation based on a name. CAST5 as well + * as BLOWFISH are defined as cipher mechanisms in NSS but error out with + * SEC_ERROR_INVALID_ALGORITHM. Blowfish is implemented using the internal + * implementation while CAST5 isn't supported at all at this time, + */ +static const nss_cipher_ref nss_cipher_lookup[] = { + {"des", &nss_des_cbc}, + {"des-cbc", &nss_des_cbc}, + {"des-ecb", &nss_des_ecb}, + {"des3-cbc", &nss_des3_cbc}, + {"3des", &nss_des3_cbc}, + {"3des-cbc", &nss_des3_cbc}, + {"des3-ecb", &nss_des3_ecb}, + {"3des-ecb", &nss_des3_ecb}, + {"aes", &nss_aes_cbc}, + {"aes-cbc", &nss_aes_cbc}, + {"aes-ecb", &nss_aes_ecb}, + {"rijndael", &nss_aes_cbc}, + {"rijndael-cbc", &nss_aes_cbc}, + {"rijndael-ecb", &nss_aes_ecb}, + {"blowfish", &nss_bf_cbc}, + {"blowfish-cbc", &nss_bf_cbc}, + {"blowfish-ecb", &nss_bf_ecb}, + {"bf", &nss_bf_cbc}, + {"bf-cbc", &nss_bf_cbc}, + {"bf-ecb", &nss_bf_ecb}, + {NULL} +}; + +/* + * px_find_cipher + * + * Search for the requested cipher and see if there is support for it, and if + * so return an allocated object containing the playbook for how to encrypt + * and decrypt using the cipher. + */ +int +px_find_cipher(const char *alias, PX_Cipher **res) +{ + const nss_cipher_ref *cipher_ref; + PX_Cipher *px_cipher; + NSSInitParameters params; + NSSInitContext *nss_context; + nss_cipher *cipher; + internal_cipher *int_cipher; + + for (cipher_ref = nss_cipher_lookup; cipher_ref->name; cipher_ref++) + { + if (strcmp(cipher_ref->name, alias) == 0) + break; + } + + if (!cipher_ref->name) + return PXE_NO_CIPHER; + + /* + * Fill in the PX_Cipher to pass back to PX describing the operations to + * perform in order to use the cipher. + */ + px_cipher = palloc(sizeof(*px_cipher)); + px_cipher->block_size = cipher_ref->impl->get_block_size; + px_cipher->key_size = cipher_ref->impl->get_key_size; + px_cipher->iv_size = cipher_ref->impl->get_iv_size; + px_cipher->free = cipher_ref->impl->free; + px_cipher->init = cipher_ref->impl->init; + px_cipher->encrypt = cipher_ref->impl->encrypt; + px_cipher->decrypt = cipher_ref->impl->decrypt; + + /* + * Set the private data, which is different for NSS and internal ciphers + */ + if (cipher_ref->impl->is_nss) + { + /* + * Initialize our own NSS context without a database backing it. At + * some point we might want to allow users to use stored keys in the + * database rather than passing them via SELECT encrypt(), and then + * this need to be changed to open a user specified database. + */ + memset(¶ms, 0, sizeof(params)); + params.length = sizeof(params); + nss_context = NSS_InitContext("", "", "", "", ¶ms, + NSS_INIT_READONLY | NSS_INIT_NOCERTDB | + NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN | + NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD); + + /* nss_cipher is the private state struct for the operation */ + cipher = palloc(sizeof(*cipher)); + cipher->context = nss_context; + cipher->impl = cipher_ref->impl; + cipher->crypt_context = NULL; + + px_cipher->ptr = cipher; + } + else + { + int_cipher = palloc0(sizeof(*int_cipher)); + + int_cipher->impl = cipher_ref->impl; + px_cipher->ptr = int_cipher; + } + + *res = px_cipher; + return 0; +} diff --git a/contrib/pgcrypto/px.c b/contrib/pgcrypto/px.c index 6a4681dae9..a243f575d3 100644 --- a/contrib/pgcrypto/px.c +++ b/contrib/pgcrypto/px.c @@ -58,6 +58,7 @@ static const struct error_desc px_err_list[] = { {PXE_MCRYPT_INTERNAL, "mcrypt internal error"}, {PXE_NO_RANDOM, "Failed to generate strong random bits"}, {PXE_DECRYPT_FAILED, "Decryption failed"}, + {PXE_ENCRYPT_FAILED, "Encryption failed"}, {PXE_PGP_CORRUPT_DATA, "Wrong key or corrupt data"}, {PXE_PGP_CORRUPT_ARMOR, "Corrupt ascii-armor"}, {PXE_PGP_UNSUPPORTED_COMPR, "Unsupported compression algorithm"}, diff --git a/contrib/pgcrypto/px.h b/contrib/pgcrypto/px.h index 5487923edb..17d6f22498 100644 --- a/contrib/pgcrypto/px.h +++ b/contrib/pgcrypto/px.h @@ -61,6 +61,7 @@ #define PXE_MCRYPT_INTERNAL -16 #define PXE_NO_RANDOM -17 #define PXE_DECRYPT_FAILED -18 +#define PXE_ENCRYPT_FAILED -19 #define PXE_PGP_CORRUPT_DATA -100 #define PXE_PGP_CORRUPT_ARMOR -101 diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c index 5ba3988e27..84bb2c65b8 100644 --- a/contrib/sslinfo/sslinfo.c +++ b/contrib/sslinfo/sslinfo.c @@ -9,9 +9,11 @@ #include "postgres.h" +#ifdef USE_OPENSSL #include #include #include +#endif #include "access/htup_details.h" #include "funcapi.h" @@ -21,8 +23,8 @@ PG_MODULE_MAGIC; +#ifdef USE_OPENSSL static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName); -static Datum X509_NAME_to_text(X509_NAME *name); static Datum ASN1_STRING_to_text(ASN1_STRING *str); /* @@ -32,6 +34,7 @@ typedef struct { TupleDesc tupdesc; } SSLExtensionInfoContext; +#endif /* * Indicates whether current session uses SSL @@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version); Datum ssl_version(PG_FUNCTION_ARGS) { - if (MyProcPort->ssl == NULL) + const char *version; + + if (!MyProcPort->ssl_in_use) + PG_RETURN_NULL(); + + version = be_tls_get_version(MyProcPort); + if (version == NULL) PG_RETURN_NULL(); - PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl))); + + PG_RETURN_TEXT_P(cstring_to_text(version)); } @@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher); Datum ssl_cipher(PG_FUNCTION_ARGS) { - if (MyProcPort->ssl == NULL) + const char *cipher; + + if (!MyProcPort->ssl_in_use) PG_RETURN_NULL(); - PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl))); + + cipher = be_tls_get_cipher(MyProcPort); + if (cipher == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(cstring_to_text(cipher)); } @@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present); Datum ssl_client_cert_present(PG_FUNCTION_ARGS) { - PG_RETURN_BOOL(MyProcPort->peer != NULL); + PG_RETURN_BOOL(MyProcPort->peer_cert_valid); } @@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial); Datum ssl_client_serial(PG_FUNCTION_ARGS) { + char decimal[NAMEDATALEN]; Datum result; - Port *port = MyProcPort; - X509 *peer = port->peer; - ASN1_INTEGER *serial = NULL; - BIGNUM *b; - char *decimal; - if (!peer) + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN); + + if (!*decimal) PG_RETURN_NULL(); - serial = X509_get_serialNumber(peer); - b = ASN1_INTEGER_to_BN(serial, NULL); - decimal = BN_bn2dec(b); - BN_free(b); result = DirectFunctionCall3(numeric_in, CStringGetDatum(decimal), ObjectIdGetDatum(0), Int32GetDatum(-1)); - OPENSSL_free(decimal); return result; } +#ifdef USE_OPENSSL /* * Converts OpenSSL ASN1_STRING structure into text * @@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS) text *fieldname = PG_GETARG_TEXT_PP(0); Datum result; - if (!(MyProcPort->peer)) + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) PG_RETURN_NULL(); result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), fieldname); @@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS) else return result; } +#endif /* USE_OPENSSL */ - -/* - * Equivalent of X509_NAME_oneline that respects encoding - * - * This function converts X509_NAME structure to the text variable - * converting all textual data into current database encoding. - * - * Parameter: X509_NAME *name X509_NAME structure to be converted - * - * Returns: text datum which contains string representation of - * X509_NAME - */ -static Datum -X509_NAME_to_text(X509_NAME *name) +#ifdef USE_NSS +PG_FUNCTION_INFO_V1(ssl_client_dn_field); +Datum +ssl_client_dn_field(PG_FUNCTION_ARGS) { - BIO *membuf = BIO_new(BIO_s_mem()); - int i, - nid, - count = X509_NAME_entry_count(name); - X509_NAME_ENTRY *e; - ASN1_STRING *v; - const char *field_name; - size_t size; - char nullterm; - char *sp; - char *dp; - text *result; - - if (membuf == NULL) - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("could not create OpenSSL BIO structure"))); - - (void) BIO_set_close(membuf, BIO_CLOSE); - for (i = 0; i < count; i++) - { - e = X509_NAME_get_entry(name, i); - nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e)); - if (nid == NID_undef) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not get NID for ASN1_OBJECT object"))); - v = X509_NAME_ENTRY_get_data(e); - field_name = OBJ_nid2sn(nid); - if (field_name == NULL) - field_name = OBJ_nid2ln(nid); - if (field_name == NULL) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid))); - BIO_printf(membuf, "/%s=", field_name); - ASN1_STRING_print_ex(membuf, v, - ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB) - | ASN1_STRFLGS_UTF8_CONVERT)); - } - - /* ensure null termination of the BIO's content */ - nullterm = '\0'; - BIO_write(membuf, &nullterm, 1); - size = BIO_get_mem_data(membuf, &sp); - dp = pg_any_to_server(sp, size - 1, PG_UTF8); - result = cstring_to_text(dp); - if (dp != sp) - pfree(dp); - if (BIO_free(membuf) != 1) - elog(ERROR, "could not free OpenSSL BIO structure"); + PG_RETURN_NULL(); +} - PG_RETURN_TEXT_P(result); +PG_FUNCTION_INFO_V1(ssl_issuer_field); +Datum +ssl_issuer_field(PG_FUNCTION_ARGS) +{ + PG_RETURN_NULL(); } +#endif /* USE_NSS */ /* @@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn); Datum ssl_client_dn(PG_FUNCTION_ARGS) { - if (!(MyProcPort->peer)) + char subject[NAMEDATALEN]; + + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN); + + if (!*subject) PG_RETURN_NULL(); - return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer)); + + PG_RETURN_TEXT_P(cstring_to_text(subject)); } @@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn); Datum ssl_issuer_dn(PG_FUNCTION_ARGS) { - if (!(MyProcPort->peer)) + char issuer[NAMEDATALEN]; + + if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid) + PG_RETURN_NULL(); + + be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN); + + if (!*issuer) PG_RETURN_NULL(); - return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer)); + + PG_RETURN_TEXT_P(cstring_to_text(issuer)); } +#ifdef USE_OPENSSL /* * Returns information about available SSL extensions. * @@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS) /* All done */ SRF_RETURN_DONE(funcctx); } +#endif /* USE_OPENSSL */ + +#ifdef USE_NSS +PG_FUNCTION_INFO_V1(ssl_extension_info); +Datum +ssl_extension_info(PG_FUNCTION_ARGS) +{ + PG_RETURN_NULL(); +} +#endif /* USE_NSS */ diff --git a/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml index 8748c64e2d..f03e1f5fcf 100644 --- a/doc/src/sgml/pgcrypto.sgml +++ b/doc/src/sgml/pgcrypto.sgml @@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea sha224, sha256, sha384 and sha512. If pgcrypto was built with - OpenSSL, more algorithms are available, as - detailed in . + OpenSSL or NSS, + more algorithms are available, as + detailed in . @@ -763,7 +764,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256') Which cipher algorithm to use. -Values: bf, aes128, aes192, aes256 (OpenSSL-only: 3des, cast5) +Values: bf, aes128, aes192, aes256 (OpenSSL-only: 3des, cast5; NSS-only: 3des) Default: aes128 Applies to: pgp_sym_encrypt, pgp_pub_encrypt @@ -1152,8 +1153,8 @@ gen_random_uuid() returns uuid pgcrypto configures itself according to the findings of the main PostgreSQL configure script. The options that - affect it are --with-zlib and - --with-openssl. + affect it are --with-zlib, + --with-openssl and --with-nss. @@ -1168,14 +1169,16 @@ gen_random_uuid() returns uuid BIGNUM functions. - - Summary of Functionality with and without OpenSSL - +
+ Summary of Functionality with and without external cryptographic + library + Functionality Built-in With OpenSSL + With NSS @@ -1183,51 +1186,67 @@ gen_random_uuid() returns uuid MD5yesyes + yes SHA1 yes yes + yes SHA224/256/384/512 yes yes + yes Other digest algorithms no yes (Note 1) + yes (Note 1) Blowfish yes yes + yes AES yes yes + yes DES/3DES/CAST5 no yes + yes + + + CAST5 + no + yes + no Raw encryption yes yes + yes PGP Symmetric encryption yes yes + yes PGP Public-Key encryption yes yes + yes @@ -1240,7 +1259,8 @@ gen_random_uuid() returns uuid - Any digest algorithm OpenSSL supports + Any digest algorithm OpenSSL and + NSS supports is automatically picked up. This is not possible with ciphers, which need to be supported explicitly. diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml index e16f61b41d..e8a4e7b332 100644 --- a/doc/src/sgml/sslinfo.sgml +++ b/doc/src/sgml/sslinfo.sgml @@ -22,7 +22,8 @@ This extension won't build at all unless the installation was - configured with --with-openssl. + configured with SSL support, such as --with-openssl + or --with-nss. @@ -208,6 +209,9 @@ emailAddress the X.500 and X.509 standards, so you cannot just assign arbitrary meaning to them. + + This function is only available when using OpenSSL. + @@ -223,6 +227,9 @@ emailAddress Same as ssl_client_dn_field, but for the certificate issuer rather than the certificate subject. + + This function is only available when using OpenSSL. + @@ -238,6 +245,9 @@ emailAddress Provide information about extensions of client certificate: extension name, extension value, and if it is a critical extension. + + This function is only available when using OpenSSL. + diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm index ea3af48777..afe72a7e97 100644 --- a/src/tools/msvc/Install.pm +++ b/src/tools/msvc/Install.pm @@ -438,7 +438,8 @@ sub CopyContribFiles { # These configuration-based exclusions must match vcregress.pl next if ($d eq "uuid-ossp" && !defined($config->{uuid})); - next if ($d eq "sslinfo" && !defined($config->{openssl})); + next if ($d eq "sslinfo" && !defined($config->{openssl}) + && !defined($config->{nss})); next if ($d eq "xml2" && !defined($config->{xml})); next if ($d =~ /_plperl$/ && !defined($config->{perl})); next if ($d =~ /_plpython$/ && !defined($config->{python})); diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 29879b51af..379f861a56 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -442,9 +442,14 @@ sub mkvcbuild push @contrib_excludes, 'xml2'; } + if (!$solution->{options}->{openssl} && !$solution->{options}->{nss}) + { + push @contrib_excludes, 'sslinfo'; + } + if (!$solution->{options}->{openssl}) { - push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback'; + push @contrib_excludes, 'ssl_passphrase_callback'; } if (!$solution->{options}->{uuid}) @@ -474,6 +479,11 @@ sub mkvcbuild $pgcrypto->AddFiles('contrib/pgcrypto', 'openssl.c', 'pgp-mpi-openssl.c'); } + elsif ($solution->{options}->{nss}) + { + $pgcrypto->AddFiles('contrib/pgcrypto', 'nss.c', + 'pgp-mpi-internal.c', 'imath.c', 'blf.c'); + } else { $pgcrypto->AddFiles( -- 2.21.1 (Apple Git-122.3)