Source of “pachyhash.c”.
285 lines, 8 KBytes.   Last modified 9:55 pm, 5th September 2016 PDT.
1 // Emacs settings: -*- mode: Fundamental; tab-width: 4; -*- 2 3 //////////////////////////////////////////////////////////////////////////// 4 // // 5 // A setuid program to manage the Pachylet H1 hash files // 6 // // 7 // Andrew Birrell, 2014 // 8 // // 9 // See https://birrell.org/pachylet/help.html#securityDetails // 10 // // 11 // Requires macro "pwdDir" with the pathname for the hash directory. // 12 // // 13 //////////////////////////////////////////////////////////////////////////// 14 15 16 #include <sys/types.h> 17 #include <sys/stat.h> 18 #include <unistd.h> 19 #include <stdlib.h> 20 #include <string.h> 21 #include <stdio.h> 22 #include <openssl/bio.h> 23 #include <openssl/evp.h> 24 #include <openssl/hmac.h> 25 26 #define dkMaxLen 90 27 // Enough for 64 binary bytes, base-64 encoded, with newline and null 28 #define hashSalt "pachyH1" 29 // The salt signed by the HMAC that creates the H1 hash 30 #define prefix "/h1-" 31 // Hash filename prefix between pwdDir and the user name 32 33 34 // 35 // Cleaning up the environment, because we don't trust our caller 36 // 37 38 extern char **environ; 39 extern int unsetenv(const char *name); 40 41 static void eraseEnv() { 42 // Remove all environment variables 43 // 44 while (*environ != NULL) { 45 char *nv = *environ; 46 char *value = strchr(nv, '='); 47 int nameLen = strlen(nv) - (value ? strlen(value) : 0); 48 char *name = malloc(nameLen + 1); 49 memcpy(name, nv, nameLen); 50 name[nameLen] = 0; 51 unsetenv(name); 52 free(name); 53 } 54 } 55 56 57 // 58 // Base-64, implemented via openssl's BIO library 59 // 60 61 unsigned char *base64_encode(const unsigned char *decoded, int decodedLen) { 62 // Base-64 encode binary buffer "decoded" and return a malloc'ed string. 63 // Note: openssl includes a final newline, which we strip. 64 // 65 BIO *b64 = BIO_new(BIO_f_base64()); 66 BIO *bMem = BIO_new(BIO_s_mem()); 67 BIO *bio = BIO_push(b64, bMem); 68 BIO_write(bio, decoded, decodedLen); 69 BIO_flush(bio); 70 int encodedLen = BIO_pending(bMem); 71 unsigned char *encoded = malloc(encodedLen + 1); 72 encodedLen = BIO_read(bMem, encoded, encodedLen); 73 BIO_free_all(bio); 74 if (encodedLen > 0 && encoded[encodedLen-1] == '\n') encodedLen--; 75 encoded[encodedLen] = 0; 76 return encoded; 77 } 78 79 unsigned char *base64_decode(const unsigned char *encoded, int *decodedLen) { 80 // Decode the base-64 string "encoded" and return a malloc'ed buffer. 81 // The length of the decoded data is assigned to *decodedLen. 82 // On error, return NULL with an error code in *decodedLen. 83 // 84 // There is a null after the decoded data, though in general there 85 // might be other nulls within the decoded data. 86 // 87 int encodedLen = strlen(encoded); 88 BIO *b64 = BIO_new(BIO_f_base64()); 89 BIO *bMem = BIO_new(BIO_s_mem()); 90 BIO *bio = BIO_push(b64, bMem); 91 BIO_puts(bMem, encoded); 92 // It won't work unless we reinstate the final newline 93 if (encodedLen == 0 || encoded[encodedLen-1] != '\n') { 94 BIO_puts(bMem, "\n"); 95 } 96 unsigned char *decoded = malloc(encodedLen + 1); 97 *decodedLen = BIO_read(bio, decoded, encodedLen); 98 BIO_free_all(bio); 99 if (*decodedLen < 0) return NULL; 100 decoded[*decodedLen] = 0; 101 return decoded; 102 } 103 104 int testB64() { 105 // Basic test that base-64 encoding and decoding work. 106 // Returns 1 on success, or 0 on failure 107 // 108 unsigned char *test = "hello world"; 109 unsigned char *encoded = base64_encode(test, strlen(test)); 110 printf("Encoded: %s\n", encoded); 111 int decodedLen; 112 unsigned char *decoded = base64_decode(encoded, &decodedLen); 113 printf("Decoded: %s (%d)\n", decoded, decodedLen); 114 if (strcmp(test, decoded) != 0) { 115 printf("Incorrect\n"); 116 return 0; 117 } 118 return 1; 119 } 120 121 122 // 123 // Support functions for the H1 hash files 124 // 125 126 static unsigned char *getDerivedH1(const char *dk) { 127 // Return the base-64 encoded H1 hash for the given base-64 encoded 128 // derived key, as a string. On error, return NULL. 129 // 130 int decodedLen; 131 unsigned char *decoded = base64_decode(dk, &decodedLen); 132 if (!decoded) return NULL; 133 int hmacLen; 134 unsigned char *hmac = HMAC(EVP_sha256(), 135 decoded, decodedLen, 136 hashSalt, strlen(hashSalt), 137 NULL, &hmacLen); 138 if (!hmac) return NULL; 139 return base64_encode(hmac, hmacLen); 140 } 141 142 static char *getH1FileName(const char *user) { 143 // Return malloc'ed string with the file name of the H1 hash file. 144 // Returns NULL for illegal user names. 145 // 146 if (strchr(user, '/') != NULL) return NULL; 147 char *fname = malloc(strlen(pwdDir) + strlen(prefix) + strlen(user) + 1) ; 148 fname[0] = 0; 149 strncat(fname, pwdDir, strlen(pwdDir)); 150 strncat(fname, prefix, strlen(prefix)); 151 strncat(fname, user, strlen(user)); 152 return fname; 153 } 154 155 static int verifyH1File(const char *fname, const unsigned char *h1) { 156 // Read the first line of the H1 hash file, and compare to h1. 157 // Returns 1 if they match, or 0 on error or mismatch. Uses fixed path 158 // length for the string comparison. 159 // 160 FILE *h1File = fopen(fname, "r"); 161 if (!h1File) return 0; 162 unsigned char buf[dkMaxLen]; 163 char *received = fgets(buf, dkMaxLen, h1File); 164 fclose(h1File); 165 int hashLen = strlen(received); 166 if (!received || hashLen == 0) return 0; 167 if (hashLen != strlen(h1)) return 0; 168 int mismatch = 0; 169 int i; 170 for (i = 0; i < hashLen; i++) mismatch |= (received[i] ^ h1[i]); 171 return (mismatch == 0); 172 } 173 174 static int writeH1File(const char *fname, const unsigned char *h1) { 175 // Write the given base-64 encoded H1 hash into the H1 hash file. 176 // Returns 1 on success, 0 on error. 177 // 178 umask(S_IRWXG | S_IRWXO); // no access to group or others 179 FILE *h1File = fopen(fname, "w"); 180 if (!h1File) return 0; 181 int rc = fputs(h1, h1File); 182 fclose(h1File); 183 return (rc == EOF ? 0 : 1); 184 } 185 186 static int deleteH1File(const char *fname) { 187 // Delete the H1 hash file. 188 // Return 1 on success, 0 on failure 189 // 190 int rc = unlink(fname); 191 return (rc == 0 ? 1 : 0); 192 } 193 194 195 // 196 // Main program 197 // 198 199 int main(int argc, char *argv[]) { 200 // Perform the requested operation and deliver a status code. 201 // 202 // status 0: success 203 // status 1: invalid request 204 // status 2: not allowed (includes non-match in "verify") 205 // status 3: file system update failed or internal error 206 // 207 // For non-zero status, also prints a message on stdout. 208 209 eraseEnv(); 210 211 if (argc == 2 && strcmp(argv[1], "b64") == 0) { 212 if (!testB64()) return 3; 213 return 0; 214 } 215 216 char dk[dkMaxLen]; 217 if (argc != 3) { 218 printf("Usage: %s (create|verify|set|delete) user\n", argv[0]); 219 return 1; 220 } else if (!fgets(dk, dkMaxLen, stdin) || strlen(dk) == 0) { 221 printf("Missing DK on stdin\n"); 222 return 1; 223 } else { 224 uid_t ruid = getuid(); 225 uid_t euid = geteuid(); 226 char *verb = argv[1]; 227 char *user = argv[2]; 228 char *fname = getH1FileName(user); // doesn't get freed 229 unsigned char *h1 = getDerivedH1(dk); // doesn't get freed 230 if (!fname) { 231 printf("Invalid user name\n"); 232 return 1; 233 } else if (!h1) { 234 printf("Invalid DK\n"); 235 return 1; 236 } else if (strcmp(verb, "create") == 0) { 237 if (ruid != euid && ruid != 0) { 238 printf( 239 "\"Create\" is allowed only when logged in as pachylet or root\n"); 240 return 2; 241 } else if (access(fname, F_OK) == 0) { 242 printf("Hash file already exists\n"); 243 return 2; 244 } else if (!writeH1File(fname, h1)) { 245 printf("Write failed, consult expert\n"); 246 return 3; 247 } 248 } else if (strcmp(verb, "verify") == 0) { 249 if (!verifyH1File(fname, h1)) { 250 printf("Incorrect DK or user name\n"); 251 return 2; 252 } 253 } else if (!verifyH1File(fname, h1) && ruid != 0) { 254 printf("Incorrect DK or user name\n"); 255 return 2; 256 } else if (strcmp(verb, "set") == 0) { 257 char newDk[dkMaxLen]; 258 if (!fgets(newDk, dkMaxLen, stdin) || strlen(newDk) == 0) { 259 printf("Missing new DK on stdin\n"); 260 return 1; 261 } else { 262 unsigned char *newH1 = getDerivedH1(newDk); 263 if (!newH1) { 264 printf("Invalid new DK\n"); 265 return 1; 266 } else if (!writeH1File(fname, newH1)) { 267 printf("Write failed, consult expert\n"); 268 return 3; 269 } 270 } 271 } else if (ruid == 0 && access(fname, F_OK) != 0) { 272 printf("Incorrect user name\n"); 273 return 2; 274 } else if (strcmp(verb, "delete") == 0) { 275 if (!deleteH1File(fname)) { 276 printf("Delete failed, consult expert\n"); 277 return 3; 278 } 279 } else { 280 printf("Unknown verb %s\n", verb); 281 return 1; 282 } 283 } 284 return 0; 285 }
End of listing