linkd

Control plane daemon for unos
git clone git://git.finwo.net/app/linkd
Log | Files | Refs | README

auth.c (5460B)


      1 #define _GNU_SOURCE
      2 #include <stdio.h>
      3 #include <stdlib.h>
      4 #include <string.h>
      5 #include <strings.h>
      6 #include <sys/stat.h>
      7 #include <unistd.h>
      8 
      9 #include "finwo/pbkdf2.h"
     10 #include "rxi/log.h"
     11 
     12 #include "util/auth.h"
     13 
     14 #define HASH_LEN 32
     15 #define SALT_LEN 16
     16 
     17 static const char AB64[] =
     18   "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
     19 
     20 int ab64_encode(const unsigned char *in, size_t len, char *out, size_t out_sz) {
     21   size_t need = (len * 8 + 5) / 6;
     22   if (out_sz < need + 1) return -1;
     23 
     24   size_t o = 0;
     25   for (size_t i = 0; i < len; i += 3) {
     26     unsigned v = (unsigned)in[i] << 16;
     27     if (i + 1 < len) v |= (unsigned)in[i + 1] << 8;
     28     if (i + 2 < len) v |= (unsigned)in[i + 2];
     29 
     30     out[o++] = AB64[(v >> 18) & 0x3f];
     31     out[o++] = AB64[(v >> 12) & 0x3f];
     32     if (i + 1 < len) out[o++] = AB64[(v >> 6) & 0x3f];
     33     if (i + 2 < len) out[o++] = AB64[v & 0x3f];
     34   }
     35   out[o] = '\0';
     36   return 0;
     37 }
     38 
     39 static int ab64_val(char c) {
     40   const char *p = strchr(AB64, c);
     41   return (p && c) ? (int)(p - AB64) : -1;
     42 }
     43 
     44 size_t ab64_decode(const char *in, unsigned char *out, size_t out_sz) {
     45   size_t len = strlen(in);
     46   size_t o   = 0;
     47 
     48   for (size_t i = 0; i < len; i += 4) {
     49     int c0 = ab64_val(in[i]);
     50     int c1 = (i + 1 < len) ? ab64_val(in[i + 1]) : -1;
     51     int c2 = (i + 2 < len) ? ab64_val(in[i + 2]) : -1;
     52     int c3 = (i + 3 < len) ? ab64_val(in[i + 3]) : -1;
     53     if (c0 < 0 || c1 < 0) break;
     54 
     55     unsigned v = ((unsigned)c0 << 18) | ((unsigned)c1 << 12);
     56     if (c2 >= 0) v |= (unsigned)c2 << 6;
     57     if (c3 >= 0) v |= (unsigned)c3;
     58 
     59     if (o < out_sz) out[o++] = (unsigned char)((v >> 16) & 0xff);
     60     if (c2 >= 0 && o < out_sz) out[o++] = (unsigned char)((v >> 8) & 0xff);
     61     if (c3 >= 0 && o < out_sz) out[o++] = (unsigned char)(v & 0xff);
     62   }
     63   return o;
     64 }
     65 
     66 // Comparison time must not depend on how much of the hash matched.
     67 static int ct_equal(const unsigned char *a, const unsigned char *b, size_t n) {
     68   unsigned char diff = 0;
     69   for (size_t i = 0; i < n; i++) diff |= (unsigned char)(a[i] ^ b[i]);
     70   return diff == 0;
     71 }
     72 
     73 int auth_verify_hash(const char *stored, const char *pass) {
     74   if (!stored || !pass) return 0;
     75 
     76   const char *prefix = "$pbkdf2-sha256$";
     77   if (strncmp(stored, prefix, strlen(prefix)) != 0) return 0;
     78 
     79   char *copy = strdup(stored + strlen(prefix));
     80   if (!copy) return 0;
     81 
     82   char *iter_s = copy;
     83   char *salt_s = strchr(iter_s, '$');
     84   if (!salt_s) { free(copy); return 0; }
     85   *salt_s++ = '\0';
     86   char *hash_s = strchr(salt_s, '$');
     87   if (!hash_s) { free(copy); return 0; }
     88   *hash_s++ = '\0';
     89 
     90   char *end = NULL;
     91   unsigned long iterations = strtoul(iter_s, &end, 10);
     92   if (!end || *end || iterations == 0) { free(copy); return 0; }
     93 
     94   unsigned char salt[64], want[HASH_LEN], got[HASH_LEN];
     95   size_t salt_len = ab64_decode(salt_s, salt, sizeof(salt));
     96   size_t want_len = ab64_decode(hash_s, want, sizeof(want));
     97   if (salt_len == 0 || want_len != HASH_LEN) { free(copy); return 0; }
     98 
     99   pbkdf2((const uint8_t *)pass, strlen(pass), salt, salt_len,
    100          iterations, PBKDF2_SHA256, got, HASH_LEN);
    101 
    102   int ok = ct_equal(got, want, HASH_LEN);
    103   free(copy);
    104   return ok;
    105 }
    106 
    107 char *auth_make_hash(const char *pass, unsigned iterations) {
    108   if (!pass) return NULL;
    109   if (!iterations) iterations = AUTH_DEFAULT_ITERATIONS;
    110 
    111   unsigned char salt[SALT_LEN];
    112   FILE *rnd = fopen("/dev/urandom", "rb");
    113   if (!rnd) return NULL;
    114   size_t got = fread(salt, 1, sizeof(salt), rnd);
    115   fclose(rnd);
    116   if (got != sizeof(salt)) return NULL;
    117 
    118   unsigned char hash[HASH_LEN];
    119   pbkdf2((const uint8_t *)pass, strlen(pass), salt, sizeof(salt),
    120          iterations, PBKDF2_SHA256, hash, sizeof(hash));
    121 
    122   char salt_b[64], hash_b[64];
    123   if (ab64_encode(salt, sizeof(salt), salt_b, sizeof(salt_b)) != 0) return NULL;
    124   if (ab64_encode(hash, sizeof(hash), hash_b, sizeof(hash_b)) != 0) return NULL;
    125 
    126   char *out = NULL;
    127   if (asprintf(&out, "$pbkdf2-sha256$%u$%s$%s", iterations, salt_b, hash_b) < 0) {
    128     return NULL;
    129   }
    130   return out;
    131 }
    132 
    133 int auth_check_permissions(const char *path) {
    134   struct stat st;
    135   if (stat(path, &st) != 0) return -1;
    136 
    137   if (st.st_mode & S_IWOTH) {
    138     log_error("auth: %s is world-writable; refusing to use it", path);
    139     return -1;
    140   }
    141   if (st.st_mode & (S_IRGRP | S_IROTH)) {
    142     log_warn("auth: %s is readable beyond its owner", path);
    143   }
    144   return 0;
    145 }
    146 
    147 static int role_from_string(const char *s) {
    148   if (!s || !*s)                return AUTH_ROLE_READONLY;
    149   if (!strcasecmp(s, "full"))   return AUTH_ROLE_FULL;
    150   if (!strcasecmp(s, "readonly")) return AUTH_ROLE_READONLY;
    151   log_warn("auth: unknown role `%s', treating as readonly", s);
    152   return AUTH_ROLE_READONLY;
    153 }
    154 
    155 int auth_check(const char *path, const char *user, const char *pass) {
    156   if (!path || !user || !pass) return AUTH_ROLE_NONE;
    157   if (auth_check_permissions(path) != 0) return AUTH_ROLE_NONE;
    158 
    159   FILE *fd = fopen(path, "r");
    160   if (!fd) {
    161     log_error("auth: cannot open %s", path);
    162     return AUTH_ROLE_NONE;
    163   }
    164 
    165   char line[1024];
    166   int  role = AUTH_ROLE_NONE;
    167 
    168   while (fgets(line, sizeof(line), fd)) {
    169     char *nl = strpbrk(line, "\r\n");
    170     if (nl) *nl = '\0';
    171     if (!*line || *line == '#') continue;
    172 
    173     char *hash = strchr(line, ':');
    174     if (!hash) continue;
    175     *hash++ = '\0';
    176     if (strcmp(line, user) != 0) continue;
    177 
    178     char *role_s = strchr(hash, ':');
    179     if (role_s) *role_s++ = '\0';
    180 
    181     if (auth_verify_hash(hash, pass)) role = role_from_string(role_s);
    182     break;
    183   }
    184 
    185   fclose(fd);
    186   return role;
    187 }