/src/postgres/src/common/scram-common.c
Line | Count | Source (jump to first uncovered line) |
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 |
38 | | 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 | 0 | { |
43 | 0 | int password_len = strlen(password); |
44 | 0 | uint32 one = pg_hton32(1); |
45 | 0 | int i, |
46 | 0 | j; |
47 | 0 | uint8 Ui[SCRAM_MAX_KEY_LEN]; |
48 | 0 | uint8 Ui_prev[SCRAM_MAX_KEY_LEN]; |
49 | 0 | pg_hmac_ctx *hmac_ctx = pg_hmac_create(hash_type); |
50 | |
|
51 | 0 | if (hmac_ctx == NULL) |
52 | 0 | { |
53 | 0 | *errstr = pg_hmac_error(NULL); /* returns OOM */ |
54 | 0 | return -1; |
55 | 0 | } |
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 */ |
64 | 0 | if (pg_hmac_init(hmac_ctx, (uint8 *) password, password_len) < 0 || |
65 | 0 | pg_hmac_update(hmac_ctx, salt, saltlen) < 0 || |
66 | 0 | pg_hmac_update(hmac_ctx, (uint8 *) &one, sizeof(uint32)) < 0 || |
67 | 0 | pg_hmac_final(hmac_ctx, Ui_prev, key_length) < 0) |
68 | 0 | { |
69 | 0 | *errstr = pg_hmac_error(hmac_ctx); |
70 | 0 | pg_hmac_free(hmac_ctx); |
71 | 0 | return -1; |
72 | 0 | } |
73 | | |
74 | 0 | memcpy(result, Ui_prev, key_length); |
75 | | |
76 | | /* Subsequent iterations */ |
77 | 0 | for (i = 1; i < iterations; i++) |
78 | 0 | { |
79 | 0 | #ifndef FRONTEND |
80 | | /* |
81 | | * Make sure that this is interruptible as scram_iterations could be |
82 | | * set to a large value. |
83 | | */ |
84 | 0 | CHECK_FOR_INTERRUPTS(); |
85 | 0 | #endif |
86 | |
|
87 | 0 | if (pg_hmac_init(hmac_ctx, (uint8 *) password, password_len) < 0 || |
88 | 0 | pg_hmac_update(hmac_ctx, (uint8 *) Ui_prev, key_length) < 0 || |
89 | 0 | pg_hmac_final(hmac_ctx, Ui, key_length) < 0) |
90 | 0 | { |
91 | 0 | *errstr = pg_hmac_error(hmac_ctx); |
92 | 0 | pg_hmac_free(hmac_ctx); |
93 | 0 | return -1; |
94 | 0 | } |
95 | | |
96 | 0 | for (j = 0; j < key_length; j++) |
97 | 0 | result[j] ^= Ui[j]; |
98 | 0 | memcpy(Ui_prev, Ui, key_length); |
99 | 0 | } |
100 | | |
101 | 0 | pg_hmac_free(hmac_ctx); |
102 | 0 | return 0; |
103 | 0 | } |
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 |
112 | | scram_H(const uint8 *input, pg_cryptohash_type hash_type, int key_length, |
113 | | uint8 *result, const char **errstr) |
114 | 0 | { |
115 | 0 | pg_cryptohash_ctx *ctx; |
116 | |
|
117 | 0 | ctx = pg_cryptohash_create(hash_type); |
118 | 0 | if (ctx == NULL) |
119 | 0 | { |
120 | 0 | *errstr = pg_cryptohash_error(NULL); /* returns OOM */ |
121 | 0 | return -1; |
122 | 0 | } |
123 | | |
124 | 0 | if (pg_cryptohash_init(ctx) < 0 || |
125 | 0 | pg_cryptohash_update(ctx, input, key_length) < 0 || |
126 | 0 | pg_cryptohash_final(ctx, result, key_length) < 0) |
127 | 0 | { |
128 | 0 | *errstr = pg_cryptohash_error(ctx); |
129 | 0 | pg_cryptohash_free(ctx); |
130 | 0 | return -1; |
131 | 0 | } |
132 | | |
133 | 0 | pg_cryptohash_free(ctx); |
134 | 0 | return 0; |
135 | 0 | } |
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 |
142 | | scram_ClientKey(const uint8 *salted_password, |
143 | | pg_cryptohash_type hash_type, int key_length, |
144 | | uint8 *result, const char **errstr) |
145 | 0 | { |
146 | 0 | pg_hmac_ctx *ctx = pg_hmac_create(hash_type); |
147 | |
|
148 | 0 | if (ctx == NULL) |
149 | 0 | { |
150 | 0 | *errstr = pg_hmac_error(NULL); /* returns OOM */ |
151 | 0 | return -1; |
152 | 0 | } |
153 | | |
154 | 0 | if (pg_hmac_init(ctx, salted_password, key_length) < 0 || |
155 | 0 | pg_hmac_update(ctx, (uint8 *) "Client Key", strlen("Client Key")) < 0 || |
156 | 0 | pg_hmac_final(ctx, result, key_length) < 0) |
157 | 0 | { |
158 | 0 | *errstr = pg_hmac_error(ctx); |
159 | 0 | pg_hmac_free(ctx); |
160 | 0 | return -1; |
161 | 0 | } |
162 | | |
163 | 0 | pg_hmac_free(ctx); |
164 | 0 | return 0; |
165 | 0 | } |
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 |
172 | | scram_ServerKey(const uint8 *salted_password, |
173 | | pg_cryptohash_type hash_type, int key_length, |
174 | | uint8 *result, const char **errstr) |
175 | 0 | { |
176 | 0 | pg_hmac_ctx *ctx = pg_hmac_create(hash_type); |
177 | |
|
178 | 0 | if (ctx == NULL) |
179 | 0 | { |
180 | 0 | *errstr = pg_hmac_error(NULL); /* returns OOM */ |
181 | 0 | return -1; |
182 | 0 | } |
183 | | |
184 | 0 | if (pg_hmac_init(ctx, salted_password, key_length) < 0 || |
185 | 0 | pg_hmac_update(ctx, (uint8 *) "Server Key", strlen("Server Key")) < 0 || |
186 | 0 | pg_hmac_final(ctx, result, key_length) < 0) |
187 | 0 | { |
188 | 0 | *errstr = pg_hmac_error(ctx); |
189 | 0 | pg_hmac_free(ctx); |
190 | 0 | return -1; |
191 | 0 | } |
192 | | |
193 | 0 | pg_hmac_free(ctx); |
194 | 0 | return 0; |
195 | 0 | } |
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 * |
209 | | 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 | 0 | { |
213 | 0 | uint8 salted_password[SCRAM_MAX_KEY_LEN]; |
214 | 0 | uint8 stored_key[SCRAM_MAX_KEY_LEN]; |
215 | 0 | uint8 server_key[SCRAM_MAX_KEY_LEN]; |
216 | 0 | char *result; |
217 | 0 | char *p; |
218 | 0 | int maxlen; |
219 | 0 | int encoded_salt_len; |
220 | 0 | int encoded_stored_len; |
221 | 0 | int encoded_server_len; |
222 | 0 | int encoded_result; |
223 | | |
224 | | /* Only this hash method is supported currently */ |
225 | 0 | Assert(hash_type == PG_SHA256); |
226 | |
|
227 | 0 | Assert(iterations > 0); |
228 | | |
229 | | /* Calculate StoredKey and ServerKey */ |
230 | 0 | if (scram_SaltedPassword(password, hash_type, key_length, |
231 | 0 | salt, saltlen, iterations, |
232 | 0 | salted_password, errstr) < 0 || |
233 | 0 | scram_ClientKey(salted_password, hash_type, key_length, |
234 | 0 | stored_key, errstr) < 0 || |
235 | 0 | scram_H(stored_key, hash_type, key_length, |
236 | 0 | stored_key, errstr) < 0 || |
237 | 0 | scram_ServerKey(salted_password, hash_type, key_length, |
238 | 0 | server_key, errstr) < 0) |
239 | 0 | { |
240 | | /* errstr is filled already here */ |
241 | | #ifdef FRONTEND |
242 | | return NULL; |
243 | | #else |
244 | 0 | elog(ERROR, "could not calculate stored key and server key: %s", |
245 | 0 | *errstr); |
246 | 0 | #endif |
247 | 0 | } |
248 | | |
249 | | /*---------- |
250 | | * The format is: |
251 | | * SCRAM-SHA-256$<iteration count>:<salt>$<StoredKey>:<ServerKey> |
252 | | *---------- |
253 | | */ |
254 | 0 | encoded_salt_len = pg_b64_enc_len(saltlen); |
255 | 0 | encoded_stored_len = pg_b64_enc_len(key_length); |
256 | 0 | encoded_server_len = pg_b64_enc_len(key_length); |
257 | |
|
258 | 0 | maxlen = strlen("SCRAM-SHA-256") + 1 |
259 | 0 | + 10 + 1 /* iteration count */ |
260 | 0 | + encoded_salt_len + 1 /* Base64-encoded salt */ |
261 | 0 | + encoded_stored_len + 1 /* Base64-encoded StoredKey */ |
262 | 0 | + encoded_server_len + 1; /* Base64-encoded ServerKey */ |
263 | |
|
264 | | #ifdef FRONTEND |
265 | | result = malloc(maxlen); |
266 | | if (!result) |
267 | | { |
268 | | *errstr = _("out of memory"); |
269 | | return NULL; |
270 | | } |
271 | | #else |
272 | 0 | result = palloc(maxlen); |
273 | 0 | #endif |
274 | |
|
275 | 0 | p = result + sprintf(result, "SCRAM-SHA-256$%d:", iterations); |
276 | | |
277 | | /* salt */ |
278 | 0 | encoded_result = pg_b64_encode(salt, saltlen, p, encoded_salt_len); |
279 | 0 | if (encoded_result < 0) |
280 | 0 | { |
281 | 0 | *errstr = _("could not encode salt"); |
282 | | #ifdef FRONTEND |
283 | | free(result); |
284 | | return NULL; |
285 | | #else |
286 | 0 | elog(ERROR, "%s", *errstr); |
287 | 0 | #endif |
288 | 0 | } |
289 | 0 | p += encoded_result; |
290 | 0 | *(p++) = '$'; |
291 | | |
292 | | /* stored key */ |
293 | 0 | encoded_result = pg_b64_encode(stored_key, key_length, p, |
294 | 0 | encoded_stored_len); |
295 | 0 | if (encoded_result < 0) |
296 | 0 | { |
297 | 0 | *errstr = _("could not encode stored key"); |
298 | | #ifdef FRONTEND |
299 | | free(result); |
300 | | return NULL; |
301 | | #else |
302 | 0 | elog(ERROR, "%s", *errstr); |
303 | 0 | #endif |
304 | 0 | } |
305 | | |
306 | 0 | p += encoded_result; |
307 | 0 | *(p++) = ':'; |
308 | | |
309 | | /* server key */ |
310 | 0 | encoded_result = pg_b64_encode(server_key, key_length, p, |
311 | 0 | encoded_server_len); |
312 | 0 | if (encoded_result < 0) |
313 | 0 | { |
314 | 0 | *errstr = _("could not encode server key"); |
315 | | #ifdef FRONTEND |
316 | | free(result); |
317 | | return NULL; |
318 | | #else |
319 | 0 | elog(ERROR, "%s", *errstr); |
320 | 0 | #endif |
321 | 0 | } |
322 | | |
323 | 0 | p += encoded_result; |
324 | 0 | *(p++) = '\0'; |
325 | |
|
326 | 0 | Assert(p - result <= maxlen); |
327 | |
|
328 | 0 | return result; |
329 | 0 | } |