vacuumlazy.c: Tweak local variable name.
[pgsql.git] / src / common / scram-common.c
blobbd40d497a9606ecfc91603ae9a11db73b8eb9d8c
1 /*-------------------------------------------------------------------------
2 * scram-common.c
3 * Shared frontend/backend code for SCRAM authentication
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.
9 * Portions Copyright (c) 2017-2023, PostgreSQL Global Development Group
11 * IDENTIFICATION
12 * src/common/scram-common.c
14 *-------------------------------------------------------------------------
16 #ifndef FRONTEND
17 #include "postgres.h"
18 #else
19 #include "postgres_fe.h"
20 #endif
22 #include "common/base64.h"
23 #include "common/hmac.h"
24 #include "common/scram-common.h"
25 #include "port/pg_bswap.h"
28 * Calculate SaltedPassword.
30 * The password should already be normalized by SASLprep. Returns 0 on
31 * success, -1 on failure with *errstr pointing to a message about the
32 * error details.
34 int
35 scram_SaltedPassword(const char *password,
36 pg_cryptohash_type hash_type, int key_length,
37 const char *salt, int saltlen, int iterations,
38 uint8 *result, const char **errstr)
40 int password_len = strlen(password);
41 uint32 one = pg_hton32(1);
42 int i,
44 uint8 Ui[SCRAM_MAX_KEY_LEN];
45 uint8 Ui_prev[SCRAM_MAX_KEY_LEN];
46 pg_hmac_ctx *hmac_ctx = pg_hmac_create(hash_type);
48 if (hmac_ctx == NULL)
50 *errstr = pg_hmac_error(NULL); /* returns OOM */
51 return -1;
55 * Iterate hash calculation of HMAC entry using given salt. This is
56 * essentially PBKDF2 (see RFC2898) with HMAC() as the pseudorandom
57 * function.
60 /* First iteration */
61 if (pg_hmac_init(hmac_ctx, (uint8 *) password, password_len) < 0 ||
62 pg_hmac_update(hmac_ctx, (uint8 *) salt, saltlen) < 0 ||
63 pg_hmac_update(hmac_ctx, (uint8 *) &one, sizeof(uint32)) < 0 ||
64 pg_hmac_final(hmac_ctx, Ui_prev, key_length) < 0)
66 *errstr = pg_hmac_error(hmac_ctx);
67 pg_hmac_free(hmac_ctx);
68 return -1;
71 memcpy(result, Ui_prev, key_length);
73 /* Subsequent iterations */
74 for (i = 2; i <= iterations; i++)
76 if (pg_hmac_init(hmac_ctx, (uint8 *) password, password_len) < 0 ||
77 pg_hmac_update(hmac_ctx, (uint8 *) Ui_prev, key_length) < 0 ||
78 pg_hmac_final(hmac_ctx, Ui, key_length) < 0)
80 *errstr = pg_hmac_error(hmac_ctx);
81 pg_hmac_free(hmac_ctx);
82 return -1;
85 for (j = 0; j < key_length; j++)
86 result[j] ^= Ui[j];
87 memcpy(Ui_prev, Ui, key_length);
90 pg_hmac_free(hmac_ctx);
91 return 0;
96 * Calculate hash for a NULL-terminated string. (The NULL terminator is
97 * not included in the hash). Returns 0 on success, -1 on failure with *errstr
98 * pointing to a message about the error details.
101 scram_H(const uint8 *input, pg_cryptohash_type hash_type, int key_length,
102 uint8 *result, const char **errstr)
104 pg_cryptohash_ctx *ctx;
106 ctx = pg_cryptohash_create(hash_type);
107 if (ctx == NULL)
109 *errstr = pg_cryptohash_error(NULL); /* returns OOM */
110 return -1;
113 if (pg_cryptohash_init(ctx) < 0 ||
114 pg_cryptohash_update(ctx, input, key_length) < 0 ||
115 pg_cryptohash_final(ctx, result, key_length) < 0)
117 *errstr = pg_cryptohash_error(ctx);
118 pg_cryptohash_free(ctx);
119 return -1;
122 pg_cryptohash_free(ctx);
123 return 0;
127 * Calculate ClientKey. Returns 0 on success, -1 on failure with *errstr
128 * pointing to a message about the error details.
131 scram_ClientKey(const uint8 *salted_password,
132 pg_cryptohash_type hash_type, int key_length,
133 uint8 *result, const char **errstr)
135 pg_hmac_ctx *ctx = pg_hmac_create(hash_type);
137 if (ctx == NULL)
139 *errstr = pg_hmac_error(NULL); /* returns OOM */
140 return -1;
143 if (pg_hmac_init(ctx, salted_password, key_length) < 0 ||
144 pg_hmac_update(ctx, (uint8 *) "Client Key", strlen("Client Key")) < 0 ||
145 pg_hmac_final(ctx, result, key_length) < 0)
147 *errstr = pg_hmac_error(ctx);
148 pg_hmac_free(ctx);
149 return -1;
152 pg_hmac_free(ctx);
153 return 0;
157 * Calculate ServerKey. Returns 0 on success, -1 on failure with *errstr
158 * pointing to a message about the error details.
161 scram_ServerKey(const uint8 *salted_password,
162 pg_cryptohash_type hash_type, int key_length,
163 uint8 *result, const char **errstr)
165 pg_hmac_ctx *ctx = pg_hmac_create(hash_type);
167 if (ctx == NULL)
169 *errstr = pg_hmac_error(NULL); /* returns OOM */
170 return -1;
173 if (pg_hmac_init(ctx, salted_password, key_length) < 0 ||
174 pg_hmac_update(ctx, (uint8 *) "Server Key", strlen("Server Key")) < 0 ||
175 pg_hmac_final(ctx, result, key_length) < 0)
177 *errstr = pg_hmac_error(ctx);
178 pg_hmac_free(ctx);
179 return -1;
182 pg_hmac_free(ctx);
183 return 0;
188 * Construct a SCRAM secret, for storing in pg_authid.rolpassword.
190 * The password should already have been processed with SASLprep, if necessary!
192 * If iterations is 0, default number of iterations is used. The result is
193 * palloc'd or malloc'd, so caller is responsible for freeing it.
195 * On error, returns NULL and sets *errstr to point to a message about the
196 * error details.
198 char *
199 scram_build_secret(pg_cryptohash_type hash_type, int key_length,
200 const char *salt, int saltlen, int iterations,
201 const char *password, const char **errstr)
203 uint8 salted_password[SCRAM_MAX_KEY_LEN];
204 uint8 stored_key[SCRAM_MAX_KEY_LEN];
205 uint8 server_key[SCRAM_MAX_KEY_LEN];
206 char *result;
207 char *p;
208 int maxlen;
209 int encoded_salt_len;
210 int encoded_stored_len;
211 int encoded_server_len;
212 int encoded_result;
214 /* Only this hash method is supported currently */
215 Assert(hash_type == PG_SHA256);
217 if (iterations <= 0)
218 iterations = SCRAM_DEFAULT_ITERATIONS;
220 /* Calculate StoredKey and ServerKey */
221 if (scram_SaltedPassword(password, hash_type, key_length,
222 salt, saltlen, iterations,
223 salted_password, errstr) < 0 ||
224 scram_ClientKey(salted_password, hash_type, key_length,
225 stored_key, errstr) < 0 ||
226 scram_H(stored_key, hash_type, key_length,
227 stored_key, errstr) < 0 ||
228 scram_ServerKey(salted_password, hash_type, key_length,
229 server_key, errstr) < 0)
231 /* errstr is filled already here */
232 #ifdef FRONTEND
233 return NULL;
234 #else
235 elog(ERROR, "could not calculate stored key and server key: %s",
236 *errstr);
237 #endif
240 /*----------
241 * The format is:
242 * SCRAM-SHA-256$<iteration count>:<salt>$<StoredKey>:<ServerKey>
243 *----------
245 encoded_salt_len = pg_b64_enc_len(saltlen);
246 encoded_stored_len = pg_b64_enc_len(key_length);
247 encoded_server_len = pg_b64_enc_len(key_length);
249 maxlen = strlen("SCRAM-SHA-256") + 1
250 + 10 + 1 /* iteration count */
251 + encoded_salt_len + 1 /* Base64-encoded salt */
252 + encoded_stored_len + 1 /* Base64-encoded StoredKey */
253 + encoded_server_len + 1; /* Base64-encoded ServerKey */
255 #ifdef FRONTEND
256 result = malloc(maxlen);
257 if (!result)
259 *errstr = _("out of memory");
260 return NULL;
262 #else
263 result = palloc(maxlen);
264 #endif
266 p = result + sprintf(result, "SCRAM-SHA-256$%d:", iterations);
268 /* salt */
269 encoded_result = pg_b64_encode(salt, saltlen, p, encoded_salt_len);
270 if (encoded_result < 0)
272 *errstr = _("could not encode salt");
273 #ifdef FRONTEND
274 free(result);
275 return NULL;
276 #else
277 elog(ERROR, "%s", *errstr);
278 #endif
280 p += encoded_result;
281 *(p++) = '$';
283 /* stored key */
284 encoded_result = pg_b64_encode((char *) stored_key, key_length, p,
285 encoded_stored_len);
286 if (encoded_result < 0)
288 *errstr = _("could not encode stored key");
289 #ifdef FRONTEND
290 free(result);
291 return NULL;
292 #else
293 elog(ERROR, "%s", *errstr);
294 #endif
297 p += encoded_result;
298 *(p++) = ':';
300 /* server key */
301 encoded_result = pg_b64_encode((char *) server_key, key_length, p,
302 encoded_server_len);
303 if (encoded_result < 0)
305 *errstr = _("could not encode server key");
306 #ifdef FRONTEND
307 free(result);
308 return NULL;
309 #else
310 elog(ERROR, "%s", *errstr);
311 #endif
314 p += encoded_result;
315 *(p++) = '\0';
317 Assert(p - result <= maxlen);
319 return result;