From 21f989bb9083f867446c0e964a13a6553adf6f75 Mon Sep 17 00:00:00 2001
From: Daniel Gustafsson <daniel@yesql.se>
Date: Mon, 18 Jan 2021 13:04:49 +0100
Subject: [PATCH v21 6/6] NSS cryptohash support

---
 src/common/Makefile         |   5 +
 src/common/cryptohash_nss.c | 243 ++++++++++++++++++++++++++++++++++++
 2 files changed, 248 insertions(+)
 create mode 100644 src/common/cryptohash_nss.c

diff --git a/src/common/Makefile b/src/common/Makefile
index 7a4b3dad5d..5a25e5b390 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -85,11 +85,16 @@ OBJS_COMMON += \
 	protocol_openssl.o \
 	cryptohash_openssl.o
 else
+ifeq ($(with_ssl),nss)
+OBJS_COMMON += \
+	cryptohash_nss.o
+else
 OBJS_COMMON += \
 	cryptohash.o \
 	md5.o \
 	sha2.o
 endif
+endif
 
 # A few files are currently only built for frontend, not server
 # (Mkvcbuild.pm has a copy of this list, too).  logging.c is excluded
diff --git a/src/common/cryptohash_nss.c b/src/common/cryptohash_nss.c
new file mode 100644
index 0000000000..9cd9c9f3f3
--- /dev/null
+++ b/src/common/cryptohash_nss.c
@@ -0,0 +1,243 @@
+/*-------------------------------------------------------------------------
+ *
+ * cryptohash_nss.c
+ *	  Set of wrapper routines on top of NSS to support cryptographic
+ *	  hash functions.
+ *
+ * This should only be used if code is compiled with NSS support.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *		  src/common/cryptohash_nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+/*
+ * 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 <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>
+
+/*
+ * 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
+
+#include "common/cryptohash.h"
+#ifndef FRONTEND
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+#include "utils/resowner_private.h"
+#endif
+
+/*
+ * Internal pg_cryptohash_ctx structure.
+ */
+struct pg_cryptohash_ctx
+{
+	pg_cryptohash_type type;
+
+	NSSInitContext *nss_context;
+	PK11Context *pk11_context;
+	HASH_HashType hash_type;
+
+#ifndef FRONTEND
+	ResourceOwner resowner;
+#endif
+};
+
+/*
+ * pg_cryptohash_create
+ *
+ * Create and allocate a new hash context. Returns NULL on failure in the
+ * frontend, and raise error without returning in the backend.
+ */
+pg_cryptohash_ctx *
+pg_cryptohash_create(pg_cryptohash_type type)
+{
+	NSSInitParameters params;
+	pg_cryptohash_ctx *ctx;
+	SECOidData *hash;
+
+#ifndef FRONTEND
+	/*
+	 * Make sure that the resource owner has space to remember this reference.
+	 * This can error out with "out of memory", so do this before any other
+	 * allocation to avoid leaking.
+	 */
+	ResourceOwnerEnlargeCryptoHash(CurrentResourceOwner);
+	ctx = MemoryContextAlloc(TopMemoryContext, sizeof(pg_cryptohash_ctx));
+#else
+	ctx = pg_malloc(sizeof(pg_cryptohash_ctx));
+#endif
+
+	if (!ctx)
+		goto error;
+
+	switch(type)
+	{
+		case PG_MD5:
+			ctx->hash_type = SEC_OID_MD5;
+			break;
+		case PG_SHA224:
+			ctx->hash_type = SEC_OID_SHA224;
+			break;
+		case PG_SHA256:
+			ctx->hash_type = SEC_OID_SHA256;
+			break;
+		case PG_SHA384:
+			ctx->hash_type = SEC_OID_SHA384;
+			break;
+		case PG_SHA512:
+			ctx->hash_type = SEC_OID_SHA512;
+			break;
+	}
+
+	/*
+	 * Initialize our own NSS context without a database backing it.
+	 */
+	memset(&params, 0, sizeof(params));
+	params.length = sizeof(params);
+	ctx->nss_context = NSS_InitContext("", "", "", "", &params,
+									   NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+									   NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+									   NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+	if (!ctx->nss_context)
+	{
+		explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+		pfree(ctx);
+		goto error;
+	}
+	ctx->type = type;
+	hash = SECOID_FindOIDByTag(ctx->hash_type);
+	ctx->pk11_context = PK11_CreateDigestContext(hash->offset);
+	if (!ctx->pk11_context)
+	{
+		explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+		pfree(ctx);
+		goto error;
+	}
+
+#ifndef FRONTEND
+	ctx->resowner = CurrentResourceOwner;
+	ResourceOwnerRememberCryptoHash(CurrentResourceOwner,
+									PointerGetDatum(ctx));
+#endif
+
+	return ctx;
+
+error:
+#ifndef FRONTEND
+	ereport(ERROR,
+			(errcode(ERRCODE_OUT_OF_MEMORY),
+			 errmsg("out of memory")));
+#endif
+	return NULL;
+}
+
+/*
+ * pg_cryptohash_init
+ *			Initialize the passed hash context
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_init(pg_cryptohash_ctx *ctx)
+{
+	SECStatus		status;
+
+	status = PK11_DigestBegin(ctx->pk11_context);
+
+	if (status != SECSuccess)
+		return 1;
+	return 0;
+}
+
+/*
+ * pg_cryptohash_update
+ *
+ * Update a hash context.  Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
+{
+	SECStatus	status;
+
+	if (ctx == NULL)
+		return -1;
+	
+	status = PK11_DigestOp(ctx->pk11_context, data, len);
+	if (status != SECSuccess)
+		return -1;
+	
+	return 0;
+}
+
+/*
+ * pg_cryptohash_final
+ *
+ * Finalize a hash context.  Returns 0 on success, -1 on failure.
+ */
+int
+pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest)
+{
+	SECStatus	status;
+	unsigned int outlen;
+	const SECHashObject *object;
+
+	object = HASH_GetHashObject(ctx->hash_type);
+	status = PK11_DigestFinal(ctx->pk11_context, dest, &outlen, object->length);
+
+	if (status != SECSuccess)
+		return -1;
+	return 0;
+}
+
+/*
+ * pg_cryptohash_free
+ *
+ * Free a hash context and the associated NSS context.
+ */
+void
+pg_cryptohash_free(pg_cryptohash_ctx *ctx)
+{
+	PRBool		free_context = PR_TRUE;
+	if (!ctx)
+		return;
+	
+	PK11_DestroyContext(ctx->pk11_context, free_context);
+#ifndef FRONTEND
+	ResourceOwnerForgetCryptoHash(ctx->resowner, PointerGetDatum(ctx));
+#endif
+	NSS_ShutdownContext(ctx->nss_context);
+	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
+	pfree(ctx);
+}
-- 
2.21.1 (Apple Git-122.3)

