Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : * scram-common.c
3 : : * Shared frontend/backend code for SCRAM authentication
4 : : *
5 : : * This contains the common low-level functions needed in both frontend and
6 : : * backend, for implement the Salted Challenge Response Authentication
7 : : * Mechanism (SCRAM), per IETF's RFC 5802.
8 : : *
9 : : * Portions Copyright (c) 2017-2025, PostgreSQL Global Development Group
10 : : *
11 : : * IDENTIFICATION
12 : : * src/common/scram-common.c
13 : : *
14 : : *-------------------------------------------------------------------------
15 : : */
16 : : #ifndef FRONTEND
17 : : #include "postgres.h"
18 : : #else
19 : : #include "postgres_fe.h"
20 : : #endif
21 : :
22 : : #include "common/base64.h"
23 : : #include "common/hmac.h"
24 : : #include "common/scram-common.h"
25 : : #ifndef FRONTEND
26 : : #include "miscadmin.h"
27 : : #endif
28 : : #include "port/pg_bswap.h"
29 : :
30 : : /*
31 : : * Calculate SaltedPassword.
32 : : *
33 : : * The password should already be normalized by SASLprep. Returns 0 on
34 : : * success, -1 on failure with *errstr pointing to a message about the
35 : : * error details.
36 : : */
37 : : int
3053 heikki.linnakangas@i 38 :CBC 136 : scram_SaltedPassword(const char *password,
39 : : pg_cryptohash_type hash_type, int key_length,
40 : : const uint8 *salt, int saltlen, int iterations,
41 : : uint8 *result, const char **errstr)
42 : : {
43 : 136 : int password_len = strlen(password);
2897 andres@anarazel.de 44 : 136 : uint32 one = pg_hton32(1);
45 : : int i,
46 : : j;
47 : : uint8 Ui[SCRAM_MAX_KEY_LEN];
48 : : uint8 Ui_prev[SCRAM_MAX_KEY_LEN];
991 michael@paquier.xyz 49 : 136 : pg_hmac_ctx *hmac_ctx = pg_hmac_create(hash_type);
50 : :
1617 51 [ - + ]: 136 : if (hmac_ctx == NULL)
52 : : {
1332 michael@paquier.xyz 53 :UBC 0 : *errstr = pg_hmac_error(NULL); /* returns OOM */
1617 54 : 0 : return -1;
55 : : }
56 : :
57 : : /*
58 : : * Iterate hash calculation of HMAC entry using given salt. This is
59 : : * essentially PBKDF2 (see RFC2898) with HMAC() as the pseudorandom
60 : : * function.
61 : : */
62 : :
63 : : /* First iteration */
1617 michael@paquier.xyz 64 [ + - + - ]:CBC 272 : if (pg_hmac_init(hmac_ctx, (uint8 *) password, password_len) < 0 ||
121 heikki.linnakangas@i 65 [ + - ]: 272 : pg_hmac_update(hmac_ctx, salt, saltlen) < 0 ||
1617 michael@paquier.xyz 66 [ - + ]: 272 : pg_hmac_update(hmac_ctx, (uint8 *) &one, sizeof(uint32)) < 0 ||
991 67 : 136 : pg_hmac_final(hmac_ctx, Ui_prev, key_length) < 0)
68 : : {
1332 michael@paquier.xyz 69 :UBC 0 : *errstr = pg_hmac_error(hmac_ctx);
1617 70 : 0 : pg_hmac_free(hmac_ctx);
1739 71 : 0 : return -1;
72 : : }
73 : :
991 michael@paquier.xyz 74 :CBC 136 : memcpy(result, Ui_prev, key_length);
75 : :
76 : : /* Subsequent iterations */
164 rguo@postgresql.org 77 [ + + ]: 514174 : for (i = 1; i < iterations; i++)
78 : : {
79 : : #ifndef FRONTEND
80 : : /*
81 : : * Make sure that this is interruptible as scram_iterations could be
82 : : * set to a large value.
83 : : */
648 michael@paquier.xyz 84 [ - + ]: 284636 : CHECK_FOR_INTERRUPTS();
85 : : #endif
86 : :
1617 87 [ + - + - ]: 1028076 : if (pg_hmac_init(hmac_ctx, (uint8 *) password, password_len) < 0 ||
991 88 [ - + ]: 1028076 : pg_hmac_update(hmac_ctx, (uint8 *) Ui_prev, key_length) < 0 ||
89 : 514038 : pg_hmac_final(hmac_ctx, Ui, key_length) < 0)
90 : : {
1332 michael@paquier.xyz 91 :UBC 0 : *errstr = pg_hmac_error(hmac_ctx);
1617 92 : 0 : pg_hmac_free(hmac_ctx);
1739 93 : 0 : return -1;
94 : : }
95 : :
991 michael@paquier.xyz 96 [ + + ]:CBC 16963254 : for (j = 0; j < key_length; j++)
3105 heikki.linnakangas@i 97 : 16449216 : result[j] ^= Ui[j];
991 michael@paquier.xyz 98 : 514038 : memcpy(Ui_prev, Ui, key_length);
99 : : }
100 : :
1617 101 : 136 : pg_hmac_free(hmac_ctx);
1739 102 : 136 : return 0;
103 : : }
104 : :
105 : :
106 : : /*
107 : : * Calculate hash for a NULL-terminated string. (The NULL terminator is
108 : : * not included in the hash). Returns 0 on success, -1 on failure with *errstr
109 : : * pointing to a message about the error details.
110 : : */
111 : : int
991 112 : 177 : scram_H(const uint8 *input, pg_cryptohash_type hash_type, int key_length,
113 : : uint8 *result, const char **errstr)
114 : : {
115 : : pg_cryptohash_ctx *ctx;
116 : :
117 : 177 : ctx = pg_cryptohash_create(hash_type);
1739 118 [ - + ]: 177 : if (ctx == NULL)
119 : : {
1332 michael@paquier.xyz 120 :UBC 0 : *errstr = pg_cryptohash_error(NULL); /* returns OOM */
1739 121 : 0 : return -1;
122 : : }
123 : :
1739 michael@paquier.xyz 124 [ + - + - ]:CBC 354 : if (pg_cryptohash_init(ctx) < 0 ||
991 125 [ - + ]: 354 : pg_cryptohash_update(ctx, input, key_length) < 0 ||
126 : 177 : pg_cryptohash_final(ctx, result, key_length) < 0)
127 : : {
1332 michael@paquier.xyz 128 :UBC 0 : *errstr = pg_cryptohash_error(ctx);
1739 129 : 0 : pg_cryptohash_free(ctx);
130 : 0 : return -1;
131 : : }
132 : :
1739 michael@paquier.xyz 133 :CBC 177 : pg_cryptohash_free(ctx);
134 : 177 : return 0;
135 : : }
136 : :
137 : : /*
138 : : * Calculate ClientKey. Returns 0 on success, -1 on failure with *errstr
139 : : * pointing to a message about the error details.
140 : : */
141 : : int
991 142 : 109 : scram_ClientKey(const uint8 *salted_password,
143 : : pg_cryptohash_type hash_type, int key_length,
144 : : uint8 *result, const char **errstr)
145 : : {
146 : 109 : pg_hmac_ctx *ctx = pg_hmac_create(hash_type);
147 : :
1617 148 [ - + ]: 109 : if (ctx == NULL)
149 : : {
1332 michael@paquier.xyz 150 :UBC 0 : *errstr = pg_hmac_error(NULL); /* returns OOM */
1617 151 : 0 : return -1;
152 : : }
153 : :
991 michael@paquier.xyz 154 [ + - + - ]:CBC 218 : if (pg_hmac_init(ctx, salted_password, key_length) < 0 ||
1617 155 [ - + ]: 218 : pg_hmac_update(ctx, (uint8 *) "Client Key", strlen("Client Key")) < 0 ||
991 156 : 109 : pg_hmac_final(ctx, result, key_length) < 0)
157 : : {
1332 michael@paquier.xyz 158 :UBC 0 : *errstr = pg_hmac_error(ctx);
1617 159 : 0 : pg_hmac_free(ctx);
1739 160 : 0 : return -1;
161 : : }
162 : :
1617 michael@paquier.xyz 163 :CBC 109 : pg_hmac_free(ctx);
1739 164 : 109 : return 0;
165 : : }
166 : :
167 : : /*
168 : : * Calculate ServerKey. Returns 0 on success, -1 on failure with *errstr
169 : : * pointing to a message about the error details.
170 : : */
171 : : int
991 172 : 130 : scram_ServerKey(const uint8 *salted_password,
173 : : pg_cryptohash_type hash_type, int key_length,
174 : : uint8 *result, const char **errstr)
175 : : {
176 : 130 : pg_hmac_ctx *ctx = pg_hmac_create(hash_type);
177 : :
1617 178 [ - + ]: 130 : if (ctx == NULL)
179 : : {
1332 michael@paquier.xyz 180 :UBC 0 : *errstr = pg_hmac_error(NULL); /* returns OOM */
1617 181 : 0 : return -1;
182 : : }
183 : :
991 michael@paquier.xyz 184 [ + - + - ]:CBC 260 : if (pg_hmac_init(ctx, salted_password, key_length) < 0 ||
1617 185 [ - + ]: 260 : pg_hmac_update(ctx, (uint8 *) "Server Key", strlen("Server Key")) < 0 ||
991 186 : 130 : pg_hmac_final(ctx, result, key_length) < 0)
187 : : {
1332 michael@paquier.xyz 188 :UBC 0 : *errstr = pg_hmac_error(ctx);
1617 189 : 0 : pg_hmac_free(ctx);
1739 190 : 0 : return -1;
191 : : }
192 : :
1617 michael@paquier.xyz 193 :CBC 130 : pg_hmac_free(ctx);
1739 194 : 130 : return 0;
195 : : }
196 : :
197 : :
198 : : /*
199 : : * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
200 : : *
201 : : * The password should already have been processed with SASLprep, if necessary!
202 : : *
203 : : * The result is palloc'd or malloc'd, so caller is responsible for freeing it.
204 : : *
205 : : * On error, returns NULL and sets *errstr to point to a message about the
206 : : * error details.
207 : : */
208 : : char *
991 209 : 52 : scram_build_secret(pg_cryptohash_type hash_type, int key_length,
210 : : const uint8 *salt, int saltlen, int iterations,
211 : : const char *password, const char **errstr)
212 : : {
213 : : uint8 salted_password[SCRAM_MAX_KEY_LEN];
214 : : uint8 stored_key[SCRAM_MAX_KEY_LEN];
215 : : uint8 server_key[SCRAM_MAX_KEY_LEN];
216 : : char *result;
217 : : char *p;
218 : : int maxlen;
219 : : int encoded_salt_len;
220 : : int encoded_stored_len;
221 : : int encoded_server_len;
222 : : int encoded_result;
223 : :
224 : : /* Only this hash method is supported currently */
225 [ - + ]: 52 : Assert(hash_type == PG_SHA256);
226 : :
894 dgustafsson@postgres 227 [ - + ]: 52 : Assert(iterations > 0);
228 : :
229 : : /* Calculate StoredKey and ServerKey */
991 michael@paquier.xyz 230 [ + - ]: 52 : if (scram_SaltedPassword(password, hash_type, key_length,
231 : : salt, saltlen, iterations,
1332 232 [ + - ]: 52 : salted_password, errstr) < 0 ||
991 233 : 52 : scram_ClientKey(salted_password, hash_type, key_length,
234 [ + - ]: 52 : stored_key, errstr) < 0 ||
235 : 52 : scram_H(stored_key, hash_type, key_length,
236 [ - + ]: 52 : stored_key, errstr) < 0 ||
237 : 52 : scram_ServerKey(salted_password, hash_type, key_length,
238 : : server_key, errstr) < 0)
239 : : {
240 : : /* errstr is filled already here */
241 : : #ifdef FRONTEND
1739 michael@paquier.xyz 242 :UBC 0 : return NULL;
243 : : #else
1332 244 [ # # ]: 0 : elog(ERROR, "could not calculate stored key and server key: %s",
245 : : *errstr);
246 : : #endif
247 : : }
248 : :
249 : : /*----------
250 : : * The format is:
251 : : * SCRAM-SHA-256$<iteration count>:<salt>$<StoredKey>:<ServerKey>
252 : : *----------
253 : : */
2256 michael@paquier.xyz 254 :CBC 52 : encoded_salt_len = pg_b64_enc_len(saltlen);
991 255 : 52 : encoded_stored_len = pg_b64_enc_len(key_length);
256 : 52 : encoded_server_len = pg_b64_enc_len(key_length);
257 : :
3048 heikki.linnakangas@i 258 : 52 : maxlen = strlen("SCRAM-SHA-256") + 1
259 : : + 10 + 1 /* iteration count */
260 : : + encoded_salt_len + 1 /* Base64-encoded salt */
2256 michael@paquier.xyz 261 : 52 : + encoded_stored_len + 1 /* Base64-encoded StoredKey */
262 : 52 : + encoded_server_len + 1; /* Base64-encoded ServerKey */
263 : :
264 : : #ifdef FRONTEND
3048 heikki.linnakangas@i 265 : 1 : result = malloc(maxlen);
266 [ - + ]: 1 : if (!result)
267 : : {
1332 michael@paquier.xyz 268 :UBC 0 : *errstr = _("out of memory");
3048 heikki.linnakangas@i 269 : 0 : return NULL;
270 : : }
271 : : #else
3048 heikki.linnakangas@i 272 :CBC 51 : result = palloc(maxlen);
273 : : #endif
274 : :
275 : 52 : p = result + sprintf(result, "SCRAM-SHA-256$%d:", iterations);
276 : :
277 : : /* salt */
2256 michael@paquier.xyz 278 : 52 : encoded_result = pg_b64_encode(salt, saltlen, p, encoded_salt_len);
279 [ - + ]: 52 : if (encoded_result < 0)
280 : : {
1332 michael@paquier.xyz 281 :UBC 0 : *errstr = _("could not encode salt");
282 : : #ifdef FRONTEND
2256 283 : 0 : free(result);
284 : 0 : return NULL;
285 : : #else
1332 286 [ # # ]: 0 : elog(ERROR, "%s", *errstr);
287 : : #endif
288 : : }
2256 michael@paquier.xyz 289 :CBC 52 : p += encoded_result;
3048 heikki.linnakangas@i 290 : 52 : *(p++) = '$';
291 : :
292 : : /* stored key */
121 293 : 52 : encoded_result = pg_b64_encode(stored_key, key_length, p,
294 : : encoded_stored_len);
2256 michael@paquier.xyz 295 [ - + ]: 52 : if (encoded_result < 0)
296 : : {
1332 michael@paquier.xyz 297 :UBC 0 : *errstr = _("could not encode stored key");
298 : : #ifdef FRONTEND
2256 299 : 0 : free(result);
300 : 0 : return NULL;
301 : : #else
1332 302 [ # # ]: 0 : elog(ERROR, "%s", *errstr);
303 : : #endif
304 : : }
305 : :
2256 michael@paquier.xyz 306 :CBC 52 : p += encoded_result;
3048 heikki.linnakangas@i 307 : 52 : *(p++) = ':';
308 : :
309 : : /* server key */
121 310 : 52 : encoded_result = pg_b64_encode(server_key, key_length, p,
311 : : encoded_server_len);
2256 michael@paquier.xyz 312 [ - + ]: 52 : if (encoded_result < 0)
313 : : {
1332 michael@paquier.xyz 314 :UBC 0 : *errstr = _("could not encode server key");
315 : : #ifdef FRONTEND
2256 316 : 0 : free(result);
317 : 0 : return NULL;
318 : : #else
1332 319 [ # # ]: 0 : elog(ERROR, "%s", *errstr);
320 : : #endif
321 : : }
322 : :
2256 michael@paquier.xyz 323 :CBC 52 : p += encoded_result;
3048 heikki.linnakangas@i 324 : 52 : *(p++) = '\0';
325 : :
326 [ - + ]: 52 : Assert(p - result <= maxlen);
327 : :
328 : 52 : return result;
329 : : }
|