2 * Sub-authentication Daemon: Authentication database
4 * (c) 2017 Martin Mares <mj@ucw.cz>
8 #include <ucw/base64.h>
10 #include <ucw/stkstring.h>
11 #include <ucw/string.h>
12 #include <ucw/unaligned.h>
22 struct auth_token *auth_fake_token;
24 #define HASH_NODE struct auth_user
25 #define HASH_PREFIX(x) user_hash_##x
26 #define HASH_KEY_ENDSTRING login
27 #define HASH_WANT_FIND
28 #define HASH_WANT_LOOKUP
29 #define HASH_WANT_REMOVE
30 #define HASH_GIVE_INIT_DATA
32 static void user_hash_init_data(struct auth_user *au)
34 clist_init(&au->accounts);
37 #include <ucw/hashtable.h>
39 struct auth_zone *auth_find_zone(const char *name)
41 CLIST_FOR_EACH(struct auth_zone *, z, zone_list)
42 if (!strcmp(z->name, name))
47 struct auth_user *auth_find_user(const char *login, bool create)
50 return user_hash_lookup((char *) login);
52 return user_hash_find((char *) login);
55 struct auth_acct *auth_find_acct(struct auth_user *au, struct auth_zone *az, bool create)
57 CLIST_FOR_EACH(struct auth_acct *, aa, au->accounts)
64 struct auth_acct *aa = xmalloc_zero(sizeof(*aa));
65 clist_add_tail(&au->accounts, &aa->n);
68 clist_init(&aa->tokens);
72 struct auth_token *auth_find_token_passwd(struct auth_acct *aa)
74 CLIST_FOR_EACH(struct auth_token *, at, aa->tokens)
75 if (at->type == TOKEN_PASSWORD)
80 struct auth_token *auth_find_token_generated(struct auth_acct *aa, char *ident)
82 CLIST_FOR_EACH(struct auth_token *, at, aa->tokens)
83 if (at->type == TOKEN_GENERATED && !strcmp(at->ident, ident))
88 void auth_delete_user(struct auth_user *au)
91 while (aa = clist_head(&au->accounts))
97 void auth_delete_acct(struct auth_acct *ac)
99 struct auth_token *at;
100 while (at = clist_head(&ac->tokens))
101 auth_delete_token(at);
102 clist_remove(&ac->n);
105 void auth_delete_token(struct auth_token *at)
107 clist_remove(&at->n);
113 struct auth_token *auth_create_token(struct auth_acct *aa)
115 struct auth_token *at = xmalloc_zero(sizeof(*at));
116 clist_add_tail(&aa->tokens, &at->n);
118 at->last_modified = time(NULL);
122 static void auth_hash(char *hash_text, struct auth_token *at, const char *passwd)
124 // This is PBKDF2 based on SHA-256-HMAC
127 if (gcry_md_open(&md, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC) != GPG_ERR_NO_ERROR)
128 die("gcry_md_open(CGRY_MD_SHA256) failed");
129 if (gcry_md_setkey(md, passwd, strlen(passwd)) != GPG_ERR_NO_ERROR)
130 die("gcry_md_setkey() failed");
132 byte current_hash[HASH_BYTES];
133 gcry_md_write(md, at->salt, strlen(at->salt));
134 byte zeroes[4] = { 0, 0, 0, 0 };
135 gcry_md_write(md, zeroes, 4);
136 memcpy(current_hash, gcry_md_read(md, 0), HASH_BYTES);
138 byte accumulator[HASH_BYTES];
139 memcpy(accumulator, current_hash, HASH_BYTES);
141 for (uint i=1; i < at->iterations; i++)
144 gcry_md_write(md, current_hash, HASH_BYTES);
145 memcpy(current_hash, gcry_md_read(md, 0), HASH_BYTES);
146 for (uint j=0; j<HASH_BYTES; j++)
147 accumulator[j] ^= current_hash[j];
152 uint out_len = base64_encode(hash_text, accumulator, HASH_BYTES);
153 hash_text[out_len] = 0;
156 static void set_salt(struct auth_token *at)
158 byte salt_bytes[DEFAULT_SALT_BYTES];
159 gcry_randomize(salt_bytes, DEFAULT_SALT_BYTES, GCRY_STRONG_RANDOM);
161 char salt_text[2*DEFAULT_SALT_BYTES + 1];
162 uint salt_len = base64_encode(salt_text, salt_bytes, DEFAULT_SALT_BYTES);
163 salt_text[salt_len] = 0;
165 at->salt = xstrdup(salt_text);
166 at->iterations = DEFAULT_HASH_ITERATIONS;
169 void auth_set_token_passwd(struct auth_token *at, const char *passwd)
171 at->type = TOKEN_PASSWORD;
174 char hash[MAX_TEXT_HASH_SIZE];
175 auth_hash(hash, at, passwd);
176 at->hash = xstrdup(hash);
179 char *auth_set_token_generated(struct auth_token *at, const char *comment)
181 char ident_text[2*DEFAULT_IDENT_BYTES + 1];
184 byte ident_bytes[DEFAULT_IDENT_BYTES];
185 gcry_randomize(ident_bytes, DEFAULT_IDENT_BYTES, GCRY_STRONG_RANDOM);
186 mem_to_hex(ident_text, ident_bytes, DEFAULT_IDENT_BYTES, 0);
188 while (auth_find_token_generated(at->acct, ident_text));
190 at->type = TOKEN_GENERATED;
191 at->ident = xstrdup(ident_text);
192 at->comment = xstrdup(comment);
195 byte token_bytes[DEFAULT_GENERATED_BYTES];
196 gcry_randomize(token_bytes, DEFAULT_GENERATED_BYTES, GCRY_STRONG_RANDOM);
198 uint token_len = 2*DEFAULT_IDENT_BYTES + 1 + 2*DEFAULT_GENERATED_BYTES + 1;
199 char *token_text = xmalloc(token_len);
200 memcpy(token_text, ident_text, 2*DEFAULT_IDENT_BYTES);
201 char *token_rhs = token_text + 2*DEFAULT_IDENT_BYTES;
203 uint out_len = base64_encode(token_rhs, token_bytes, DEFAULT_GENERATED_BYTES);
204 if (out_len > 0 && token_rhs[out_len-1] == '=')
206 token_rhs[out_len] = 0;
208 char hash[MAX_TEXT_HASH_SIZE];
209 auth_hash(hash, at, token_rhs);
210 at->hash = xstrdup(hash);
215 bool auth_check_token(struct auth_token *at, const char *passwd)
217 char hash[MAX_TEXT_HASH_SIZE];
218 auth_hash(hash, at, passwd);
219 return !strcmp(at->hash, hash);
222 static void auth_create_fake_token(void)
224 struct auth_token *at = xmalloc_zero(sizeof(*at));
225 auth_set_token_passwd(at, "TheKeyIsUnderTheFlyingCarpet");
226 auth_fake_token = at;
229 static void NONRET db_corrupted(const char *reason)
231 die("Database file corrupted: %s", reason);
234 static void db_parse_user(struct json_node *ju)
236 if (ju->type != JSON_OBJECT)
237 db_corrupted("Expected object");
239 const char *login = get_string(ju, "l");
241 db_corrupted("User has no login");
243 struct json_node **jas = get_array(ju, "a");
245 db_corrupted("User has no accounts");
247 struct auth_user *au = auth_find_user(login, 1);
248 if (clist_head(&au->accounts))
249 db_corrupted("Multiple users with the same login");
251 for (uint i=0; i < GARY_SIZE(jas); i++)
253 struct json_node *ja = jas[i];
255 const char *zone_name = get_string(ja, "z");
257 db_corrupted("Account has no zone");
259 struct auth_acct *aa = xmalloc_zero(sizeof(*aa));
260 clist_add_tail(&au->accounts, &aa->n);
262 aa->zone = auth_find_zone(zone_name);
264 die("Database defines accounts in zone %s, which is not configured", zone_name);
265 clist_init(&aa->tokens);
267 struct json_node **jts = get_array(ja, "t");
270 for (uint j=0; j < GARY_SIZE(jts); j++)
272 struct json_node *jt = jts[j];
273 if (jt->type != JSON_OBJECT)
274 db_corrupted("Token is not an object");
277 if (!get_uint(jt, "t", &type) || type >= TOKEN_NUM_TYPES)
278 db_corrupted("Token has no valid type");
280 const char *salt = get_string(jt, "s");
281 const char *hash = get_string(jt, "h");
282 const char *cmt = get_string(jt, "c");
283 const char *ident = get_string(jt, "i");
285 db_corrupted("Token must have salt and hash");
287 struct auth_token *at = auth_create_token(aa);
289 at->salt = xstrdup(salt);
290 at->hash = xstrdup(hash);
291 at->comment = xstrdup(cmt);
292 at->ident = xstrdup(ident);
295 if (!get_uint(jt, "m", &lastmod))
296 db_corrupted("Token has invalid last modified time");
297 at->last_modified = lastmod;
299 if (!get_uint(jt, "n", &at->iterations))
300 db_corrupted("Token has invalid number of hash iterations");
306 static void db_read(void)
308 struct fastbuf *fb = bopen_try(database_name, O_RDONLY, 65536);
311 msg(L_WARN, "No database file found, starting with no users");
315 struct json_context *js = json_new();
316 json_set_input(js, fb);
318 struct json_node *sep = json_next_token(js);
319 if (sep->type != JSON_BEGIN_ARRAY)
320 db_corrupted("Does not start with '['");
326 sep = json_peek_token(js);
327 if (sep->type == JSON_END_ARRAY)
333 else if (sep->type == JSON_VALUE_SEP)
336 struct json_node *ju = json_next_value(js);
341 sep = json_next_token(js);
342 if (sep->type != JSON_EOF)
343 db_corrupted("Garbage at the end");
351 char *temp_name = stk_strcat(database_name, ".new");
352 struct fastbuf *fb = bopen(temp_name, O_WRONLY | O_CREAT | O_TRUNC, 65536);
353 struct json_context *js = json_new();
354 json_set_output(js, fb);
358 HASH_FOR_ALL(user_hash, au)
363 struct json_node *ju = json_new_object(js);
364 json_object_set(ju, "l", json_new_string_ref(js, au->login));
365 struct json_node *jas = json_new_array(js);
366 json_object_set(ju, "a", jas);
367 CLIST_FOR_EACH(struct auth_acct *, aa, au->accounts)
369 struct json_node *ja = json_new_object(js);
370 json_array_append(jas, ja);
371 json_object_set(ja, "z", json_new_string_ref(js, aa->zone->name));
372 struct json_node *jts = json_new_array(js);
373 CLIST_FOR_EACH(struct auth_token *, at, aa->tokens)
375 struct json_node *jt = json_new_object(js);
376 json_array_append(jts, jt);
377 json_object_set(jt, "t", json_new_number(js, at->type));
379 json_object_set(jt, "s", json_new_string_ref(js, at->salt));
381 json_object_set(jt, "h", json_new_string_ref(js, at->hash));
383 json_object_set(jt, "c", json_new_string_ref(js, at->comment));
385 json_object_set(jt, "i", json_new_string_ref(js, at->ident));
386 json_object_set(jt, "n", json_new_number(js, at->iterations));
387 json_object_set(jt, "m", json_new_number(js, at->last_modified));
389 if (GARY_SIZE(jts->elements))
390 json_object_set(ja, "t", jts);
392 json_write_value(js, ju);
402 char *old_name = stk_strcat(database_name, ".old");
403 if (rename(database_name, old_name) < 0 && errno != ENOENT)
404 die("Cannot rename %s to %s: %m", database_name, old_name);
406 if (rename(temp_name, database_name) < 0)
407 die("Cannot rename %s to %s: %m", temp_name, database_name);
412 if (!gcry_check_version(GCRYPT_VERSION))
413 die("libgcrypt version mismatch");
414 gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
415 gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
419 auth_create_fake_token();