+/*
+ * 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);
+ }
+}