On Thu, Jan 06, 2022 at 11:40:04AM -0500, Tom Lane wrote:
> 1. It draws a cast-away-const warning.  We'd have to make the result
> of pg_cryptohash_error be "const char *", which would be better
> practice anyway, but that propagates into some other APIs and I didn't
> take the trouble to chase it to the end.

Yeah.  I wanted to switch all those routines to use a const here
anyway, just did not have the time to tackle that yesterday with the
rest of the issues I could think about.  Looking at that today, I
don't see any problems in switching to const in all those places, so
done this way (two places in crypt.c are more picky, though, for
logdetail).

> 2. It feels a bit bogus to be fetching ERR_get_error() at this point.
> Maybe it's all right to assume that the OpenSSL error stack won't
> change state before we get to pg_cryptohash_error, but I don't like
> the idea much.  I think it'd be better to capture ERR_get_error()
> sooner and store it in an additional field in pg_cryptohash_ctx.

Right, I forgot about the ERR_get_error() piece of it.  Thanks!  I'd
also rather have the check done just after the OpenSSL call.  If hash
computations are split across multiple code paths, this could lead to
issues.  We don't have any problems currently in the core code, but I
see no reason to not make that safer in the long run.  And the
structure is flexible enough, so that's not an issue.

> Also, I wonder if this shouldn't be unified with the SSLerrmessage()
> support found in be-secure-openssl.c and fe-secure-openssl.c.

Guess so.  HEAD could be poked at for this part.  I recall looking at
that once by that did not seem worth the complications.

I have also looked at the ABI part of the patch.  I cannot spot any
public trace of pg_md5_hash() and pg_md5_binary().  pgbouncer and
pgpool2 have each a copy of pg_md5_encrypt(), but they just share the
API name with PG, nothing more.  So that looks reasonably safe to
change.

The last thing I have on my notes is to assign logdetail in
md5_crypt_verify() and plain_crypt_verify() to feed back a LOG entry
to the postmaster on those failures, and saw that it is safe to assign
directly the error returned by the cryptohash APIs, avoiding the
extra psprintf call that could become an issue under memory pressure.

What do you think?
--
Michael
From 62ecfb1a77a58f6e056822e3a7a0b3052fcf9e46 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@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

Attachment: signature.asc
Description: PGP signature

Reply via email to