/src/dovecot/src/lib-auth/auth-scram.c
Line | Count | Source |
1 | | /* Copyright (c) 2022-2023 Dovecot authors, see the included COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "safe-memset.h" |
5 | | #include "base64.h" |
6 | | #include "hmac.h" |
7 | | #include "randgen.h" |
8 | | #include "str.h" |
9 | | |
10 | | #include "auth-scram.h" |
11 | | |
12 | | void auth_scram_key_data_clear(struct auth_scram_key_data *data) |
13 | 1.64k | { |
14 | 1.64k | if (data->hmethod != NULL) { |
15 | 1.31k | if (data->stored_key != NULL) { |
16 | 1.31k | safe_memset(data->stored_key, 0, |
17 | 1.31k | data->hmethod->digest_size); |
18 | 1.31k | } |
19 | 1.31k | if (data->server_key != NULL) { |
20 | 1.31k | safe_memset(data->server_key, 0, |
21 | 1.31k | data->hmethod->digest_size); |
22 | 1.31k | } |
23 | 1.31k | } else { |
24 | 321 | i_assert(data->stored_key == NULL); |
25 | 321 | i_assert(data->server_key == NULL); |
26 | 321 | } |
27 | 1.64k | } |
28 | | |
29 | | void auth_scram_hi(const struct hash_method *hmethod, |
30 | | const unsigned char *str, size_t str_size, |
31 | | const unsigned char *salt, size_t salt_size, unsigned int i, |
32 | | unsigned char *result) |
33 | 2.24k | { |
34 | 2.24k | struct hmac_context ctx; |
35 | 2.24k | unsigned char U[hmethod->digest_size]; |
36 | 2.24k | unsigned int j, k; |
37 | | |
38 | | /* Hi(str, salt, i): |
39 | | |
40 | | U1 := HMAC(str, salt + INT(1)) |
41 | | U2 := HMAC(str, U1) |
42 | | ... |
43 | | Ui-1 := HMAC(str, Ui-2) |
44 | | Ui := HMAC(str, Ui-1) |
45 | | |
46 | | Hi := U1 XOR U2 XOR ... XOR Ui |
47 | | |
48 | | where "i" is the iteration count, "+" is the string concatenation |
49 | | operator, and INT(g) is a 4-octet encoding of the integer g, most |
50 | | significant octet first. |
51 | | */ |
52 | | |
53 | | /* Calculate U1 */ |
54 | 2.24k | hmac_init(&ctx, str, str_size, hmethod); |
55 | 2.24k | hmac_update(&ctx, salt, salt_size); |
56 | 2.24k | hmac_update(&ctx, "\0\0\0\1", 4); |
57 | 2.24k | hmac_final(&ctx, U); |
58 | | |
59 | 2.24k | memcpy(result, U, hmethod->digest_size); |
60 | | |
61 | | /* Calculate U2 to Ui and Hi */ |
62 | 9.12M | for (j = 2; j <= i; j++) { |
63 | 9.12M | hmac_init(&ctx, str, str_size, hmethod); |
64 | 9.12M | hmac_update(&ctx, U, sizeof(U)); |
65 | 9.12M | hmac_final(&ctx, U); |
66 | 228M | for (k = 0; k < hmethod->digest_size; k++) |
67 | 219M | result[k] ^= U[k]; |
68 | 9.12M | } |
69 | 2.24k | } |
70 | | |
71 | | void auth_scram_generate_key_data(const struct hash_method *hmethod, |
72 | | const char *plaintext, unsigned int rounds, |
73 | | unsigned int *iter_count_r, |
74 | | const char **salt_r, |
75 | | unsigned char stored_key_r[], |
76 | | unsigned char server_key_r[]) |
77 | 1.31k | { |
78 | 1.31k | struct hmac_context ctx; |
79 | 1.31k | unsigned char salt[16]; |
80 | 1.31k | unsigned char salted_password[hmethod->digest_size]; |
81 | 1.31k | unsigned char client_key[hmethod->digest_size]; |
82 | | |
83 | 1.31k | if (rounds == 0) |
84 | 1.31k | rounds = AUTH_SCRAM_DEFAULT_ITERATE_COUNT; |
85 | 0 | else { |
86 | 0 | rounds = I_MAX(I_MIN(AUTH_SCRAM_MAX_ITERATE_COUNT, rounds), |
87 | 0 | AUTH_SCRAM_MIN_ITERATE_COUNT); |
88 | 0 | } |
89 | 1.31k | *iter_count_r = rounds; |
90 | | |
91 | 1.31k | random_fill(salt, sizeof(salt)); |
92 | 1.31k | *salt_r = str_c(t_base64_encode(0, 0, salt, sizeof(salt))); |
93 | | |
94 | | /* FIXME: credentials should be SASLprepped UTF8 data here */ |
95 | 1.31k | auth_scram_hi(hmethod, |
96 | 1.31k | (const unsigned char *)plaintext, strlen(plaintext), |
97 | 1.31k | salt, sizeof(salt), rounds, salted_password); |
98 | | |
99 | | /* Calculate ClientKey */ |
100 | 1.31k | hmac_init(&ctx, salted_password, sizeof(salted_password), hmethod); |
101 | 1.31k | hmac_update(&ctx, "Client Key", 10); |
102 | 1.31k | hmac_final(&ctx, client_key); |
103 | | |
104 | | /* Calculate StoredKey */ |
105 | 1.31k | hash_method_get_digest(hmethod, client_key, sizeof(client_key), |
106 | 1.31k | stored_key_r); |
107 | | |
108 | | /* Calculate ServerKey */ |
109 | 1.31k | hmac_init(&ctx, salted_password, sizeof(salted_password), hmethod); |
110 | 1.31k | hmac_update(&ctx, "Server Key", 10); |
111 | 1.31k | hmac_final(&ctx, server_key_r); |
112 | | |
113 | 1.31k | safe_memset(salted_password, 0, sizeof(salted_password)); |
114 | 1.31k | safe_memset(client_key, 0, sizeof(client_key)); |
115 | 1.31k | } |
116 | | |
117 | | int auth_scram_credentials_parse(const struct hash_method *hmethod, |
118 | | const char *name, |
119 | | const unsigned char *credentials, size_t size, |
120 | | unsigned int *iter_count_r, |
121 | | const char **salt_r, |
122 | | unsigned char stored_key_r[], |
123 | | unsigned char server_key_r[], |
124 | | const char **error_r) |
125 | 1.31k | { |
126 | 1.31k | const char *const *fields; |
127 | 1.31k | buffer_t *buf; |
128 | | |
129 | | /* password string format: iter,salt,stored_key,server_key */ |
130 | 1.31k | fields = t_strsplit(t_strndup(credentials, size), ","); |
131 | | |
132 | 1.31k | if (str_array_length(fields) != 4) { |
133 | 0 | *error_r = t_strdup_printf( |
134 | 0 | "Invalid %s passdb entry format", name); |
135 | 0 | return -1; |
136 | 0 | } |
137 | 1.31k | if (str_to_uint(fields[0], iter_count_r) < 0 || |
138 | 1.31k | *iter_count_r < AUTH_SCRAM_MIN_ITERATE_COUNT || |
139 | 1.31k | *iter_count_r > AUTH_SCRAM_MAX_ITERATE_COUNT) { |
140 | 0 | *error_r = t_strdup_printf( |
141 | 0 | "Invalid %s iteration count in passdb", name); |
142 | 0 | return -1; |
143 | 0 | } |
144 | 1.31k | *salt_r = fields[1]; |
145 | | |
146 | 1.31k | buf = t_buffer_create(hmethod->digest_size); |
147 | 1.31k | if (base64_decode(fields[2], strlen(fields[2]), buf) < 0 || |
148 | 1.31k | buf->used != hmethod->digest_size) { |
149 | 0 | *error_r = t_strdup_printf( |
150 | 0 | "Invalid %s StoredKey in passdb", name); |
151 | 0 | return -1; |
152 | 0 | } |
153 | 1.31k | memcpy(stored_key_r, buf->data, hmethod->digest_size); |
154 | | |
155 | 1.31k | buffer_set_used_size(buf, 0); |
156 | 1.31k | if (base64_decode(fields[3], strlen(fields[3]), buf) < 0 || |
157 | 1.31k | buf->used != hmethod->digest_size) { |
158 | 0 | *error_r = t_strdup_printf( |
159 | 0 | "Invalid %s ServerKey in passdb", name); |
160 | 0 | return -1; |
161 | 0 | } |
162 | 1.31k | memcpy(server_key_r, buf->data, hmethod->digest_size); |
163 | 1.31k | return 0; |
164 | 1.31k | } |