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-2019, 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/scram-common.h"
24#include "port/pg_bswap.h"
25
26#define HMAC_IPAD 0x36
27#define HMAC_OPAD 0x5C
28
29/*
30 * Calculate HMAC per RFC2104.
31 *
32 * The hash function used is SHA-256.
33 */
34void
35scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
36{
37 uint8 k_ipad[SHA256_HMAC_B];
38 int i;
39 uint8 keybuf[SCRAM_KEY_LEN];
40
41 /*
42 * If the key is longer than the block size (64 bytes for SHA-256), pass
43 * it through SHA-256 once to shrink it down.
44 */
45 if (keylen > SHA256_HMAC_B)
46 {
47 pg_sha256_ctx sha256_ctx;
48
49 pg_sha256_init(&sha256_ctx);
50 pg_sha256_update(&sha256_ctx, key, keylen);
51 pg_sha256_final(&sha256_ctx, keybuf);
52 key = keybuf;
53 keylen = SCRAM_KEY_LEN;
54 }
55
56 memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
57 memset(ctx->k_opad, HMAC_OPAD, SHA256_HMAC_B);
58
59 for (i = 0; i < keylen; i++)
60 {
61 k_ipad[i] ^= key[i];
62 ctx->k_opad[i] ^= key[i];
63 }
64
65 /* tmp = H(K XOR ipad, text) */
66 pg_sha256_init(&ctx->sha256ctx);
67 pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
68}
69
70/*
71 * Update HMAC calculation
72 * The hash function used is SHA-256.
73 */
74void
75scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
76{
77 pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
78}
79
80/*
81 * Finalize HMAC calculation.
82 * The hash function used is SHA-256.
83 */
84void
85scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
86{
87 uint8 h[SCRAM_KEY_LEN];
88
89 pg_sha256_final(&ctx->sha256ctx, h);
90
91 /* H(K XOR opad, tmp) */
92 pg_sha256_init(&ctx->sha256ctx);
93 pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
94 pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
95 pg_sha256_final(&ctx->sha256ctx, result);
96}
97
98/*
99 * Calculate SaltedPassword.
100 *
101 * The password should already be normalized by SASLprep.
102 */
103void
104scram_SaltedPassword(const char *password,
105 const char *salt, int saltlen, int iterations,
106 uint8 *result)
107{
108 int password_len = strlen(password);
109 uint32 one = pg_hton32(1);
110 int i,
111 j;
112 uint8 Ui[SCRAM_KEY_LEN];
113 uint8 Ui_prev[SCRAM_KEY_LEN];
114 scram_HMAC_ctx hmac_ctx;
115
116 /*
117 * Iterate hash calculation of HMAC entry using given salt. This is
118 * essentially PBKDF2 (see RFC2898) with HMAC() as the pseudorandom
119 * function.
120 */
121
122 /* First iteration */
123 scram_HMAC_init(&hmac_ctx, (uint8 *) password, password_len);
124 scram_HMAC_update(&hmac_ctx, salt, saltlen);
125 scram_HMAC_update(&hmac_ctx, (char *) &one, sizeof(uint32));
126 scram_HMAC_final(Ui_prev, &hmac_ctx);
127 memcpy(result, Ui_prev, SCRAM_KEY_LEN);
128
129 /* Subsequent iterations */
130 for (i = 2; i <= iterations; i++)
131 {
132 scram_HMAC_init(&hmac_ctx, (uint8 *) password, password_len);
133 scram_HMAC_update(&hmac_ctx, (const char *) Ui_prev, SCRAM_KEY_LEN);
134 scram_HMAC_final(Ui, &hmac_ctx);
135 for (j = 0; j < SCRAM_KEY_LEN; j++)
136 result[j] ^= Ui[j];
137 memcpy(Ui_prev, Ui, SCRAM_KEY_LEN);
138 }
139}
140
141
142/*
143 * Calculate SHA-256 hash for a NULL-terminated string. (The NULL terminator is
144 * not included in the hash).
145 */
146void
147scram_H(const uint8 *input, int len, uint8 *result)
148{
149 pg_sha256_ctx ctx;
150
151 pg_sha256_init(&ctx);
152 pg_sha256_update(&ctx, input, len);
153 pg_sha256_final(&ctx, result);
154}
155
156/*
157 * Calculate ClientKey.
158 */
159void
160scram_ClientKey(const uint8 *salted_password, uint8 *result)
161{
162 scram_HMAC_ctx ctx;
163
164 scram_HMAC_init(&ctx, salted_password, SCRAM_KEY_LEN);
165 scram_HMAC_update(&ctx, "Client Key", strlen("Client Key"));
166 scram_HMAC_final(result, &ctx);
167}
168
169/*
170 * Calculate ServerKey.
171 */
172void
173scram_ServerKey(const uint8 *salted_password, uint8 *result)
174{
175 scram_HMAC_ctx ctx;
176
177 scram_HMAC_init(&ctx, salted_password, SCRAM_KEY_LEN);
178 scram_HMAC_update(&ctx, "Server Key", strlen("Server Key"));
179 scram_HMAC_final(result, &ctx);
180}
181
182
183/*
184 * Construct a verifier string for SCRAM, stored in pg_authid.rolpassword.
185 *
186 * The password should already have been processed with SASLprep, if necessary!
187 *
188 * If iterations is 0, default number of iterations is used. The result is
189 * palloc'd or malloc'd, so caller is responsible for freeing it.
190 */
191char *
192scram_build_verifier(const char *salt, int saltlen, int iterations,
193 const char *password)
194{
195 uint8 salted_password[SCRAM_KEY_LEN];
196 uint8 stored_key[SCRAM_KEY_LEN];
197 uint8 server_key[SCRAM_KEY_LEN];
198 char *result;
199 char *p;
200 int maxlen;
201
202 if (iterations <= 0)
203 iterations = SCRAM_DEFAULT_ITERATIONS;
204
205 /* Calculate StoredKey and ServerKey */
206 scram_SaltedPassword(password, salt, saltlen, iterations,
207 salted_password);
208 scram_ClientKey(salted_password, stored_key);
209 scram_H(stored_key, SCRAM_KEY_LEN, stored_key);
210
211 scram_ServerKey(salted_password, server_key);
212
213 /*----------
214 * The format is:
215 * SCRAM-SHA-256$<iteration count>:<salt>$<StoredKey>:<ServerKey>
216 *----------
217 */
218 maxlen = strlen("SCRAM-SHA-256") + 1
219 + 10 + 1 /* iteration count */
220 + pg_b64_enc_len(saltlen) + 1 /* Base64-encoded salt */
221 + pg_b64_enc_len(SCRAM_KEY_LEN) + 1 /* Base64-encoded StoredKey */
222 + pg_b64_enc_len(SCRAM_KEY_LEN) + 1; /* Base64-encoded ServerKey */
223
224#ifdef FRONTEND
225 result = malloc(maxlen);
226 if (!result)
227 return NULL;
228#else
229 result = palloc(maxlen);
230#endif
231
232 p = result + sprintf(result, "SCRAM-SHA-256$%d:", iterations);
233
234 p += pg_b64_encode(salt, saltlen, p);
235 *(p++) = '$';
236 p += pg_b64_encode((char *) stored_key, SCRAM_KEY_LEN, p);
237 *(p++) = ':';
238 p += pg_b64_encode((char *) server_key, SCRAM_KEY_LEN, p);
239 *(p++) = '\0';
240
241 Assert(p - result <= maxlen);
242
243 return result;
244}
245