From 72fc8feb103d59070da4410354ec97c44dc22eb1 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 8 Feb 2021 23:52:45 +0100
Subject: [PATCH v36 6/9] nss: Support NSS in pgcrypto

This extends pgcrypto to be able to use libnss as a cryptographic
backend for pgcrypto much like how OpenSSL is a supported backend.
Blowfish is not a supported cipher in NSS, so the implementation
falls back on the built-in BF code to be compatible in terms of
cipher support.
---
 contrib/pgcrypto/Makefile  |   7 +-
 contrib/pgcrypto/nss.c     | 753 +++++++++++++++++++++++++++++++++++++
 doc/src/sgml/pgcrypto.sgml |  49 ++-
 3 files changed, 795 insertions(+), 14 deletions(-)
 create mode 100644 contrib/pgcrypto/nss.c

diff --git a/contrib/pgcrypto/Makefile b/contrib/pgcrypto/Makefile
index c0b4f1fcf6..ede7a78146 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 openssl,,$(with_ssl)), $(INT_SRCS), $(OSSL_SRCS))
-CF_TESTS = $(if $(subst openssl,,$(with_ssl)), $(INT_TESTS), $(OSSL_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..d4fd4680ec
--- /dev/null
+++ b/contrib/pgcrypto/nss.c
@@ -0,0 +1,753 @@
+/*-------------------------------------------------------------------------
+ *
+ * nss.c
+ *	  Wrapper for using NSS as a backend for pgcrypto PX
+ *
+ *
+ * Portions Copyright (c) 1996-2021, 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 "common/nss.h"
+#include "utils/memutils.h"
+
+#include <nss/nss.h>
+#include <nss/hasht.h>
+#include <nss/pk11func.h>
+#include <nss/pk11pub.h>
+#include <nss/secitem.h>
+#include <nss/sechash.h>
+#include <nss/secoid.h>
+#include <nss/secerr.h>
+
+/*
+ * 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(&params, 0, sizeof(params));
+	params.length = sizeof(params);
+	nss_context = NSS_InitContext("", "", "", "", &params,
+								  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 publicly 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(&params, 0, sizeof(params));
+		params.length = sizeof(params);
+		nss_context = NSS_InitContext("", "", "", "", &params,
+									  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/doc/src/sgml/pgcrypto.sgml b/doc/src/sgml/pgcrypto.sgml
index 13770dfc6f..1896eda9e3 100644
--- a/doc/src/sgml/pgcrypto.sgml
+++ b/doc/src/sgml/pgcrypto.sgml
@@ -45,8 +45,9 @@ digest(data bytea, type text) returns bytea
     <literal>sha224</literal>, <literal>sha256</literal>,
     <literal>sha384</literal> and <literal>sha512</literal>.
     If <filename>pgcrypto</filename> was built with
-    <productname>OpenSSL</productname>, more algorithms are available, as
-    detailed in <xref linkend="pgcrypto-with-without-openssl"/>.
+    <productname>OpenSSL</productname> or <productname>NSS</productname>,
+    more algorithms are available, as
+    detailed in <xref linkend="pgcrypto-with-without-openssl-nss"/>.
    </para>
 
    <para>
@@ -764,7 +765,7 @@ pgp_sym_encrypt(data, psw, 'compress-algo=1, cipher-algo=aes256')
     Which cipher algorithm to use.
    </para>
 <literallayout>
-Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>)
+Values: bf, aes128, aes192, aes256 (OpenSSL-only: <literal>3des</literal>, <literal>cast5</literal>; NSS-only: <literal>3des</literal>)
 Default: aes128
 Applies to: pgp_sym_encrypt, pgp_pub_encrypt
 </literallayout>
@@ -1153,8 +1154,8 @@ gen_random_uuid() returns uuid
    <para>
     <filename>pgcrypto</filename> configures itself according to the findings of the
     main PostgreSQL <literal>configure</literal> script.  The options that
-    affect it are <literal>--with-zlib</literal> and
-    <literal>--with-ssl=openssl</literal>.
+    affect it are <literal>--with-zlib</literal>,
+    <literal>--with-ssl=openssl</literal> and <literal>--with-ssl=nss</literal>.
    </para>
 
    <para>
@@ -1169,14 +1170,16 @@ gen_random_uuid() returns uuid
     BIGNUM functions.
    </para>
 
-   <table id="pgcrypto-with-without-openssl">
-    <title>Summary of Functionality with and without OpenSSL</title>
-    <tgroup cols="3">
+   <table id="pgcrypto-with-without-openssl-nss">
+    <title>Summary of Functionality with and without external cryptographic
+      library</title>
+    <tgroup cols="4">
      <thead>
       <row>
        <entry>Functionality</entry>
        <entry>Built-in</entry>
        <entry>With OpenSSL</entry>
+       <entry>With NSS</entry>
       </row>
      </thead>
      <tbody>
@@ -1184,51 +1187,67 @@ gen_random_uuid() returns uuid
        <entry>MD5</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
        <entry>SHA1</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
        <entry>SHA224/256/384/512</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
        <entry>Other digest algorithms</entry>
        <entry>no</entry>
        <entry>yes (Note 1)</entry>
+       <entry>yes (Note 1)</entry>
       </row>
       <row>
        <entry>Blowfish</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes (Note 2)</entry>
       </row>
       <row>
        <entry>AES</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
-       <entry>DES/3DES/CAST5</entry>
+       <entry>DES/3DES</entry>
        <entry>no</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
+      </row>
+      <row>
+       <entry>CAST5</entry>
+       <entry>no</entry>
+       <entry>yes</entry>
+       <entry>no</entry>
       </row>
       <row>
        <entry>Raw encryption</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
        <entry>PGP Symmetric encryption</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
       <row>
        <entry>PGP Public-Key encryption</entry>
        <entry>yes</entry>
        <entry>yes</entry>
+       <entry>yes</entry>
       </row>
      </tbody>
     </tgroup>
@@ -1241,12 +1260,18 @@ gen_random_uuid() returns uuid
    <orderedlist>
     <listitem>
      <para>
-      Any digest algorithm <productname>OpenSSL</productname> supports
-      is automatically picked up.
-      This is not possible with ciphers, which need to be supported
+      Any digest algorithm included with the library that
+      <productname>PostgreSQL</productname> is compiled with is automatically
+      picked up. This is not possible with ciphers, which need to be supported
       explicitly.
      </para>
     </listitem>
+    <listitem>
+     <para>
+      Blowfish is not supported by <productname>NSS</productname>, the
+      built-in implementation is used as a fallback.
+     </para>
+    </listitem>
    </orderedlist>
   </sect3>
 
-- 
2.30.1 (Apple Git-130)

