Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * cryptohash_openssl.c
4 : : * Set of wrapper routines on top of OpenSSL to support cryptographic
5 : : * hash functions.
6 : : *
7 : : * This should only be used if code is compiled with OpenSSL support.
8 : : *
9 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
10 : : * Portions Copyright (c) 1994, Regents of the University of California
11 : : *
12 : : * IDENTIFICATION
13 : : * src/common/cryptohash_openssl.c
14 : : *
15 : : *-------------------------------------------------------------------------
16 : : */
17 : :
18 : : #ifndef FRONTEND
19 : : #include "postgres.h"
20 : : #else
21 : : #include "postgres_fe.h"
22 : : #endif
23 : :
24 : : #include <openssl/err.h>
25 : : #include <openssl/evp.h>
26 : :
27 : : #include "common/cryptohash.h"
28 : : #include "common/md5.h"
29 : : #include "common/sha1.h"
30 : : #include "common/sha2.h"
31 : : #ifndef FRONTEND
32 : : #include "utils/memutils.h"
33 : : #include "utils/resowner.h"
34 : : #endif
35 : :
36 : : /*
37 : : * In the backend, use an allocation in TopMemoryContext to count for
38 : : * resowner cleanup handling. In the frontend, use malloc to be able
39 : : * to return a failure status back to the caller.
40 : : */
41 : : #ifndef FRONTEND
42 : : #define ALLOC(size) MemoryContextAlloc(TopMemoryContext, size)
43 : : #define FREE(ptr) pfree(ptr)
44 : : #else
45 : : #define ALLOC(size) malloc(size)
46 : : #define FREE(ptr) free(ptr)
47 : : #endif
48 : :
49 : : /* Set of error states */
50 : : typedef enum pg_cryptohash_errno
51 : : {
52 : : PG_CRYPTOHASH_ERROR_NONE = 0,
53 : : PG_CRYPTOHASH_ERROR_DEST_LEN,
54 : : PG_CRYPTOHASH_ERROR_OPENSSL,
55 : : } pg_cryptohash_errno;
56 : :
57 : : /*
58 : : * Internal pg_cryptohash_ctx structure.
59 : : *
60 : : * This tracks the resource owner associated to each EVP context data
61 : : * for the backend.
62 : : */
63 : : struct pg_cryptohash_ctx
64 : : {
65 : : pg_cryptohash_type type;
66 : : pg_cryptohash_errno error;
67 : : const char *errreason;
68 : :
69 : : EVP_MD_CTX *evpctx;
70 : : #if OPENSSL_VERSION_NUMBER >= 0x30000000L
71 : : EVP_MD *algo;
72 : : #endif
73 : :
74 : : #ifndef FRONTEND
75 : : ResourceOwner resowner;
76 : : #endif
77 : : };
78 : :
79 : : /* ResourceOwner callbacks to hold cryptohash contexts */
80 : : #ifndef FRONTEND
81 : : static void ResOwnerReleaseCryptoHash(Datum res);
82 : :
83 : : static const ResourceOwnerDesc cryptohash_resowner_desc =
84 : : {
85 : : .name = "OpenSSL cryptohash context",
86 : : .release_phase = RESOURCE_RELEASE_BEFORE_LOCKS,
87 : : .release_priority = RELEASE_PRIO_CRYPTOHASH_CONTEXTS,
88 : : .ReleaseResource = ResOwnerReleaseCryptoHash,
89 : : .DebugPrint = NULL /* the default message is fine */
90 : : };
91 : :
92 : : /* Convenience wrappers over ResourceOwnerRemember/Forget */
93 : : static inline void
94 : 687160 : ResourceOwnerRememberCryptoHash(ResourceOwner owner, pg_cryptohash_ctx *ctx)
95 : : {
96 : 687160 : ResourceOwnerRemember(owner, PointerGetDatum(ctx), &cryptohash_resowner_desc);
97 : 687160 : }
98 : : static inline void
99 : 687150 : ResourceOwnerForgetCryptoHash(ResourceOwner owner, pg_cryptohash_ctx *ctx)
100 : : {
101 : 687150 : ResourceOwnerForget(owner, PointerGetDatum(ctx), &cryptohash_resowner_desc);
102 : 687150 : }
103 : : #endif
104 : :
105 : : static const char *
106 : 0 : SSLerrmessage(unsigned long ecode)
107 : : {
108 [ # # ]: 0 : if (ecode == 0)
109 : 0 : return NULL;
110 : :
111 : : /*
112 : : * This may return NULL, but we would fall back to a default error path if
113 : : * that were the case.
114 : : */
115 : 0 : return ERR_reason_error_string(ecode);
116 : : }
117 : :
118 : : /*
119 : : * pg_cryptohash_create
120 : : *
121 : : * Allocate a hash context. Returns NULL on failure for an OOM. The
122 : : * backend issues an error, without returning.
123 : : */
124 : : pg_cryptohash_ctx *
125 : 697649 : pg_cryptohash_create(pg_cryptohash_type type)
126 : : {
127 : : pg_cryptohash_ctx *ctx;
128 : :
129 : : /*
130 : : * Make sure that the resource owner has space to remember this reference.
131 : : * This can error out with "out of memory", so do this before any other
132 : : * allocation to avoid leaking.
133 : : */
134 : : #ifndef FRONTEND
135 : 687160 : ResourceOwnerEnlarge(CurrentResourceOwner);
136 : : #endif
137 : :
138 : 697649 : ctx = ALLOC(sizeof(pg_cryptohash_ctx));
139 [ - + ]: 697649 : if (ctx == NULL)
140 : 0 : return NULL;
141 : 697649 : memset(ctx, 0, sizeof(pg_cryptohash_ctx));
142 : 697649 : ctx->type = type;
143 : 697649 : ctx->error = PG_CRYPTOHASH_ERROR_NONE;
144 : 697649 : ctx->errreason = NULL;
145 : :
146 : : /*
147 : : * Initialization takes care of assigning the correct type for OpenSSL.
148 : : * Also ensure that there aren't any unconsumed errors in the queue from
149 : : * previous runs.
150 : : */
151 : 697649 : ERR_clear_error();
152 : 697649 : ctx->evpctx = EVP_MD_CTX_create();
153 : :
154 [ - + ]: 697649 : if (ctx->evpctx == NULL)
155 : : {
156 : 0 : explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
157 : 0 : FREE(ctx);
158 : : #ifndef FRONTEND
159 [ # # ]: 0 : ereport(ERROR,
160 : : (errcode(ERRCODE_OUT_OF_MEMORY),
161 : : errmsg("out of memory")));
162 : : #else
163 : 0 : return NULL;
164 : : #endif
165 : : }
166 : :
167 : : #ifndef FRONTEND
168 : 687160 : ctx->resowner = CurrentResourceOwner;
169 : 687160 : ResourceOwnerRememberCryptoHash(CurrentResourceOwner, ctx);
170 : : #endif
171 : :
172 : 697649 : return ctx;
173 : : }
174 : :
175 : : /*
176 : : * pg_cryptohash_init
177 : : *
178 : : * Initialize a hash context. Returns 0 on success, and -1 on failure.
179 : : */
180 : : int
181 : 697649 : pg_cryptohash_init(pg_cryptohash_ctx *ctx)
182 : : {
183 : 697649 : int status = 0;
184 : : #if OPENSSL_VERSION_NUMBER >= 0x30000000L
185 : 697649 : const char *name = NULL;
186 : : #endif
187 : :
188 [ - + ]: 697649 : if (ctx == NULL)
189 : 0 : return -1;
190 : :
191 : : #if OPENSSL_VERSION_NUMBER >= 0x30000000L
192 : :
193 : : /*
194 : : * Fetch the digest implementation so that it is served by the loaded
195 : : * provider.
196 : : */
197 [ + - + + : 697649 : switch (ctx->type)
+ + - ]
198 : : {
199 : 124 : case PG_MD5:
200 : 124 : name = "MD5";
201 : 124 : break;
202 : 0 : case PG_SHA1:
203 : 0 : name = "SHA1";
204 : 0 : break;
205 : 6183 : case PG_SHA224:
206 : 6183 : name = "SHA224";
207 : 6183 : break;
208 : 683090 : case PG_SHA256:
209 : 683090 : name = "SHA256";
210 : 683090 : break;
211 : 4126 : case PG_SHA384:
212 : 4126 : name = "SHA384";
213 : 4126 : break;
214 : 4126 : case PG_SHA512:
215 : 4126 : name = "SHA512";
216 : 4126 : break;
217 : : }
218 : :
219 : : /*
220 : : * Call EVP_MD_fetch() only once for each context, as provider lookups can
221 : : * be expensive.
222 : : */
223 [ + - ]: 697649 : if (ctx->algo == NULL)
224 : 697649 : ctx->algo = EVP_MD_fetch(NULL, name, NULL);
225 [ + - ]: 697649 : if (ctx->algo != NULL)
226 : 697649 : status = EVP_DigestInit_ex(ctx->evpctx, ctx->algo, NULL);
227 : : #else
228 : : switch (ctx->type)
229 : : {
230 : : case PG_MD5:
231 : : status = EVP_DigestInit_ex(ctx->evpctx, EVP_md5(), NULL);
232 : : break;
233 : : case PG_SHA1:
234 : : status = EVP_DigestInit_ex(ctx->evpctx, EVP_sha1(), NULL);
235 : : break;
236 : : case PG_SHA224:
237 : : status = EVP_DigestInit_ex(ctx->evpctx, EVP_sha224(), NULL);
238 : : break;
239 : : case PG_SHA256:
240 : : status = EVP_DigestInit_ex(ctx->evpctx, EVP_sha256(), NULL);
241 : : break;
242 : : case PG_SHA384:
243 : : status = EVP_DigestInit_ex(ctx->evpctx, EVP_sha384(), NULL);
244 : : break;
245 : : case PG_SHA512:
246 : : status = EVP_DigestInit_ex(ctx->evpctx, EVP_sha512(), NULL);
247 : : break;
248 : : }
249 : : #endif
250 : :
251 : : /* OpenSSL internals return 1 on success, 0 on failure */
252 [ - + ]: 697649 : if (status <= 0)
253 : : {
254 : 0 : ctx->errreason = SSLerrmessage(ERR_get_error());
255 : 0 : ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL;
256 : :
257 : : /*
258 : : * The OpenSSL error queue should normally be empty since we've
259 : : * consumed an error, but cipher initialization can in FIPS-enabled
260 : : * OpenSSL builds generate two errors so clear the queue here as well.
261 : : */
262 : 0 : ERR_clear_error();
263 : 0 : return -1;
264 : : }
265 : 697649 : return 0;
266 : : }
267 : :
268 : : /*
269 : : * pg_cryptohash_update
270 : : *
271 : : * Update a hash context. Returns 0 on success, and -1 on failure.
272 : : */
273 : : int
274 : 891840 : pg_cryptohash_update(pg_cryptohash_ctx *ctx, const uint8 *data, size_t len)
275 : : {
276 : 891840 : int status = 0;
277 : :
278 [ - + ]: 891840 : if (ctx == NULL)
279 : 0 : return -1;
280 : :
281 : 891840 : status = EVP_DigestUpdate(ctx->evpctx, data, len);
282 : :
283 : : /* OpenSSL internals return 1 on success, 0 on failure */
284 [ - + ]: 891840 : if (status <= 0)
285 : : {
286 : 0 : ctx->errreason = SSLerrmessage(ERR_get_error());
287 : 0 : ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL;
288 : 0 : return -1;
289 : : }
290 : 891840 : return 0;
291 : : }
292 : :
293 : : /*
294 : : * pg_cryptohash_final
295 : : *
296 : : * Finalize a hash context. Returns 0 on success, and -1 on failure.
297 : : */
298 : : int
299 : 697641 : pg_cryptohash_final(pg_cryptohash_ctx *ctx, uint8 *dest, size_t len)
300 : : {
301 : 697641 : int status = 0;
302 : :
303 [ - + ]: 697641 : if (ctx == NULL)
304 : 0 : return -1;
305 : :
306 [ + - + + : 697641 : switch (ctx->type)
+ + - ]
307 : : {
308 : 124 : case PG_MD5:
309 [ - + ]: 124 : if (len < MD5_DIGEST_LENGTH)
310 : : {
311 : 0 : ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
312 : 0 : return -1;
313 : : }
314 : 124 : break;
315 : 0 : case PG_SHA1:
316 [ # # ]: 0 : if (len < SHA1_DIGEST_LENGTH)
317 : : {
318 : 0 : ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
319 : 0 : return -1;
320 : : }
321 : 0 : break;
322 : 6183 : case PG_SHA224:
323 [ - + ]: 6183 : if (len < PG_SHA224_DIGEST_LENGTH)
324 : : {
325 : 0 : ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
326 : 0 : return -1;
327 : : }
328 : 6183 : break;
329 : 683082 : case PG_SHA256:
330 [ - + ]: 683082 : if (len < PG_SHA256_DIGEST_LENGTH)
331 : : {
332 : 0 : ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
333 : 0 : return -1;
334 : : }
335 : 683082 : break;
336 : 4126 : case PG_SHA384:
337 [ - + ]: 4126 : if (len < PG_SHA384_DIGEST_LENGTH)
338 : : {
339 : 0 : ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
340 : 0 : return -1;
341 : : }
342 : 4126 : break;
343 : 4126 : case PG_SHA512:
344 [ - + ]: 4126 : if (len < PG_SHA512_DIGEST_LENGTH)
345 : : {
346 : 0 : ctx->error = PG_CRYPTOHASH_ERROR_DEST_LEN;
347 : 0 : return -1;
348 : : }
349 : 4126 : break;
350 : : }
351 : :
352 : 697641 : status = EVP_DigestFinal_ex(ctx->evpctx, dest, 0);
353 : :
354 : : /* OpenSSL internals return 1 on success, 0 on failure */
355 [ - + ]: 697641 : if (status <= 0)
356 : : {
357 : 0 : ctx->errreason = SSLerrmessage(ERR_get_error());
358 : 0 : ctx->error = PG_CRYPTOHASH_ERROR_OPENSSL;
359 : 0 : return -1;
360 : : }
361 : 697641 : return 0;
362 : : }
363 : :
364 : : /*
365 : : * pg_cryptohash_free
366 : : *
367 : : * Free a hash context.
368 : : */
369 : : void
370 : 697646 : pg_cryptohash_free(pg_cryptohash_ctx *ctx)
371 : : {
372 [ + + ]: 697646 : if (ctx == NULL)
373 : 1 : return;
374 : :
375 : 697645 : EVP_MD_CTX_destroy(ctx->evpctx);
376 : : #if OPENSSL_VERSION_NUMBER >= 0x30000000L
377 : 697645 : EVP_MD_free(ctx->algo);
378 : : #endif
379 : :
380 : : #ifndef FRONTEND
381 [ + + ]: 687160 : if (ctx->resowner)
382 : 687150 : ResourceOwnerForgetCryptoHash(ctx->resowner, ctx);
383 : : #endif
384 : :
385 : 697645 : explicit_bzero(ctx, sizeof(pg_cryptohash_ctx));
386 : 697645 : FREE(ctx);
387 : : }
388 : :
389 : : /*
390 : : * pg_cryptohash_error
391 : : *
392 : : * Returns a static string providing details about an error that
393 : : * happened during a computation.
394 : : */
395 : : const char *
396 : 0 : pg_cryptohash_error(pg_cryptohash_ctx *ctx)
397 : : {
398 : : /*
399 : : * This implementation would never fail because of an out-of-memory error,
400 : : * except when creating the context.
401 : : */
402 [ # # ]: 0 : if (ctx == NULL)
403 : 0 : return _("out of memory");
404 : :
405 : : /*
406 : : * If a reason is provided, rely on it, else fallback to any error code
407 : : * set.
408 : : */
409 [ # # ]: 0 : if (ctx->errreason)
410 : 0 : return ctx->errreason;
411 : :
412 [ # # # # ]: 0 : switch (ctx->error)
413 : : {
414 : 0 : case PG_CRYPTOHASH_ERROR_NONE:
415 : 0 : return _("success");
416 : 0 : case PG_CRYPTOHASH_ERROR_DEST_LEN:
417 : 0 : return _("destination buffer too small");
418 : 0 : case PG_CRYPTOHASH_ERROR_OPENSSL:
419 : 0 : return _("OpenSSL failure");
420 : : }
421 : :
422 : : Assert(false); /* cannot be reached */
423 : 0 : return _("success");
424 : : }
425 : :
426 : : /* ResourceOwner callbacks */
427 : :
428 : : #ifndef FRONTEND
429 : : static void
430 : 10 : ResOwnerReleaseCryptoHash(Datum res)
431 : : {
432 : 10 : pg_cryptohash_ctx *ctx = (pg_cryptohash_ctx *) DatumGetPointer(res);
433 : :
434 : 10 : ctx->resowner = NULL;
435 : 10 : pg_cryptohash_free(ctx);
436 : 10 : }
437 : : #endif
|