SocketPath @INSTALL_RUN_DIR@/subauthd.socket
MaxConnections 1000
Database @INSTALL_STATE_DIR@/subauthd.db
+ TempKeyFile @INSTALL_STATE_DIR@/subauthd-seed
Zone {
Name mail
AutoCreateAcct 1
AllowPasswd 1
AllowTokens 16
+ MaxTempValidity 20
}
Zone {
PROGS+=$(o)/server/subauthd
CONFIGS+=subauthd
-$(o)/server/subauthd: $(addprefix $(o)/server/, subauthd.o cmd.o auth.o)
+$(o)/server/subauthd: $(addprefix $(o)/server/, subauthd.o cmd.o auth.o temp.o)
$(o)/server/subauthd: LIBS+=$(LIBGCRYPT_LIBS)
$(o)/server/auth.o: CFLAGS+=$(LIBGCRYPT_CFLAGS)
{
if (!gcry_check_version(GCRYPT_VERSION))
die("libgcrypt version mismatch");
- gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
- gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+ gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
+ gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
user_hash_init();
db_read();
cmd_ok(c);
}
+static void cmd_create_temp(struct client *c)
+{
+ struct auth_acct *aa = cmd_need_target_acct(c);
+
+ uint validity;
+ if (get_uint(c->request, "validity", &validity))
+ cmd_error(c, "Validity must be given");
+
+ if (!aa->zone->max_temp_validity)
+ cmd_error(c, "This zone does not allow temporary tokens");
+
+ if (validity > aa->zone->max_temp_validity)
+ cmd_error(c, "This zone limits temporary token validity to %d seconds", aa->zone->max_temp_validity);
+
+ char *tok = temp_generate(aa->zone->name, aa->user->login, validity);
+ set_string(c, c->reply, "token", tok);
+ xfree(tok);
+
+ msg(L_INFO, "Created temp token: login=<%s> zone=<%s> validity=%u", aa->user->login, aa->zone->name, validity);
+
+ 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_by_temp(struct client *c, struct auth_zone *az, const char *given_passwd)
+{
+ const char *login = cmd_need_string(c, "login");
+
+ const char *reason = temp_check(az->name, login, given_passwd);
+ if (reason)
+ {
+ msg(L_INFO, "Login failed: %s user=<%s> zone=<%s> type=<temp>", reason, login, az->name);
+ goto reject;
+ }
+
+ /*
+ * The following checks test for improbable things like user
+ * disappearing since the token has been issued.
+ */
+
+ if (!az->max_temp_validity)
+ {
+ msg(L_INFO, "Login failed: Temporary tokens no longer accepted for zone=<%s>", az->name);
+ goto reject;
+ }
+
+ struct auth_user *au = auth_find_user(login, 0);
+ if (!au)
+ {
+ msg(L_INFO, "Login failed: No user=<%s> type=<temp>", login);
+ goto reject;
+ }
+
+ struct auth_acct *aa = auth_find_acct(au, az, 0);
+ if (!aa)
+ {
+ msg(L_INFO, "Login failed: No account user=<%s> zone=<%s> type=<temp>", login, az->name);
+ goto reject;
+ }
+
+ msg(L_INFO, "Login successful: user=<%s> zone=<%s> type=<temp>", login, az->name);
+ cmd_ok(c);
+
+reject:
+ cmd_error(c, "Temporary token refused");
+}
+
static void cmd_login(struct client *c)
{
struct auth_zone *az = cmd_need_zone(c);
passwd = passbuf;
}
+ if (ident && !strcmp(ident, "t"))
+ return cmd_login_by_temp(c, az, given_passwd);
+
const char *login = cmd_need_string(c, "login");
struct auth_user *au = auth_find_user(login, 0);
if (!au)
{ "delete-token", cmd_delete_token },
{ "set-passwd", cmd_set_passwd },
{ "delete-passwd", cmd_delete_passwd },
+ { "create-temp", cmd_create_temp },
{ "login", cmd_login },
{ "list-accts", cmd_list_accts },
{ "list-zones", cmd_list_zones },
static uint max_connections = ~0U;
clist zone_list;
char *database_name = "subauthd.db";
+char *temp_key_file;
static struct main_file listen_socket;
static uint num_connections;
CF_UINT("AutoCreateAcct", PTR_TO(struct auth_zone, auto_create_acct)),
CF_UINT("AllowPasswd", PTR_TO(struct auth_zone, allow_passwd)),
CF_UINT("AllowTokens", PTR_TO(struct auth_zone, allow_tokens)),
+ CF_UINT("MaxTempValidity", PTR_TO(struct auth_zone, max_temp_validity)),
CF_END
}
};
CF_UINT("MaxConnections", &max_connections),
CF_LIST("Zone", &zone_list, &zone_config),
CF_STRING("Database", &database_name),
+ CF_STRING("TempKeyFile", &temp_key_file),
CF_END
}
};
opt_parse(&options, argv+1);
auth_init();
+ temp_init();
main_init();
init_socket();
extern clist zone_list; // of struct auth_zone
extern char *database_name;
+extern char *temp_key_file;
/* cmd.c */
uint auto_create_acct;
uint allow_passwd;
uint allow_tokens;
+ uint max_temp_validity;
};
struct auth_user {
bool auth_check_token(struct auth_token *at, const char *passwd);
extern struct auth_token *auth_fake_token;
+
+/* temp.c */
+
+void temp_init(void);
+char *temp_generate(const char *zone, const char *login, uint validity);
+const char *temp_check(const char *zone, const char *login, const char *token);
--- /dev/null
+/*
+ * Sub-authentication Daemon: Temporary Tokens
+ *
+ * (c) 2017 Martin Mares <mj@ucw.cz>
+ */
+
+// FIXME
+#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"
+
+// FIXME: Re-defined to handle const strings properly
+#undef stk_strmulticat
+#define stk_strmulticat(s...) ({ const char *_s[]={s}; char *_x=alloca(stk_array_len((char **)_s, ARRAY_SIZE(_s)-1)); stk_array_join(_x, (char **)_s, ARRAY_SIZE(_s)-1, 0); _x; })
+
+#define TEMP_KEY_SIZE 32
+#define TEMP_NONCE_SIZE 16
+#define TEMP_SIGN_SIZE 16 // SHA2-256 HMAC
+
+static byte temp_key[TEMP_KEY_SIZE];
+
+static void temp_sign(byte *sig_text, char *raw_token)
+{
+ // Sign the nonce with the temp key using SHA2-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, temp_key, TEMP_KEY_SIZE) != GPG_ERR_NO_ERROR)
+ die("gcry_md_setkey() failed");
+
+ gcry_md_write(md, raw_token, strlen(raw_token));
+
+ uint sig_len = base64_encode(sig_text, gcry_md_read(md, 0), TEMP_SIGN_SIZE);
+ sig_text[sig_len] = 0;
+
+ gcry_md_close(md);
+}
+
+char *temp_generate(const char *zone, const char *login, uint validity)
+{
+ byte nonce[TEMP_NONCE_SIZE];
+ gcry_randomize(nonce, TEMP_NONCE_SIZE, GCRY_STRONG_RANDOM);
+
+ char nonce_text[2*TEMP_NONCE_SIZE + 1];
+ uint nonce_len = base64_encode(nonce_text, nonce, TEMP_NONCE_SIZE);
+ nonce_text[nonce_len] = 0;
+
+ unsigned long long expire = time(NULL) + validity;
+ char expire_text[32];
+ int expire_len = snprintf(expire_text, sizeof(expire_text), "%lld", expire);
+ ASSERT(expire_len < (int) sizeof(expire_text));
+
+ char *raw_token = stk_strmulticat((char *) zone, "-", login, "-", nonce_text, "-", expire_text, NULL);
+
+ char sig_text[2*TEMP_SIGN_SIZE + 1];
+ temp_sign(sig_text, raw_token);
+
+ return xstrdup(stk_strmulticat("t-", nonce_text, "-", expire_text, "-", sig_text, NULL));
+}
+
+const char *temp_check(const char *zone, const char *login, const char *token)
+{
+ // Split and check format
+ // FIXME: Refuse tokens which are too long
+ char *fields[4];
+ if (str_sepsplit(stk_strdup(token), '-', fields, 4) != 4)
+ return "Wrong format";
+ if (!strcmp(fields[0], "t"))
+ return "Wrong format";
+
+ // Check signature
+ char *raw_token = stk_strmulticat(zone, "-", login, "-", fields[1], "-", fields[2], NULL);
+ char sig_text[2*TEMP_SIGN_SIZE + 1];
+ temp_sign(sig_text, raw_token);
+ if (strcmp(sig_text, fields[3]))
+ return "Wrong signature";
+
+ // Check expiration
+ unsigned long long expire;
+ if (sscanf(fields[2], "%lld", &expire) != 1)
+ return "Wrong format";
+ if (expire < (unsigned long long) time(NULL))
+ return "Expired";
+
+ // OK
+ return NULL;
+}
+
+void temp_init(void)
+{
+ if (temp_key_file)
+ {
+ int fd = open(temp_key_file, O_RDONLY);
+ if (fd >= 0)
+ {
+ int n = read(fd, temp_key, TEMP_KEY_SIZE);
+ if (n != TEMP_KEY_SIZE)
+ die("Cannot read temporary token key from %s", temp_key_file);
+ close(fd);
+ msg(L_INFO, "Loaded temporary token key from %s", temp_key_file);
+ return;
+ }
+ }
+
+ gcry_randomize(temp_key_file, TEMP_KEY_SIZE, GCRY_STRONG_RANDOM);
+ msg(L_INFO, "Generated new temporary token key");
+
+ if (temp_key_file)
+ {
+ int fd = open(temp_key_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (fd < 0)
+ die("Cannot create %s: %m", temp_key_file);
+ int n = write(fd, temp_key, TEMP_KEY_SIZE);
+ if (n != TEMP_KEY_SIZE)
+ die("Cannot write temporary token key");
+ close(fd);
+ }
+}