/src/postgres/src/common/hmac.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * hmac.c |
4 | | * Implements Keyed-Hashing for Message Authentication (HMAC) |
5 | | * |
6 | | * Fallback implementation of HMAC, as specified in RFC 2104. |
7 | | * |
8 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
9 | | * Portions Copyright (c) 1994, Regents of the University of California |
10 | | * |
11 | | * IDENTIFICATION |
12 | | * src/common/hmac.c |
13 | | * |
14 | | *------------------------------------------------------------------------- |
15 | | */ |
16 | | |
17 | | #ifndef FRONTEND |
18 | | #include "postgres.h" |
19 | | #else |
20 | | #include "postgres_fe.h" |
21 | | #endif |
22 | | |
23 | | #include "common/cryptohash.h" |
24 | | #include "common/hmac.h" |
25 | | #include "common/md5.h" |
26 | | #include "common/sha1.h" |
27 | | #include "common/sha2.h" |
28 | | |
29 | | /* |
30 | | * In backend, use palloc/pfree to ease the error handling. In frontend, |
31 | | * use malloc to be able to return a failure status back to the caller. |
32 | | */ |
33 | | #ifndef FRONTEND |
34 | 0 | #define ALLOC(size) palloc(size) |
35 | 0 | #define FREE(ptr) pfree(ptr) |
36 | | #else |
37 | | #define ALLOC(size) malloc(size) |
38 | | #define FREE(ptr) free(ptr) |
39 | | #endif |
40 | | |
41 | | /* Set of error states */ |
42 | | typedef enum pg_hmac_errno |
43 | | { |
44 | | PG_HMAC_ERROR_NONE = 0, |
45 | | PG_HMAC_ERROR_OOM, |
46 | | PG_HMAC_ERROR_INTERNAL, |
47 | | } pg_hmac_errno; |
48 | | |
49 | | /* Internal pg_hmac_ctx structure */ |
50 | | struct pg_hmac_ctx |
51 | | { |
52 | | pg_cryptohash_ctx *hash; |
53 | | pg_cryptohash_type type; |
54 | | pg_hmac_errno error; |
55 | | const char *errreason; |
56 | | int block_size; |
57 | | int digest_size; |
58 | | |
59 | | /* |
60 | | * Use the largest block size among supported options. This wastes some |
61 | | * memory but simplifies the allocation logic. |
62 | | */ |
63 | | uint8 k_ipad[PG_SHA512_BLOCK_LENGTH]; |
64 | | uint8 k_opad[PG_SHA512_BLOCK_LENGTH]; |
65 | | }; |
66 | | |
67 | 0 | #define HMAC_IPAD 0x36 |
68 | 0 | #define HMAC_OPAD 0x5C |
69 | | |
70 | | /* |
71 | | * pg_hmac_create |
72 | | * |
73 | | * Allocate a hash context. Returns NULL on failure for an OOM. The |
74 | | * backend issues an error, without returning. |
75 | | */ |
76 | | pg_hmac_ctx * |
77 | | pg_hmac_create(pg_cryptohash_type type) |
78 | 0 | { |
79 | 0 | pg_hmac_ctx *ctx; |
80 | |
|
81 | 0 | ctx = ALLOC(sizeof(pg_hmac_ctx)); |
82 | 0 | if (ctx == NULL) |
83 | 0 | return NULL; |
84 | 0 | memset(ctx, 0, sizeof(pg_hmac_ctx)); |
85 | 0 | ctx->type = type; |
86 | 0 | ctx->error = PG_HMAC_ERROR_NONE; |
87 | 0 | ctx->errreason = NULL; |
88 | | |
89 | | /* |
90 | | * Initialize the context data. This requires to know the digest and |
91 | | * block lengths, that depend on the type of hash used. |
92 | | */ |
93 | 0 | switch (type) |
94 | 0 | { |
95 | 0 | case PG_MD5: |
96 | 0 | ctx->digest_size = MD5_DIGEST_LENGTH; |
97 | 0 | ctx->block_size = MD5_BLOCK_SIZE; |
98 | 0 | break; |
99 | 0 | case PG_SHA1: |
100 | 0 | ctx->digest_size = SHA1_DIGEST_LENGTH; |
101 | 0 | ctx->block_size = SHA1_BLOCK_SIZE; |
102 | 0 | break; |
103 | 0 | case PG_SHA224: |
104 | 0 | ctx->digest_size = PG_SHA224_DIGEST_LENGTH; |
105 | 0 | ctx->block_size = PG_SHA224_BLOCK_LENGTH; |
106 | 0 | break; |
107 | 0 | case PG_SHA256: |
108 | 0 | ctx->digest_size = PG_SHA256_DIGEST_LENGTH; |
109 | 0 | ctx->block_size = PG_SHA256_BLOCK_LENGTH; |
110 | 0 | break; |
111 | 0 | case PG_SHA384: |
112 | 0 | ctx->digest_size = PG_SHA384_DIGEST_LENGTH; |
113 | 0 | ctx->block_size = PG_SHA384_BLOCK_LENGTH; |
114 | 0 | break; |
115 | 0 | case PG_SHA512: |
116 | 0 | ctx->digest_size = PG_SHA512_DIGEST_LENGTH; |
117 | 0 | ctx->block_size = PG_SHA512_BLOCK_LENGTH; |
118 | 0 | break; |
119 | 0 | } |
120 | | |
121 | 0 | ctx->hash = pg_cryptohash_create(type); |
122 | 0 | if (ctx->hash == NULL) |
123 | 0 | { |
124 | 0 | explicit_bzero(ctx, sizeof(pg_hmac_ctx)); |
125 | 0 | FREE(ctx); |
126 | 0 | return NULL; |
127 | 0 | } |
128 | | |
129 | 0 | return ctx; |
130 | 0 | } |
131 | | |
132 | | /* |
133 | | * pg_hmac_init |
134 | | * |
135 | | * Initialize a HMAC context. Returns 0 on success, -1 on failure. |
136 | | */ |
137 | | int |
138 | | pg_hmac_init(pg_hmac_ctx *ctx, const uint8 *key, size_t len) |
139 | 0 | { |
140 | 0 | int digest_size; |
141 | 0 | int block_size; |
142 | 0 | uint8 *shrinkbuf = NULL; |
143 | |
|
144 | 0 | if (ctx == NULL) |
145 | 0 | return -1; |
146 | | |
147 | 0 | digest_size = ctx->digest_size; |
148 | 0 | block_size = ctx->block_size; |
149 | |
|
150 | 0 | memset(ctx->k_opad, HMAC_OPAD, ctx->block_size); |
151 | 0 | memset(ctx->k_ipad, HMAC_IPAD, ctx->block_size); |
152 | | |
153 | | /* |
154 | | * If the key is longer than the block size, pass it through the hash once |
155 | | * to shrink it down. |
156 | | */ |
157 | 0 | if (len > block_size) |
158 | 0 | { |
159 | 0 | pg_cryptohash_ctx *hash_ctx; |
160 | | |
161 | | /* temporary buffer for one-time shrink */ |
162 | 0 | shrinkbuf = ALLOC(digest_size); |
163 | 0 | if (shrinkbuf == NULL) |
164 | 0 | { |
165 | 0 | ctx->error = PG_HMAC_ERROR_OOM; |
166 | 0 | return -1; |
167 | 0 | } |
168 | 0 | memset(shrinkbuf, 0, digest_size); |
169 | |
|
170 | 0 | hash_ctx = pg_cryptohash_create(ctx->type); |
171 | 0 | if (hash_ctx == NULL) |
172 | 0 | { |
173 | 0 | ctx->error = PG_HMAC_ERROR_OOM; |
174 | 0 | FREE(shrinkbuf); |
175 | 0 | return -1; |
176 | 0 | } |
177 | | |
178 | 0 | if (pg_cryptohash_init(hash_ctx) < 0 || |
179 | 0 | pg_cryptohash_update(hash_ctx, key, len) < 0 || |
180 | 0 | pg_cryptohash_final(hash_ctx, shrinkbuf, digest_size) < 0) |
181 | 0 | { |
182 | 0 | ctx->error = PG_HMAC_ERROR_INTERNAL; |
183 | 0 | ctx->errreason = pg_cryptohash_error(hash_ctx); |
184 | 0 | pg_cryptohash_free(hash_ctx); |
185 | 0 | FREE(shrinkbuf); |
186 | 0 | return -1; |
187 | 0 | } |
188 | | |
189 | 0 | key = shrinkbuf; |
190 | 0 | len = digest_size; |
191 | 0 | pg_cryptohash_free(hash_ctx); |
192 | 0 | } |
193 | | |
194 | 0 | for (size_t i = 0; i < len; i++) |
195 | 0 | { |
196 | 0 | ctx->k_ipad[i] ^= key[i]; |
197 | 0 | ctx->k_opad[i] ^= key[i]; |
198 | 0 | } |
199 | | |
200 | | /* tmp = H(K XOR ipad, text) */ |
201 | 0 | if (pg_cryptohash_init(ctx->hash) < 0 || |
202 | 0 | pg_cryptohash_update(ctx->hash, ctx->k_ipad, ctx->block_size) < 0) |
203 | 0 | { |
204 | 0 | ctx->error = PG_HMAC_ERROR_INTERNAL; |
205 | 0 | ctx->errreason = pg_cryptohash_error(ctx->hash); |
206 | 0 | if (shrinkbuf) |
207 | 0 | FREE(shrinkbuf); |
208 | 0 | return -1; |
209 | 0 | } |
210 | | |
211 | 0 | if (shrinkbuf) |
212 | 0 | FREE(shrinkbuf); |
213 | 0 | return 0; |
214 | 0 | } |
215 | | |
216 | | /* |
217 | | * pg_hmac_update |
218 | | * |
219 | | * Update a HMAC context. Returns 0 on success, -1 on failure. |
220 | | */ |
221 | | int |
222 | | pg_hmac_update(pg_hmac_ctx *ctx, const uint8 *data, size_t len) |
223 | 0 | { |
224 | 0 | if (ctx == NULL) |
225 | 0 | return -1; |
226 | | |
227 | 0 | if (pg_cryptohash_update(ctx->hash, data, len) < 0) |
228 | 0 | { |
229 | 0 | ctx->error = PG_HMAC_ERROR_INTERNAL; |
230 | 0 | ctx->errreason = pg_cryptohash_error(ctx->hash); |
231 | 0 | return -1; |
232 | 0 | } |
233 | | |
234 | 0 | return 0; |
235 | 0 | } |
236 | | |
237 | | /* |
238 | | * pg_hmac_final |
239 | | * |
240 | | * Finalize a HMAC context. Returns 0 on success, -1 on failure. |
241 | | */ |
242 | | int |
243 | | pg_hmac_final(pg_hmac_ctx *ctx, uint8 *dest, size_t len) |
244 | 0 | { |
245 | 0 | uint8 *h; |
246 | |
|
247 | 0 | if (ctx == NULL) |
248 | 0 | return -1; |
249 | | |
250 | 0 | h = ALLOC(ctx->digest_size); |
251 | 0 | if (h == NULL) |
252 | 0 | { |
253 | 0 | ctx->error = PG_HMAC_ERROR_OOM; |
254 | 0 | return -1; |
255 | 0 | } |
256 | 0 | memset(h, 0, ctx->digest_size); |
257 | |
|
258 | 0 | if (pg_cryptohash_final(ctx->hash, h, ctx->digest_size) < 0) |
259 | 0 | { |
260 | 0 | ctx->error = PG_HMAC_ERROR_INTERNAL; |
261 | 0 | ctx->errreason = pg_cryptohash_error(ctx->hash); |
262 | 0 | FREE(h); |
263 | 0 | return -1; |
264 | 0 | } |
265 | | |
266 | | /* H(K XOR opad, tmp) */ |
267 | 0 | if (pg_cryptohash_init(ctx->hash) < 0 || |
268 | 0 | pg_cryptohash_update(ctx->hash, ctx->k_opad, ctx->block_size) < 0 || |
269 | 0 | pg_cryptohash_update(ctx->hash, h, ctx->digest_size) < 0 || |
270 | 0 | pg_cryptohash_final(ctx->hash, dest, len) < 0) |
271 | 0 | { |
272 | 0 | ctx->error = PG_HMAC_ERROR_INTERNAL; |
273 | 0 | ctx->errreason = pg_cryptohash_error(ctx->hash); |
274 | 0 | FREE(h); |
275 | 0 | return -1; |
276 | 0 | } |
277 | | |
278 | 0 | FREE(h); |
279 | 0 | return 0; |
280 | 0 | } |
281 | | |
282 | | /* |
283 | | * pg_hmac_free |
284 | | * |
285 | | * Free a HMAC context. |
286 | | */ |
287 | | void |
288 | | pg_hmac_free(pg_hmac_ctx *ctx) |
289 | 0 | { |
290 | 0 | if (ctx == NULL) |
291 | 0 | return; |
292 | | |
293 | 0 | pg_cryptohash_free(ctx->hash); |
294 | 0 | explicit_bzero(ctx, sizeof(pg_hmac_ctx)); |
295 | 0 | FREE(ctx); |
296 | 0 | } |
297 | | |
298 | | /* |
299 | | * pg_hmac_error |
300 | | * |
301 | | * Returns a static string providing details about an error that happened |
302 | | * during a HMAC computation. |
303 | | */ |
304 | | const char * |
305 | | pg_hmac_error(pg_hmac_ctx *ctx) |
306 | 0 | { |
307 | 0 | if (ctx == NULL) |
308 | 0 | return _("out of memory"); |
309 | | |
310 | | /* |
311 | | * If a reason is provided, rely on it, else fallback to any error code |
312 | | * set. |
313 | | */ |
314 | 0 | if (ctx->errreason) |
315 | 0 | return ctx->errreason; |
316 | | |
317 | 0 | switch (ctx->error) |
318 | 0 | { |
319 | 0 | case PG_HMAC_ERROR_NONE: |
320 | 0 | return _("success"); |
321 | 0 | case PG_HMAC_ERROR_INTERNAL: |
322 | 0 | return _("internal error"); |
323 | 0 | case PG_HMAC_ERROR_OOM: |
324 | 0 | return _("out of memory"); |
325 | 0 | } |
326 | | |
327 | 0 | Assert(false); /* cannot be reached */ |
328 | 0 | return _("success"); |
329 | 0 | } |