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 }