From 97ffe4248840c44548d7fc43756b1629c2e936a8 Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Wed, 19 Jul 2017 17:21:00 +0200 Subject: [PATCH] Real cryptography --- configure | 1 + server/Makefile | 2 + server/auth.c | 148 ++++++++++++++++++++++++++++++++++++++++++++-- server/cmd.c | 146 +++++++++++++++++++++++++++++++++++++++++---- server/subauthd.c | 14 +++-- server/subauthd.h | 16 +++++ 6 files changed, 303 insertions(+), 24 deletions(-) diff --git a/configure b/configure index 1b22783..15aa37e 100755 --- a/configure +++ b/configure @@ -40,6 +40,7 @@ require UCW::Configure::Pkg; # Get some libraries UCW::Configure::Pkg::PkgConfig("libucw") or Fail("libucw is required"); UCW::Configure::Pkg::PkgConfig("libucw-json") or Fail("libucw-json is required"); +UCW::Configure::Pkg::TrivConfig("libgcrypt", script => "libgcrypt-config", minversion => '1.6') or Fail("libgcrypt is required"); Finish(); Log "\nConfigured, run `make' to build everything.\n"; diff --git a/server/Makefile b/server/Makefile index a447a0c..fef0b41 100644 --- a/server/Makefile +++ b/server/Makefile @@ -4,3 +4,5 @@ PROGS+=$(o)/server/subauthd CONFIGS+=subauthd $(o)/server/subauthd: $(addprefix $(o)/server/, subauthd.o cmd.o auth.o) +$(o)/server/subauthd: LIBS+=$(LIBGCRYPT_LIBS) +$(o)/server/auth.o: CFLAGS+=$(LIBGCRYPT_CFLAGS) diff --git a/server/auth.c b/server/auth.c index 0401898..bafcd52 100644 --- a/server/auth.c +++ b/server/auth.c @@ -5,16 +5,22 @@ */ #include +#include #include #include +#include +#include #include #include +#include #include #include #include "subauthd.h" +struct auth_token *auth_fake_token; + #define HASH_NODE struct auth_user #define HASH_PREFIX(x) user_hash_##x #define HASH_KEY_ENDSTRING login @@ -63,6 +69,22 @@ struct auth_acct *auth_find_acct(struct auth_user *au, struct auth_zone *az, boo return aa; } +struct auth_token *auth_find_token_passwd(struct auth_acct *aa) +{ + CLIST_FOR_EACH(struct auth_token *, at, aa->tokens) + if (at->type == TOKEN_PASSWORD) + return at; + return NULL; +} + +struct auth_token *auth_find_token_generated(struct auth_acct *aa, char *ident) +{ + CLIST_FOR_EACH(struct auth_token *, at, aa->tokens) + if (at->type == TOKEN_GENERATED && !strcmp(at->ident, ident)) + return at; + return NULL; +} + void auth_delete_user(struct auth_user *au) { struct auth_acct *aa; @@ -97,11 +119,111 @@ struct auth_token *auth_create_token(struct auth_acct *aa) return at; } +static void auth_hash(char *hash_text, struct auth_token *at, const char *passwd) +{ + // This is PBKDF2 based on SHA-256-HMAC + + gcry_md_hd_t md; + if (gcry_md_open(&md, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC) != GPG_ERR_NO_ERROR) + die("gcry_md_open(CGRY_MD_SHA256) failed"); + if (gcry_md_setkey(md, passwd, strlen(passwd)) != GPG_ERR_NO_ERROR) + die("gcry_md_setkey() failed"); + + byte current_hash[HASH_BYTES]; + gcry_md_write(md, at->salt, strlen(at->salt)); + byte zeroes[4] = { 0, 0, 0, 0 }; + gcry_md_write(md, zeroes, 4); + memcpy(current_hash, gcry_md_read(md, 0), HASH_BYTES); + + byte accumulator[HASH_BYTES]; + memcpy(accumulator, current_hash, HASH_BYTES); + + for (uint i=1; i < at->iterations; i++) + { + gcry_md_reset(md); + gcry_md_write(md, current_hash, HASH_BYTES); + memcpy(current_hash, gcry_md_read(md, 0), HASH_BYTES); + for (uint j=0; jsalt = xstrdup(salt_text); + at->iterations = DEFAULT_HASH_ITERATIONS; +} + void auth_set_token_passwd(struct auth_token *at, const char *passwd) { at->type = TOKEN_PASSWORD; - at->salt = xstrdup("???"); - at->hash = xstrdup("???"); + set_salt(at); + + char hash[MAX_TEXT_HASH_SIZE]; + auth_hash(hash, at, passwd); + at->hash = xstrdup(hash); +} + +char *auth_set_token_generated(struct auth_token *at, const char *comment) +{ + char ident_text[2*DEFAULT_IDENT_BYTES + 1]; + do + { + byte ident_bytes[DEFAULT_IDENT_BYTES]; + gcry_randomize(ident_bytes, DEFAULT_IDENT_BYTES, GCRY_STRONG_RANDOM); + mem_to_hex(ident_text, ident_bytes, DEFAULT_IDENT_BYTES, 0); + } + while (auth_find_token_generated(at->acct, ident_text)); + + at->type = TOKEN_GENERATED; + at->ident = xstrdup(ident_text); + at->comment = xstrdup(comment); + set_salt(at); + + byte token_bytes[DEFAULT_GENERATED_BYTES]; + gcry_randomize(token_bytes, DEFAULT_GENERATED_BYTES, GCRY_STRONG_RANDOM); + + uint token_len = 2*DEFAULT_IDENT_BYTES + 1 + 2*DEFAULT_GENERATED_BYTES + 1; + char *token_text = xmalloc(token_len); + memcpy(token_text, ident_text, 2*DEFAULT_IDENT_BYTES); + char *token_rhs = token_text + 2*DEFAULT_IDENT_BYTES; + *token_rhs++ = '-'; + uint out_len = base64_encode(token_rhs, token_bytes, DEFAULT_GENERATED_BYTES); + if (out_len > 0 && token_rhs[out_len-1] == '=') + out_len--; + token_rhs[out_len] = 0; + + char hash[MAX_TEXT_HASH_SIZE]; + auth_hash(hash, at, token_rhs); + at->hash = xstrdup(hash); + + return token_text; +} + +bool auth_check_token(struct auth_token *at, const char *passwd) +{ + char hash[MAX_TEXT_HASH_SIZE]; + auth_hash(hash, at, passwd); + return !strcmp(at->hash, hash); +} + +static void auth_create_fake_token(void) +{ + struct auth_token *at = xmalloc_zero(sizeof(*at)); + auth_set_token_passwd(at, "TheKeyIsUnderTheFlyingCarpet"); + auth_fake_token = at; } static void NONRET db_corrupted(const char *reason) @@ -158,19 +280,24 @@ static void db_parse_user(struct json_node *ju) const char *salt = get_string(jt, "s"); const char *hash = get_string(jt, "h"); const char *cmt = get_string(jt, "c"); + const char *ident = get_string(jt, "i"); if (!salt || !hash) db_corrupted("Token must have salt and hash"); - uint lastmod; - if (!get_uint(jt, "m", &lastmod)) - db_corrupted("Token has invalid last modified time"); - struct auth_token *at = auth_create_token(aa); at->type = type; at->salt = xstrdup(salt); at->hash = xstrdup(hash); at->comment = xstrdup(cmt); + at->ident = xstrdup(ident); + + uint lastmod; + if (!get_uint(jt, "m", &lastmod)) + db_corrupted("Token has invalid last modified time"); at->last_modified = lastmod; + + if (!get_uint(jt, "n", &at->iterations)) + db_corrupted("Token has invalid number of hash iterations"); } } } @@ -254,6 +381,9 @@ void db_write(void) json_object_set(jt, "h", json_new_string_ref(js, at->hash)); if (at->comment) json_object_set(jt, "c", json_new_string_ref(js, at->comment)); + if (at->ident) + json_object_set(jt, "i", json_new_string_ref(js, at->ident)); + json_object_set(jt, "n", json_new_number(js, at->iterations)); json_object_set(jt, "m", json_new_number(js, at->last_modified)); } if (GARY_SIZE(jts->elements)) @@ -279,6 +409,12 @@ void db_write(void) void auth_init(void) { + if (!gcry_check_version(GCRYPT_VERSION)) + die("libgcrypt version mismatch"); + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); + user_hash_init(); db_read(); + auth_create_fake_token(); } diff --git a/server/cmd.c b/server/cmd.c index 8eba618..3eb6bc0 100644 --- a/server/cmd.c +++ b/server/cmd.c @@ -211,30 +211,150 @@ static void cmd_set_passwd(struct client *c) if (!aa->zone->allow_passwd) cmd_error(c, "This zone does not allow authentication by password"); + if (strchr(passwd, '-')) + cmd_error(c, "The minus sign is forbidden in passwords"); + msg(L_INFO, "Set password: login=<%s> zone=<%s>", aa->user->login, aa->zone->name); - CLIST_FOR_EACH(struct auth_token *, at, aa->tokens) - if (at->type == TOKEN_PASSWORD) - { - auth_delete_token(at); - break; - } + struct auth_token *at_old = auth_find_token_passwd(aa); + if (at_old) + auth_delete_token(at_old); struct auth_token *at = auth_create_token(aa); auth_set_token_passwd(at, passwd); - // FIXME - db_write(); cmd_ok(c); } -static void cmd_verify(struct client *c) +static void cmd_create_token(struct client *c) { struct auth_acct *aa = cmd_need_target_acct(c); - const char *passwd = cmd_need_string(c, "passwd"); - // FIXME + if (!aa->zone->allow_tokens) + cmd_error(c, "This zone does not allow authentication by tokens"); + + if (clist_size(&aa->tokens) >= aa->zone->allow_tokens) + cmd_error(c, "Maximum number of tokens was reached"); + + struct auth_token *at = auth_create_token(aa); + char *tok = auth_set_token_generated(at, get_string(c->request, "comment")); + json_object_set(c->reply, "token", json_new_string(c->json, tok)); + xfree(tok); + + msg(L_INFO, "Created token: login=<%s> zone=<%s> id=<%s>", aa->user->login, aa->zone->name, at->ident); + + db_write(); + cmd_ok(c); +} + +static void cmd_login_fake(struct client *c, const char *passwd) +{ + auth_check_token(auth_fake_token, passwd); + cmd_error(c, "Invalid password"); +} + +static void cmd_login(struct client *c) +{ + struct auth_zone *az = cmd_need_zone(c); + const char *given_passwd = cmd_need_string(c, "passwd"); + + // Split password string to token ident and the password proper + char passbuf[strlen(given_passwd) + 1]; + strcpy(passbuf, given_passwd); + char *ident, *passwd; + char *sep = strchr(passbuf, '-'); + if (sep) + { + *sep = 0; + ident = passbuf; + passwd = sep + 1; + } + else + { + ident = NULL; + passwd = passbuf; + } + + const char *login = cmd_need_string(c, "login"); + struct auth_user *au = auth_find_user(login, 0); + if (!au) + { + msg(L_INFO, "Login failed: No user=<%s>", login); + return cmd_login_fake(c, passwd); + } + + struct auth_acct *aa = auth_find_acct(au, az, 0); + if (!aa) + { + msg(L_INFO, "Login failed: No account user=<%s> zone=<%s>", login, az->name); + return cmd_login_fake(c, passwd); + } + + struct auth_token *at; + if (ident) + at = auth_find_token_generated(aa, ident); + else + at = auth_find_token_passwd(aa); + if (!at) + { + msg(L_INFO, "Login failed: No token user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : "")); + return cmd_login_fake(c, passwd); + } + + if (!auth_check_token(at, passwd)) + { + msg(L_INFO, "Login failed: Wrong password for user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : "")); + cmd_error(c, "Invalid password"); + } + + msg(L_INFO, "Login successful: user=<%s> zone=<%s> ident=<%s>", login, az->name, (ident ? : "")); + cmd_ok(c); +} + +static void cmd_list(struct client *c) +{ + const char *login = cmd_need_target_login(c); + + struct auth_user *au = auth_find_user(login, 0); + if (!au) + cmd_error(c, "No such user"); + + struct json_context *js = c->json; + json_object_set(c->reply, "login", json_new_string(js, au->login)); + struct json_node *jas = json_new_array(js); + json_object_set(c->reply, "accounts", jas); + + CLIST_FOR_EACH(struct auth_acct *, aa, au->accounts) + { + struct json_node *ja = json_new_object(js); + json_array_append(jas, ja); + json_object_set(ja, "zone", json_new_string(js, aa->zone->name)); + + struct json_node *jts = json_new_array(js); + json_object_set(ja, "tokens", jts); + + CLIST_FOR_EACH(struct auth_token *, at, aa->tokens) + { + struct json_node *jt = json_new_object(js); + json_array_append(jts, jt); + + const char *type; + switch (at->type) + { + case TOKEN_PASSWORD: type = "passwd"; break; + case TOKEN_GENERATED: type = "token"; break; + default: type = "unknown"; break; + } + json_object_set(jt, "type", json_new_string(js, type)); + + if (at->ident) + json_object_set(jt, "ident", json_new_string(js, at->ident)); + if (at->comment) + json_object_set(jt, "comment", json_new_string(js, at->comment)); + json_object_set(jt, "lastmod", json_new_number(js, at->last_modified)); + } + } cmd_ok(c); } @@ -248,8 +368,10 @@ static const struct command command_table[] = { { "nop", cmd_nop }, { "create-acct", cmd_create_acct }, { "delete-acct", cmd_delete_acct }, + { "create-token", cmd_create_token }, { "set-passwd", cmd_set_passwd }, - { "verify", cmd_verify }, + { "login", cmd_login }, + { "list", cmd_list }, }; void cmd_dispatch(struct client *c) diff --git a/server/subauthd.c b/server/subauthd.c index 30c25a2..c8bff03 100644 --- a/server/subauthd.c +++ b/server/subauthd.c @@ -4,6 +4,8 @@ * (c) 2017 Martin Mares */ +#undef LOCAL_DEBUG + #include #include #include @@ -35,7 +37,7 @@ static int socket_write_handler(struct main_file *fi); static void client_close(struct client *c) { - msg(L_INFO, "Closing connection"); + DBG("Closing connection"); file_del(&c->socket); timer_del(&c->timer); close(c->socket.fd); @@ -69,12 +71,12 @@ static void try_send_reply(struct client *c) TRANS_END; int len = fbbuf_count_written(&fb); - msg(L_INFO, "Sending reply of %d bytes", len); + DBG("Sending reply of %d bytes", len); if (send(c->socket.fd, packet_buffer, len, 0) < 0) { if (errno == EAGAIN || errno == EINTR) { - msg(L_INFO, "Postponed send"); + DBG("Postponed send"); c->socket.write_handler = socket_write_handler; file_chg(&c->socket); } @@ -83,7 +85,7 @@ static void try_send_reply(struct client *c) } else { - msg(L_INFO, "Reply sent"); + DBG("Reply sent"); c->socket.read_handler = socket_read_handler; c->socket.write_handler = NULL; file_chg(&c->socket); @@ -188,7 +190,7 @@ static int socket_read_handler(struct main_file *fi) return HOOK_RETRY; } - msg(L_INFO, "Got message from UID %d", (int) cred->uid); + DBG("Got message from UID %d", (int) cred->uid); c->uid = cred->uid; fi->read_handler = NULL; @@ -219,7 +221,7 @@ static int listen_read_handler(struct main_file *fi) } num_connections++; - msg(L_INFO, "Accepted connection"); + DBG("Accepted connection"); if (fcntl(new_sk, F_SETFL, fcntl(new_sk, F_GETFL) | O_NONBLOCK) < 0) die("Cannot set O_NONBLOCK: %m"); diff --git a/server/subauthd.h b/server/subauthd.h index d1a1cf7..30fd016 100644 --- a/server/subauthd.h +++ b/server/subauthd.h @@ -39,6 +39,13 @@ struct json_node *get_object(struct json_node *n, const char *key); /* auth.c */ +#define DEFAULT_SALT_BYTES 8 +#define DEFAULT_IDENT_BYTES 2 +#define DEFAULT_GENERATED_BYTES 8 +#define HASH_BYTES 32 // We are using SHA-256 +#define DEFAULT_HASH_ITERATIONS 64 // Number of hash function iterations per PBKDF2 +#define MAX_TEXT_HASH_SIZE 256 + struct auth_zone { cnode n; char *name; @@ -60,6 +67,7 @@ struct auth_acct { }; enum token_type { + TOKEN_UNDEFINED, TOKEN_PASSWORD, TOKEN_GENERATED, TOKEN_NUM_TYPES, @@ -71,8 +79,10 @@ struct auth_token { enum token_type type; char *salt; char *hash; + char *ident; char *comment; time_t last_modified; + uint iterations; }; void auth_init(void); @@ -80,8 +90,14 @@ void db_write(void); struct auth_zone *auth_find_zone(const char *name); struct auth_user *auth_find_user(const char *login, bool create); struct auth_acct *auth_find_acct(struct auth_user *au, struct auth_zone *az, bool create); +struct auth_token *auth_find_token_passwd(struct auth_acct *aa); +struct auth_token *auth_find_token_generated(struct auth_acct *aa, char *ident); void auth_delete_user(struct auth_user *au); void auth_delete_acct(struct auth_acct *aa); void auth_delete_token(struct auth_token *at); struct auth_token *auth_create_token(struct auth_acct *aa); void auth_set_token_passwd(struct auth_token *at, const char *passwd); +char *auth_set_token_generated(struct auth_token *at, const char *comment); +bool auth_check_token(struct auth_token *at, const char *passwd); + +extern struct auth_token *auth_fake_token; -- 2.39.5