Skip to content

Commit e6bdfd9

Browse files
committed
Refactor HMAC implementations
Similarly to the cryptohash implementations, this refactors the existing HMAC code into a single set of APIs that can be plugged with any crypto libraries PostgreSQL is built with (only OpenSSL currently). If there is no such libraries, a fallback implementation is available. Those new APIs are designed similarly to the existing cryptohash layer, so there is no real new design here, with the same logic around buffer bound checks and memory handling. HMAC has a dependency on cryptohashes, so all the cryptohash types supported by cryptohash{_openssl}.c can be used with HMAC. This refactoring is an advantage mainly for SCRAM, that included its own implementation of HMAC with SHA256 without relying on the existing crypto libraries even if PostgreSQL was built with their support. This code has been tested on Windows and Linux, with and without OpenSSL, across all the versions supported on HEAD from 1.1.1 down to 1.0.1. I have also checked that the implementations are working fine using some sample results, a custom extension of my own, and doing cross-checks across different major versions with SCRAM with the client and the backend. Author: Michael Paquier Reviewed-by: Bruce Momjian Discussion: https://postgr.es/m/[email protected]
1 parent 1d9c5d0 commit e6bdfd9

File tree

18 files changed

+749
-200
lines changed

18 files changed

+749
-200
lines changed

configure

+1-1
Original file line numberDiff line numberDiff line change
@@ -12642,7 +12642,7 @@ done
1264212642
# defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it
1264312643
# doesn't have these OpenSSL 1.1.0 functions. So check for individual
1264412644
# functions.
12645-
for ac_func in OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data
12645+
for ac_func in OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data HMAC_CTX_new HMAC_CTX_free
1264612646
do :
1264712647
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
1264812648
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"

configure.ac

+1-1
Original file line numberDiff line numberDiff line change
@@ -1258,7 +1258,7 @@ if test "$with_ssl" = openssl ; then
12581258
# defines OPENSSL_VERSION_NUMBER to claim version 2.0.0, even though it
12591259
# doesn't have these OpenSSL 1.1.0 functions. So check for individual
12601260
# functions.
1261-
AC_CHECK_FUNCS([OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data])
1261+
AC_CHECK_FUNCS([OPENSSL_init_ssl BIO_get_data BIO_meth_new ASN1_STRING_get0_data HMAC_CTX_new HMAC_CTX_free])
12621262
# OpenSSL versions before 1.1.0 required setting callback functions, for
12631263
# thread-safety. In 1.1.0, it's no longer required, and CRYPTO_lock()
12641264
# function was removed.

src/backend/libpq/auth-scram.c

+33-28
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
#include "catalog/pg_authid.h"
9696
#include "catalog/pg_control.h"
9797
#include "common/base64.h"
98+
#include "common/hmac.h"
9899
#include "common/saslprep.h"
99100
#include "common/scram-common.h"
100101
#include "common/sha2.h"
@@ -1100,31 +1101,33 @@ verify_client_proof(scram_state *state)
11001101
uint8 ClientSignature[SCRAM_KEY_LEN];
11011102
uint8 ClientKey[SCRAM_KEY_LEN];
11021103
uint8 client_StoredKey[SCRAM_KEY_LEN];
1103-
scram_HMAC_ctx ctx;
1104+
pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
11041105
int i;
11051106

11061107
/*
11071108
* Calculate ClientSignature. Note that we don't log directly a failure
11081109
* here even when processing the calculations as this could involve a mock
11091110
* authentication.
11101111
*/
1111-
if (scram_HMAC_init(&ctx, state->StoredKey, SCRAM_KEY_LEN) < 0 ||
1112-
scram_HMAC_update(&ctx,
1113-
state->client_first_message_bare,
1114-
strlen(state->client_first_message_bare)) < 0 ||
1115-
scram_HMAC_update(&ctx, ",", 1) < 0 ||
1116-
scram_HMAC_update(&ctx,
1117-
state->server_first_message,
1118-
strlen(state->server_first_message)) < 0 ||
1119-
scram_HMAC_update(&ctx, ",", 1) < 0 ||
1120-
scram_HMAC_update(&ctx,
1121-
state->client_final_message_without_proof,
1122-
strlen(state->client_final_message_without_proof)) < 0 ||
1123-
scram_HMAC_final(ClientSignature, &ctx) < 0)
1112+
if (pg_hmac_init(ctx, state->StoredKey, SCRAM_KEY_LEN) < 0 ||
1113+
pg_hmac_update(ctx,
1114+
(uint8 *) state->client_first_message_bare,
1115+
strlen(state->client_first_message_bare)) < 0 ||
1116+
pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
1117+
pg_hmac_update(ctx,
1118+
(uint8 *) state->server_first_message,
1119+
strlen(state->server_first_message)) < 0 ||
1120+
pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
1121+
pg_hmac_update(ctx,
1122+
(uint8 *) state->client_final_message_without_proof,
1123+
strlen(state->client_final_message_without_proof)) < 0 ||
1124+
pg_hmac_final(ctx, ClientSignature, sizeof(ClientSignature)) < 0)
11241125
{
11251126
elog(ERROR, "could not calculate client signature");
11261127
}
11271128

1129+
pg_hmac_free(ctx);
1130+
11281131
/* Extract the ClientKey that the client calculated from the proof */
11291132
for (i = 0; i < SCRAM_KEY_LEN; i++)
11301133
ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
@@ -1359,26 +1362,28 @@ build_server_final_message(scram_state *state)
13591362
uint8 ServerSignature[SCRAM_KEY_LEN];
13601363
char *server_signature_base64;
13611364
int siglen;
1362-
scram_HMAC_ctx ctx;
1365+
pg_hmac_ctx *ctx = pg_hmac_create(PG_SHA256);
13631366

13641367
/* calculate ServerSignature */
1365-
if (scram_HMAC_init(&ctx, state->ServerKey, SCRAM_KEY_LEN) < 0 ||
1366-
scram_HMAC_update(&ctx,
1367-
state->client_first_message_bare,
1368-
strlen(state->client_first_message_bare)) < 0 ||
1369-
scram_HMAC_update(&ctx, ",", 1) < 0 ||
1370-
scram_HMAC_update(&ctx,
1371-
state->server_first_message,
1372-
strlen(state->server_first_message)) < 0 ||
1373-
scram_HMAC_update(&ctx, ",", 1) < 0 ||
1374-
scram_HMAC_update(&ctx,
1375-
state->client_final_message_without_proof,
1376-
strlen(state->client_final_message_without_proof)) < 0 ||
1377-
scram_HMAC_final(ServerSignature, &ctx) < 0)
1368+
if (pg_hmac_init(ctx, state->ServerKey, SCRAM_KEY_LEN) < 0 ||
1369+
pg_hmac_update(ctx,
1370+
(uint8 *) state->client_first_message_bare,
1371+
strlen(state->client_first_message_bare)) < 0 ||
1372+
pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
1373+
pg_hmac_update(ctx,
1374+
(uint8 *) state->server_first_message,
1375+
strlen(state->server_first_message)) < 0 ||
1376+
pg_hmac_update(ctx, (uint8 *) ",", 1) < 0 ||
1377+
pg_hmac_update(ctx,
1378+
(uint8 *) state->client_final_message_without_proof,
1379+
strlen(state->client_final_message_without_proof)) < 0 ||
1380+
pg_hmac_final(ctx, ServerSignature, sizeof(ServerSignature)) < 0)
13781381
{
13791382
elog(ERROR, "could not calculate server signature");
13801383
}
13811384

1385+
pg_hmac_free(ctx);
1386+
13821387
siglen = pg_b64_enc_len(SCRAM_KEY_LEN);
13831388
/* don't forget the zero-terminator */
13841389
server_signature_base64 = palloc(siglen + 1);

src/backend/utils/resowner/resowner.c

+61
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include "common/cryptohash.h"
2424
#include "common/hashfn.h"
25+
#include "common/hmac.h"
2526
#include "jit/jit.h"
2627
#include "storage/bufmgr.h"
2728
#include "storage/ipc.h"
@@ -130,6 +131,7 @@ typedef struct ResourceOwnerData
130131
ResourceArray dsmarr; /* dynamic shmem segments */
131132
ResourceArray jitarr; /* JIT contexts */
132133
ResourceArray cryptohasharr; /* cryptohash contexts */
134+
ResourceArray hmacarr; /* HMAC contexts */
133135

134136
/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
135137
int nlocks; /* number of owned locks */
@@ -178,6 +180,7 @@ static void PrintSnapshotLeakWarning(Snapshot snapshot);
178180
static void PrintFileLeakWarning(File file);
179181
static void PrintDSMLeakWarning(dsm_segment *seg);
180182
static void PrintCryptoHashLeakWarning(Datum handle);
183+
static void PrintHMACLeakWarning(Datum handle);
181184

182185

183186
/*****************************************************************************
@@ -448,6 +451,7 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
448451
ResourceArrayInit(&(owner->dsmarr), PointerGetDatum(NULL));
449452
ResourceArrayInit(&(owner->jitarr), PointerGetDatum(NULL));
450453
ResourceArrayInit(&(owner->cryptohasharr), PointerGetDatum(NULL));
454+
ResourceArrayInit(&(owner->hmacarr), PointerGetDatum(NULL));
451455

452456
return owner;
453457
}
@@ -568,6 +572,16 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
568572
PrintCryptoHashLeakWarning(foundres);
569573
pg_cryptohash_free(context);
570574
}
575+
576+
/* Ditto for HMAC contexts */
577+
while (ResourceArrayGetAny(&(owner->hmacarr), &foundres))
578+
{
579+
pg_hmac_ctx *context = (pg_hmac_ctx *) PointerGetDatum(foundres);
580+
581+
if (isCommit)
582+
PrintHMACLeakWarning(foundres);
583+
pg_hmac_free(context);
584+
}
571585
}
572586
else if (phase == RESOURCE_RELEASE_LOCKS)
573587
{
@@ -737,6 +751,7 @@ ResourceOwnerDelete(ResourceOwner owner)
737751
Assert(owner->dsmarr.nitems == 0);
738752
Assert(owner->jitarr.nitems == 0);
739753
Assert(owner->cryptohasharr.nitems == 0);
754+
Assert(owner->hmacarr.nitems == 0);
740755
Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
741756

742757
/*
@@ -765,6 +780,7 @@ ResourceOwnerDelete(ResourceOwner owner)
765780
ResourceArrayFree(&(owner->dsmarr));
766781
ResourceArrayFree(&(owner->jitarr));
767782
ResourceArrayFree(&(owner->cryptohasharr));
783+
ResourceArrayFree(&(owner->hmacarr));
768784

769785
pfree(owner);
770786
}
@@ -1428,3 +1444,48 @@ PrintCryptoHashLeakWarning(Datum handle)
14281444
elog(WARNING, "cryptohash context reference leak: context %p still referenced",
14291445
DatumGetPointer(handle));
14301446
}
1447+
1448+
/*
1449+
* Make sure there is room for at least one more entry in a ResourceOwner's
1450+
* hmac context reference array.
1451+
*
1452+
* This is separate from actually inserting an entry because if we run out of
1453+
* memory, it's critical to do so *before* acquiring the resource.
1454+
*/
1455+
void
1456+
ResourceOwnerEnlargeHMAC(ResourceOwner owner)
1457+
{
1458+
ResourceArrayEnlarge(&(owner->hmacarr));
1459+
}
1460+
1461+
/*
1462+
* Remember that a HMAC context is owned by a ResourceOwner
1463+
*
1464+
* Caller must have previously done ResourceOwnerEnlargeHMAC()
1465+
*/
1466+
void
1467+
ResourceOwnerRememberHMAC(ResourceOwner owner, Datum handle)
1468+
{
1469+
ResourceArrayAdd(&(owner->hmacarr), handle);
1470+
}
1471+
1472+
/*
1473+
* Forget that a HMAC context is owned by a ResourceOwner
1474+
*/
1475+
void
1476+
ResourceOwnerForgetHMAC(ResourceOwner owner, Datum handle)
1477+
{
1478+
if (!ResourceArrayRemove(&(owner->hmacarr), handle))
1479+
elog(ERROR, "HMAC context %p is not owned by resource owner %s",
1480+
DatumGetPointer(handle), owner->name);
1481+
}
1482+
1483+
/*
1484+
* Debugging subroutine
1485+
*/
1486+
static void
1487+
PrintHMACLeakWarning(Datum handle)
1488+
{
1489+
elog(WARNING, "HMAC context reference leak: context %p still referenced",
1490+
DatumGetPointer(handle));
1491+
}

src/common/Makefile

+3-1
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,12 @@ OBJS_COMMON = \
8383
ifeq ($(with_ssl),openssl)
8484
OBJS_COMMON += \
8585
protocol_openssl.o \
86-
cryptohash_openssl.o
86+
cryptohash_openssl.o \
87+
hmac_openssl.o
8788
else
8889
OBJS_COMMON += \
8990
cryptohash.o \
91+
hmac.o \
9092
md5.o \
9193
sha1.o \
9294
sha2.o

0 commit comments

Comments
 (0)