From 62ecfb1a77a58f6e056822e3a7a0b3052fcf9e46 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 7 Jan 2022 11:08:17 +0900
Subject: [PATCH v2] Improve error reporting for cryptohashes

---
 src/include/common/cryptohash.h           |  1 +
 src/include/common/md5.h                  |  9 ++-
 src/backend/libpq/auth.c                  | 14 ++--
 src/backend/libpq/crypt.c                 | 40 +++++++-----
 src/backend/replication/backup_manifest.c |  9 ++-
 src/backend/utils/adt/cryptohashfuncs.c   | 25 ++++---
 src/common/cryptohash.c                   | 57 +++++++++++++++-
 src/common/cryptohash_openssl.c           | 80 +++++++++++++++++++++++
 src/common/md5_common.c                   | 20 ++++--
 src/interfaces/libpq/fe-auth.c            | 22 +++++--
 contrib/uuid-ossp/uuid-ossp.c             | 18 +++--
 11 files changed, 244 insertions(+), 51 deletions(-)

diff --git a/src/include/common/cryptohash.h b/src/include/common/cryptohash.h
index 541dc844c8..c62c350d57 100644
--- a/src/include/common/cryptohash.h
+++ b/src/include/common/cryptohash.h
@@ -34,5 +34,6 @@ extern int	pg_cryptohash_init(pg_cryptohash_ctx *ctx);
 extern int	pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len);
 extern int	pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len);
 extern void pg_cryptohash_free(pg_cryptohash_ctx *ctx);
+extern const char *pg_cryptohash_error(pg_cryptohash_ctx *ctx);
 
 #endif							/* PG_CRYPTOHASH_H */
diff --git a/src/include/common/md5.h b/src/include/common/md5.h
index 62a31e6ed4..cc43fc6606 100644
--- a/src/include/common/md5.h
+++ b/src/include/common/md5.h
@@ -26,9 +26,12 @@
 #define MD5_PASSWD_LEN	35
 
 /* Utilities common to all the MD5 implementations, as of md5_common.c */
-extern bool pg_md5_hash(const void *buff, size_t len, char *hexsum);
-extern bool pg_md5_binary(const void *buff, size_t len, void *outbuf);
+extern bool pg_md5_hash(const void *buff, size_t len, char *hexsum,
+						const char **errstr);
+extern bool pg_md5_binary(const void *buff, size_t len, void *outbuf,
+						  const char **errstr);
 extern bool pg_md5_encrypt(const char *passwd, const char *salt,
-						   size_t salt_len, char *buf);
+						   size_t salt_len, char *buf,
+						   const char **errstr);
 
 #endif							/* PG_MD5_H */
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 7bcf52523b..eea933f41e 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -3085,6 +3085,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 	md5trailer = packet->vector;
 	for (i = 0; i < encryptedpasswordlen; i += RADIUS_VECTOR_LENGTH)
 	{
+		const char   *errstr = NULL;
+
 		memcpy(cryptvector + strlen(secret), md5trailer, RADIUS_VECTOR_LENGTH);
 
 		/*
@@ -3093,10 +3095,12 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 		 */
 		md5trailer = encryptedpassword + i;
 
-		if (!pg_md5_binary(cryptvector, strlen(secret) + RADIUS_VECTOR_LENGTH, encryptedpassword + i))
+		if (!pg_md5_binary(cryptvector, strlen(secret) + RADIUS_VECTOR_LENGTH,
+						   encryptedpassword + i, &errstr))
 		{
 			ereport(LOG,
-					(errmsg("could not perform MD5 encryption of password")));
+					(errmsg("could not perform MD5 encryption of password: %s",
+							errstr)));
 			pfree(cryptvector);
 			pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
 			return STATUS_ERROR;
@@ -3181,6 +3185,7 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 		struct timeval timeout;
 		struct timeval now;
 		int64		timeoutval;
+		const char *errstr;
 
 		gettimeofday(&now, NULL);
 		timeoutval = (endtime.tv_sec * 1000000 + endtime.tv_usec) - (now.tv_sec * 1000000 + now.tv_usec);
@@ -3299,10 +3304,11 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 
 		if (!pg_md5_binary(cryptvector,
 						   packetlength + strlen(secret),
-						   encryptedpassword))
+						   encryptedpassword, &errstr))
 		{
 			ereport(LOG,
-					(errmsg("could not perform MD5 encryption of received packet")));
+					(errmsg("could not perform MD5 encryption of received packet: %s",
+							errstr)));
 			pfree(cryptvector);
 			continue;
 		}
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 3fcad991a7..b0d0dceafc 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -116,6 +116,7 @@ encrypt_password(PasswordType target_type, const char *role,
 {
 	PasswordType guessed_type = get_password_type(password);
 	char	   *encrypted_password;
+	const char *errstr = NULL;
 
 	if (guessed_type != PASSWORD_TYPE_PLAINTEXT)
 	{
@@ -132,8 +133,8 @@ encrypt_password(PasswordType target_type, const char *role,
 			encrypted_password = palloc(MD5_PASSWD_LEN + 1);
 
 			if (!pg_md5_encrypt(password, role, strlen(role),
-								encrypted_password))
-				elog(ERROR, "password encryption failed");
+								encrypted_password, &errstr))
+				elog(ERROR, "password encryption failed: %s", errstr);
 			return encrypted_password;
 
 		case PASSWORD_TYPE_SCRAM_SHA_256:
@@ -159,8 +160,9 @@ encrypt_password(PasswordType target_type, const char *role,
  * 'client_pass' is the response given by the remote user to the MD5 challenge.
  * 'md5_salt' is the salt used in the MD5 authentication challenge.
  *
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * In the error case, optionally save a string at *logdetail that will be
+ * sent to the postmaster log (but not the client).  Note that this may
+ * not be palloc()'d.
  */
 int
 md5_crypt_verify(const char *role, const char *shadow_pass,
@@ -170,6 +172,7 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
 {
 	int			retval;
 	char		crypt_pwd[MD5_PASSWD_LEN + 1];
+	const char *errstr = NULL;
 
 	Assert(md5_salt_len > 0);
 
@@ -183,16 +186,18 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
 
 	/*
 	 * Compute the correct answer for the MD5 challenge.
-	 *
-	 * We do not bother setting logdetail for any pg_md5_encrypt failure
-	 * below: the only possible error is out-of-memory, which is unlikely, and
-	 * if it did happen adding a psprintf call would only make things worse.
 	 */
 	/* stored password already encrypted, only do salt */
 	if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
 						md5_salt, md5_salt_len,
-						crypt_pwd))
+						crypt_pwd, &errstr))
 	{
+		/*
+		 * Note that this is not palloc()'d.  One possible outcome here
+		 * is an out-of-memory error, so adding an extra psprintf call
+		 * would only make things worse.
+		 */
+		*logdetail = (char *) errstr;
 		return STATUS_ERROR;
 	}
 
@@ -215,8 +220,9 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
  * pg_authid.rolpassword.
  * 'client_pass' is the password given by the remote user.
  *
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * In the error case, optionally store a string at *logdetail that will be
+ * sent to the postmaster log (but not the client).  Note that this may not
+ * be palloc()'d.
  */
 int
 plain_crypt_verify(const char *role, const char *shadow_pass,
@@ -224,6 +230,7 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
 				   char **logdetail)
 {
 	char		crypt_client_pass[MD5_PASSWD_LEN + 1];
+	const char *errstr = NULL;
 
 	/*
 	 * Client sent password in plaintext.  If we have an MD5 hash stored, hash
@@ -251,14 +258,15 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
 			if (!pg_md5_encrypt(client_pass,
 								role,
 								strlen(role),
-								crypt_client_pass))
+								crypt_client_pass,
+								&errstr))
 			{
 				/*
-				 * We do not bother setting logdetail for pg_md5_encrypt
-				 * failure: the only possible error is out-of-memory, which is
-				 * unlikely, and if it did happen adding a psprintf call would
-				 * only make things worse.
+				 * Note that this is not palloc()'d.  One possible outcome
+				 * here is an out-of-memory error, so adding an extra
+				 * psprintf call would only make things worse.
 				 */
+				*logdetail = (char *) errstr;
 				return STATUS_ERROR;
 			}
 			if (strcmp(crypt_client_pass, shadow_pass) == 0)
diff --git a/src/backend/replication/backup_manifest.c b/src/backend/replication/backup_manifest.c
index 4fe11a3b5c..f29d66b719 100644
--- a/src/backend/replication/backup_manifest.c
+++ b/src/backend/replication/backup_manifest.c
@@ -68,7 +68,8 @@ InitializeBackupManifest(backup_manifest_info *manifest,
 		manifest->buffile = BufFileCreateTemp(false);
 		manifest->manifest_ctx = pg_cryptohash_create(PG_SHA256);
 		if (pg_cryptohash_init(manifest->manifest_ctx) < 0)
-			elog(ERROR, "failed to initialize checksum of backup manifest");
+			elog(ERROR, "failed to initialize checksum of backup manifest: %s",
+				 pg_cryptohash_error(manifest->manifest_ctx));
 	}
 
 	manifest->manifest_size = UINT64CONST(0);
@@ -334,7 +335,8 @@ SendBackupManifest(backup_manifest_info *manifest, bbsink *sink)
 	manifest->still_checksumming = false;
 	if (pg_cryptohash_final(manifest->manifest_ctx, checksumbuf,
 							sizeof(checksumbuf)) < 0)
-		elog(ERROR, "failed to finalize checksum of backup manifest");
+		elog(ERROR, "failed to finalize checksum of backup manifest: %s",
+			 pg_cryptohash_error(manifest->manifest_ctx));
 	AppendStringToManifest(manifest, "\"Manifest-Checksum\": \"");
 
 	hex_encode((char *) checksumbuf, sizeof checksumbuf, checksumstringbuf);
@@ -391,7 +393,8 @@ AppendStringToManifest(backup_manifest_info *manifest, char *s)
 	if (manifest->still_checksumming)
 	{
 		if (pg_cryptohash_update(manifest->manifest_ctx, (uint8 *) s, len) < 0)
-			elog(ERROR, "failed to update checksum of backup manifest");
+			elog(ERROR, "failed to update checksum of backup manifest: %s",
+				 pg_cryptohash_error(manifest->manifest_ctx));
 	}
 	BufFileWrite(manifest->buffile, s, len);
 	manifest->manifest_size += len;
diff --git a/src/backend/utils/adt/cryptohashfuncs.c b/src/backend/utils/adt/cryptohashfuncs.c
index 6a0f0258e6..c977db2f0e 100644
--- a/src/backend/utils/adt/cryptohashfuncs.c
+++ b/src/backend/utils/adt/cryptohashfuncs.c
@@ -35,15 +35,17 @@ md5_text(PG_FUNCTION_ARGS)
 	text	   *in_text = PG_GETARG_TEXT_PP(0);
 	size_t		len;
 	char		hexsum[MD5_HASH_LEN + 1];
+	const char *errstr = NULL;
 
 	/* Calculate the length of the buffer using varlena metadata */
 	len = VARSIZE_ANY_EXHDR(in_text);
 
 	/* get the hash result */
-	if (pg_md5_hash(VARDATA_ANY(in_text), len, hexsum) == false)
+	if (pg_md5_hash(VARDATA_ANY(in_text), len, hexsum, &errstr) == false)
 		ereport(ERROR,
-				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("out of memory")));
+				(errcode(ERRCODE_INTERNAL_ERROR),
+				 errmsg("could not compute %s hash: %s", "MD5",
+						errstr)));
 
 	/* convert to text and return it */
 	PG_RETURN_TEXT_P(cstring_to_text(hexsum));
@@ -58,12 +60,14 @@ md5_bytea(PG_FUNCTION_ARGS)
 	bytea	   *in = PG_GETARG_BYTEA_PP(0);
 	size_t		len;
 	char		hexsum[MD5_HASH_LEN + 1];
+	const char *errstr = NULL;
 
 	len = VARSIZE_ANY_EXHDR(in);
-	if (pg_md5_hash(VARDATA_ANY(in), len, hexsum) == false)
+	if (pg_md5_hash(VARDATA_ANY(in), len, hexsum, &errstr) == false)
 		ereport(ERROR,
-				(errcode(ERRCODE_OUT_OF_MEMORY),
-				 errmsg("out of memory")));
+				(errcode(ERRCODE_INTERNAL_ERROR),
+				 errmsg("could not compute %s hash: %s", "MD5",
+						errstr)));
 
 	PG_RETURN_TEXT_P(cstring_to_text(hexsum));
 }
@@ -111,12 +115,15 @@ cryptohash_internal(pg_cryptohash_type type, bytea *input)
 
 	ctx = pg_cryptohash_create(type);
 	if (pg_cryptohash_init(ctx) < 0)
-		elog(ERROR, "could not initialize %s context", typestr);
+		elog(ERROR, "could not initialize %s context: %s", typestr,
+			 pg_cryptohash_error(ctx));
 	if (pg_cryptohash_update(ctx, data, len) < 0)
-		elog(ERROR, "could not update %s context", typestr);
+		elog(ERROR, "could not update %s context: %s", typestr,
+			 pg_cryptohash_error(ctx));
 	if (pg_cryptohash_final(ctx, (unsigned char *) VARDATA(result),
 							digest_len) < 0)
-		elog(ERROR, "could not finalize %s context", typestr);
+		elog(ERROR, "could not finalize %s context: %s", typestr,
+			 pg_cryptohash_error(ctx));
 	pg_cryptohash_free(ctx);
 
 	SET_VARSIZE(result, digest_len + VARHDRSZ);
diff --git a/src/common/cryptohash.c b/src/common/cryptohash.c
index 0dab74a094..30bbc2e2b6 100644
--- a/src/common/cryptohash.c
+++ b/src/common/cryptohash.c
@@ -40,10 +40,18 @@
 #define FREE(ptr) free(ptr)
 #endif
 
+/* Set of error states */
+typedef enum pg_cryptohash_errno
+{
+	PG_CRYPTOHASH_ERROR_NONE = 0,
+	PG_CRYPTOHASH_ERROR_DEST_LEN
+} pg_cryptohash_errno;
+
 /* Internal pg_cryptohash_ctx structure */
 struct pg_cryptohash_ctx
 {
 	pg_cryptohash_type type;
+	pg_cryptohash_errno	error;
 
 	union
 	{
@@ -76,9 +84,10 @@ pg_cryptohash_create(pg_cryptohash_type type)
 	ctx = ALLOC(sizeof(pg_cryptohash_ctx));
 	if (ctx == NULL)
 		return NULL;
+
 	memset(ctx, 0, sizeof(pg_cryptohash_ctx));
 	ctx->type = type;
-
+	ctx->error = PG_CRYPTOHASH_ERROR_NONE;
 	return ctx;
 }
 
@@ -174,32 +183,50 @@ pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
 	{
 		case PG_MD5:
 			if (len < MD5_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			pg_md5_final(&ctx->data.md5, dest);
 			break;
 		case PG_SHA1:
 			if (len < SHA1_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			pg_sha1_final(&ctx->data.sha1, dest);
 			break;
 		case PG_SHA224:
 			if (len < PG_SHA224_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			pg_sha224_final(&ctx->data.sha224, dest);
 			break;
 		case PG_SHA256:
 			if (len < PG_SHA256_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			pg_sha256_final(&ctx->data.sha256, dest);
 			break;
 		case PG_SHA384:
 			if (len < PG_SHA384_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			pg_sha384_final(&ctx->data.sha384, dest);
 			break;
 		case PG_SHA512:
 			if (len < PG_SHA512_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			pg_sha512_final(&ctx->data.sha512, dest);
 			break;
 	}
@@ -221,3 +248,31 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
 	FREE(ctx);
 }
+
+/*
+ * pg_cryptohash_error
+ *
+ * Returns a static string providing errors about an error that
+ * happened during a computation.
+ */
+const char *
+pg_cryptohash_error(pg_cryptohash_ctx *ctx)
+{
+	/*
+	 * This implementation would never fail because of an out-of-memory
+	 * error, except when creating the context.
+	 */
+	if (ctx == NULL)
+		return _("out of memory");
+
+	switch (ctx->error)
+	{
+		case PG_CRYPTOHASH_ERROR_NONE:
+			return _("success");
+		case PG_CRYPTOHASH_ERROR_DEST_LEN:
+			return _("destination buffer too small");
+	}
+
+	Assert(false);
+	return NULL;
+}
diff --git a/src/common/cryptohash_openssl.c b/src/common/cryptohash_openssl.c
index 643cc7aea2..fdb22c56e8 100644
--- a/src/common/cryptohash_openssl.c
+++ b/src/common/cryptohash_openssl.c
@@ -21,6 +21,7 @@
 #include "postgres_fe.h"
 #endif
 
+#include <openssl/err.h>
 #include <openssl/evp.h>
 
 #include "common/cryptohash.h"
@@ -46,6 +47,14 @@
 #define FREE(ptr) free(ptr)
 #endif
 
+/* Set of error states */
+typedef enum pg_cryptohash_errno
+{
+	PG_CRYPTOHASH_ERROR_NONE = 0,
+	PG_CRYPTOHASH_ERROR_DEST_LEN,
+	PG_CRYPTOHASH_ERROR_OPENSSL
+} pg_cryptohash_errno;
+
 /*
  * Internal pg_cryptohash_ctx structure.
  *
@@ -55,6 +64,8 @@
 struct pg_cryptohash_ctx
 {
 	pg_cryptohash_type type;
+	pg_cryptohash_errno error;
+	const char *errreason;
 
 	EVP_MD_CTX *evpctx;
 
@@ -88,6 +99,8 @@ pg_cryptohash_create(pg_cryptohash_type type)
 		return NULL;
 	memset(ctx, 0, sizeof(pg_cryptohash_ctx));
 	ctx->type = type;
+	ctx->error = PG_CRYPTOHASH_ERROR_NONE;
+	ctx->errreason = NULL;
 
 	/*
 	 * Initialization takes care of assigning the correct type for OpenSSL.
@@ -153,7 +166,11 @@ pg_cryptohash_init(pg_cryptohash_ctx *ctx)
 
 	/* OpenSSL internals return 1 on success, 0 on failure */
 	if (status <= 0)
+	{
+		ctx->errreason = ERR_reason_error_string(ERR_get_error());
+		ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL;
 		return -1;
+	}
 	return 0;
 }
 
@@ -174,7 +191,11 @@ pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
 
 	/* OpenSSL internals return 1 on success, 0 on failure */
 	if (status <= 0)
+	{
+		ctx->errreason = ERR_reason_error_string(ERR_get_error());
+		ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL;
 		return -1;
+	}
 	return 0;
 }
 
@@ -195,27 +216,45 @@ pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
 	{
 		case PG_MD5:
 			if (len < MD5_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			break;
 		case PG_SHA1:
 			if (len < SHA1_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			break;
 		case PG_SHA224:
 			if (len < PG_SHA224_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			break;
 		case PG_SHA256:
 			if (len < PG_SHA256_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			break;
 		case PG_SHA384:
 			if (len < PG_SHA384_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			break;
 		case PG_SHA512:
 			if (len < PG_SHA512_DIGEST_LENGTH)
+			{
+				ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
 				return -1;
+			}
 			break;
 	}
 
@@ -223,7 +262,11 @@ pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
 
 	/* OpenSSL internals return 1 on success, 0 on failure */
 	if (status <= 0)
+	{
+		ctx->errreason = ERR_reason_error_string(ERR_get_error());
+		ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL;
 		return -1;
+	}
 	return 0;
 }
 
@@ -248,3 +291,40 @@ pg_cryptohash_free(pg_cryptohash_ctx *ctx)
 	explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
 	FREE(ctx);
 }
+
+/*
+ * pg_cryptohash_error
+ *
+ * Returns a static string providing errors about an error that
+ * happened during a computation.
+ */
+const char *
+pg_cryptohash_error(pg_cryptohash_ctx *ctx)
+{
+	/*
+	 * This implementation would never fail because of an out-of-memory
+	 * error, except when creating the context.
+	 */
+	if (ctx == NULL)
+		return _("out of memory");
+
+	/*
+	 * If a reason is provided, rely on it, else fallback to any error
+	 * code set.
+	 */
+	if (ctx->errreason)
+		return ctx->errreason;
+
+	switch (ctx->error)
+	{
+		case PG_CRYPTOHASH_ERROR_NONE:
+			return _("success");
+		case PG_CRYPTOHASH_ERROR_DEST_LEN:
+			return _("destination buffer too small");
+		case PG_CRYPTOHASH_ERROR_OPENSSL:
+			return _("OpenSSL failure");
+	}
+
+	Assert(false);	/* cannot be reached */
+	return NULL;
+}
diff --git a/src/common/md5_common.c b/src/common/md5_common.c
index 2114890eff..382c2d0b8c 100644
--- a/src/common/md5_common.c
+++ b/src/common/md5_common.c
@@ -67,7 +67,7 @@ bytesToHex(uint8 b[16], char *s)
  */
 
 bool
-pg_md5_hash(const void *buff, size_t len, char *hexsum)
+pg_md5_hash(const void *buff, size_t len, char *hexsum, const char **errstr)
 {
 	uint8		sum[MD5_DIGEST_LENGTH];
 	pg_cryptohash_ctx *ctx;
@@ -80,6 +80,7 @@ pg_md5_hash(const void *buff, size_t len, char *hexsum)
 		pg_cryptohash_update(ctx, buff, len) < 0 ||
 		pg_cryptohash_final(ctx, sum, sizeof(sum)) < 0)
 	{
+		*errstr = pg_cryptohash_error(ctx);
 		pg_cryptohash_free(ctx);
 		return false;
 	}
@@ -90,18 +91,23 @@ pg_md5_hash(const void *buff, size_t len, char *hexsum)
 }
 
 bool
-pg_md5_binary(const void *buff, size_t len, void *outbuf)
+pg_md5_binary(const void *buff, size_t len, void *outbuf, const char **errstr)
 {
 	pg_cryptohash_ctx *ctx;
 
+	*errstr = NULL;
 	ctx = pg_cryptohash_create(PG_MD5);
 	if (ctx == NULL)
+	{
+		*errstr = pg_cryptohash_error(NULL);	/* returns OOM */
 		return false;
+	}
 
 	if (pg_cryptohash_init(ctx) < 0 ||
 		pg_cryptohash_update(ctx, buff, len) < 0 ||
 		pg_cryptohash_final(ctx, outbuf, MD5_DIGEST_LENGTH) < 0)
 	{
+		*errstr = pg_cryptohash_error(ctx);
 		pg_cryptohash_free(ctx);
 		return false;
 	}
@@ -118,11 +124,12 @@ pg_md5_binary(const void *buff, size_t len, void *outbuf)
  * Output format is "md5" followed by a 32-hex-digit MD5 checksum.
  * Hence, the output buffer "buf" must be at least 36 bytes long.
  *
- * Returns true if okay, false on error (out of memory).
+ * Returns true if okay, false on error with *errstr providing some
+ * error context.
  */
 bool
 pg_md5_encrypt(const char *passwd, const char *salt, size_t salt_len,
-			   char *buf)
+			   char *buf, const char **errstr)
 {
 	size_t		passwd_len = strlen(passwd);
 
@@ -131,7 +138,10 @@ pg_md5_encrypt(const char *passwd, const char *salt, size_t salt_len,
 	bool		ret;
 
 	if (!crypt_buf)
+	{
+		*errstr = _("out of memory");
 		return false;
+	}
 
 	/*
 	 * Place salt at the end because it may be known by users trying to crack
@@ -141,7 +151,7 @@ pg_md5_encrypt(const char *passwd, const char *salt, size_t salt_len,
 	memcpy(crypt_buf + passwd_len, salt, salt_len);
 
 	strcpy(buf, "md5");
-	ret = pg_md5_hash(crypt_buf, passwd_len + salt_len, buf + 3);
+	ret = pg_md5_hash(crypt_buf, passwd_len + salt_len, buf + 3, errstr);
 
 	free(crypt_buf);
 
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 3421ed4685..44ea532d70 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -790,6 +790,7 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
 		case AUTH_REQ_MD5:
 			{
 				char	   *crypt_pwd2;
+				const char *errstr;
 
 				/* Allocate enough space for two MD5 hashes */
 				crypt_pwd = malloc(2 * (MD5_PASSWD_LEN + 1));
@@ -802,14 +803,21 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
 
 				crypt_pwd2 = crypt_pwd + MD5_PASSWD_LEN + 1;
 				if (!pg_md5_encrypt(password, conn->pguser,
-									strlen(conn->pguser), crypt_pwd2))
+									strlen(conn->pguser), crypt_pwd2,
+									&errstr))
 				{
+					appendPQExpBuffer(&conn->errorMessage,
+									  libpq_gettext("could not encrypt password: %s\n"),
+									  errstr);
 					free(crypt_pwd);
 					return STATUS_ERROR;
 				}
 				if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"), md5Salt,
-									4, crypt_pwd))
+									4, crypt_pwd, &errstr))
 				{
+					appendPQExpBuffer(&conn->errorMessage,
+									  libpq_gettext("could not encrypt password: %s\n"),
+									  errstr);
 					free(crypt_pwd);
 					return STATUS_ERROR;
 				}
@@ -1175,12 +1183,13 @@ char *
 PQencryptPassword(const char *passwd, const char *user)
 {
 	char	   *crypt_pwd;
+	const char *errstr = NULL;
 
 	crypt_pwd = malloc(MD5_PASSWD_LEN + 1);
 	if (!crypt_pwd)
 		return NULL;
 
-	if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd))
+	if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd, &errstr))
 	{
 		free(crypt_pwd);
 		return NULL;
@@ -1287,8 +1296,13 @@ PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
 		crypt_pwd = malloc(MD5_PASSWD_LEN + 1);
 		if (crypt_pwd)
 		{
-			if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd))
+			const char *errstr = NULL;
+
+			if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd, &errstr))
 			{
+				appendPQExpBuffer(&conn->errorMessage,
+								  libpq_gettext("could not encrypt password: %s\n"),
+								  errstr);
 				free(crypt_pwd);
 				crypt_pwd = NULL;
 			}
diff --git a/contrib/uuid-ossp/uuid-ossp.c b/contrib/uuid-ossp/uuid-ossp.c
index f9335f2863..f52f007a08 100644
--- a/contrib/uuid-ossp/uuid-ossp.c
+++ b/contrib/uuid-ossp/uuid-ossp.c
@@ -319,14 +319,17 @@ uuid_generate_internal(int v, unsigned char *ns, const char *ptr, int len)
 					pg_cryptohash_ctx *ctx = pg_cryptohash_create(PG_MD5);
 
 					if (pg_cryptohash_init(ctx) < 0)
-						elog(ERROR, "could not initialize %s context", "MD5");
+						elog(ERROR, "could not initialize %s context: %s", "MD5",
+							 pg_cryptohash_error(ctx));
 					if (pg_cryptohash_update(ctx, ns, sizeof(uu)) < 0 ||
 						pg_cryptohash_update(ctx, (unsigned char *) ptr, len) < 0)
-						elog(ERROR, "could not update %s context", "MD5");
+						elog(ERROR, "could not update %s context: %s", "MD5",
+							 pg_cryptohash_error(ctx));
 					/* we assume sizeof MD5 result is 16, same as UUID size */
 					if (pg_cryptohash_final(ctx, (unsigned char *) &uu,
 											sizeof(uu)) < 0)
-						elog(ERROR, "could not finalize %s context", "MD5");
+						elog(ERROR, "could not finalize %s context: %s", "MD5",
+							 pg_cryptohash_error(ctx));
 					pg_cryptohash_free(ctx);
 				}
 				else
@@ -335,12 +338,15 @@ uuid_generate_internal(int v, unsigned char *ns, const char *ptr, int len)
 					unsigned char sha1result[SHA1_DIGEST_LENGTH];
 
 					if (pg_cryptohash_init(ctx) < 0)
-						elog(ERROR, "could not initialize %s context", "SHA1");
+						elog(ERROR, "could not initialize %s context: %s", "SHA1",
+							 pg_cryptohash_error(ctx));
 					if (pg_cryptohash_update(ctx, ns, sizeof(uu)) < 0 ||
 						pg_cryptohash_update(ctx, (unsigned char *) ptr, len) < 0)
-						elog(ERROR, "could not update %s context", "SHA1");
+						elog(ERROR, "could not update %s context: %s", "SHA1",
+							 pg_cryptohash_error(ctx));
 					if (pg_cryptohash_final(ctx, sha1result, sizeof(sha1result)) < 0)
-						elog(ERROR, "could not finalize %s context", "SHA1");
+						elog(ERROR, "could not finalize %s context: %s", "SHA1",
+							 pg_cryptohash_error(ctx));
 					pg_cryptohash_free(ctx);
 
 					memcpy(&uu, sha1result, sizeof(uu));
-- 
2.34.1

