]> mj.ucw.cz Git - subauth.git/commitdiff
Real cryptography
authorMartin Mares <mj@ucw.cz>
Wed, 19 Jul 2017 15:21:00 +0000 (17:21 +0200)
committerMartin Mares <mj@ucw.cz>
Wed, 19 Jul 2017 15:21:00 +0000 (17:21 +0200)
configure
server/Makefile
server/auth.c
server/cmd.c
server/subauthd.c
server/subauthd.h

index 1b227830c96032fa1f075c4c92f8326e9f935fbd..15aa37eed96d318dc744e664f6fb28d605c92c97 100755 (executable)
--- 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";
index a447a0c2b6f43e7fefc9f6aa55313286ab0a0517..fef0b415f2d6d6f4195b1406079903c076494333 100644 (file)
@@ -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)
index 040189820abcf2b21251893804d7af96bf115913..bafcd525a5626869d22ebb29bb7e17107aa1b6ca 100644 (file)
@@ -5,16 +5,22 @@
  */
 
 #include <ucw/lib.h>
+#include <ucw/base64.h>
 #include <ucw/gary.h>
 #include <ucw/stkstring.h>
+#include <ucw/string.h>
+#include <ucw/unaligned.h>
 
 #include <errno.h>
 #include <fcntl.h>
+#include <gcrypt.h>
 #include <time.h>
 #include <unistd.h>
 
 #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; j<HASH_BYTES; j++)
+       accumulator[j] ^= current_hash[j];
+    }
+
+  gcry_md_close(md);
+
+  uint out_len = base64_encode(hash_text, accumulator, HASH_BYTES);
+  hash_text[out_len] = 0;
+}
+
+static void set_salt(struct auth_token *at)
+{
+  byte salt_bytes[DEFAULT_SALT_BYTES];
+  gcry_randomize(salt_bytes, DEFAULT_SALT_BYTES, GCRY_STRONG_RANDOM);
+
+  char salt_text[2*DEFAULT_SALT_BYTES + 1];
+  uint salt_len = base64_encode(salt_text, salt_bytes, DEFAULT_SALT_BYTES);
+  salt_text[salt_len] = 0;
+
+  at->salt = 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();
 }
index 8eba618a0b548f9100e4584d85c1fab5c6f4608c..3eb6bc02e79a3127850d1af1392e1b5c203a8f5a 100644 (file)
@@ -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)
index 30c25a27848fcafc89b434312463d9c04517b3ce..c8bff033dbf20d6a1a9a4566f7618b7c69b3f7b8 100644 (file)
@@ -4,6 +4,8 @@
  *     (c) 2017 Martin Mares <mj@ucw.cz>
  */
 
+#undef LOCAL_DEBUG
+
 #include <ucw/lib.h>
 #include <ucw/conf.h>
 #include <ucw/log.h>
@@ -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");
index d1a1cf7409e5c16514a4a4403ff6e0bbba6fb089..30fd01657d8f44fcba2e4be4a83f371621e2926b 100644 (file)
@@ -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;