passman

dead simple password manager using state of the art encryption
git clone git://kqueue.dev/passman.git
Log | Files | Refs | README | LICENSE

passman.c (10282B)


      1 /*
      2  * Copyright 2022 kqueue <kqueue@cocaine.ninja>
      3 
      4 Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
      5 
      6 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING
      7 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
      8 */
      9 #include <stdio.h>
     10 #include <string.h>
     11 #include <stdlib.h>
     12 #include <sys/types.h>
     13 #include <sys/stat.h>
     14 #include <termios.h>
     15 #include <stdint.h>
     16 #include "third-party/monocypher.h"
     17 #include <dirent.h>
     18 #include "argon2i-settings.h"
     19 #if defined(__linux__)
     20 uint32_t arc4random_uniform(uint32_t upper_bound);
     21 void arc4random_buf(void *buf, size_t nbytes);
     22 #endif
     23 // return value for verify()
     24 struct verifyreturn {
     25     uint8_t key[32];
     26     int matches;
     27 };
     28 struct stat st = { 0 };
     29 
     30 void password_gen(uint8_t * buf, int length)
     31 {
     32     char characters[81] =
     33 	"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~=+%^*()[]{}/!@#$?|";
     34     int random_num;
     35     for (int i = 0; i < length; i++) {
     36 	random_num = arc4random_uniform(81);
     37 	buf[i] = characters[random_num];
     38     }
     39 }
     40 
     41 int getpasswd(char password[64])
     42 {
     43     struct termios oflags, nflags;
     44 
     45     /* disabling echo */
     46     tcgetattr(fileno(stdin), &oflags);
     47     nflags = oflags;
     48     nflags.c_lflag &= ~ECHO;
     49     nflags.c_lflag |= ECHONL;
     50 
     51     if (tcsetattr(fileno(stdin), TCSANOW, &nflags) != 0) {
     52 	perror("tcsetattr");
     53 	return EXIT_FAILURE;
     54     }
     55 
     56     fgets(password, 64, stdin);
     57     password[strlen(password) - 1] = 0;
     58     /* restore terminal */
     59     if (tcsetattr(fileno(stdin), TCSANOW, &oflags) != 0) {
     60 	perror("tcsetattr");
     61 	return EXIT_FAILURE;
     62     }
     63     return 0;
     64 }
     65 
     66 struct verifyreturn verify(char password[64])
     67 {
     68     char passmanpath[500];
     69     sprintf(passmanpath, "%s/.passman-store", getenv("HOME"));
     70     struct verifyreturn returnval;
     71     int length = strlen(password);
     72     uint8_t uintpass[length + 1];
     73 // Converts from char to uint8_t
     74     for (int i = 0; i < length; i++) {
     75 	uintpass[i] = password[i];
     76     }
     77     uint8_t salt[16];
     78     crypto_wipe(password, length);
     79     void *work_area = malloc(nb_blocks * 1024);	/* Work area       */
     80     if (work_area == NULL) {
     81 	/* Handle malloc() failure */
     82 	/* Wipe secrets if they are no longer needed */
     83 	puts("malloc failed");
     84 	crypto_wipe(uintpass, length);
     85     }
     86     char filepath[200];
     87     sprintf(filepath, "%s/passman-hash", passmanpath);
     88     FILE *file = fopen(filepath, "rb");
     89     uint8_t filehash[32];
     90     fread(filehash, 1, 32, file);
     91     fread(salt, 1, 16, file);
     92     fclose(file);
     93     crypto_argon2i(returnval.key, 32, work_area, nb_blocks, nb_iterations,
     94 		   uintpass, length, salt, 16);
     95     free(work_area);
     96     crypto_wipe(uintpass, length);
     97     uint8_t hash[32];
     98     crypto_blake2b_general(hash, 32, NULL, 0, returnval.key, 32);
     99     int matches = crypto_verify32(hash, filehash);
    100     if (matches == 0) {
    101 	returnval.matches = 1;
    102     } else {
    103 	returnval.matches = 0;
    104     }
    105     return returnval;
    106 }
    107 
    108 void init()
    109 {
    110     char passmanpath[200];
    111     sprintf(passmanpath, "%s/.passman-store", getenv("HOME"));
    112     if (stat(passmanpath, &st) == -1) {
    113 	mkdir(passmanpath, 0770);
    114     } else {
    115 	printf
    116 	    ("%s already exists, you may have already initialized passman. If you want to initialize again move ~/.passman-store to a new area or delete the directory all together",
    117 	     passmanpath);
    118 	return;
    119     }
    120     puts("Enter the password that you want you use for the encryption key");
    121     char password[64];
    122     getpasswd(password);
    123     puts("Confirm password");
    124     char confirm_password[64];
    125     getpasswd(confirm_password);
    126     if (strcmp(password, confirm_password) != 0) {
    127 	puts("passwords don't match");
    128 	return;
    129     }
    130     uint8_t key[32];
    131     int length = strlen(password);
    132     uint8_t uintpass[length];
    133 // Converts from char to uint8_t
    134     for (int i = 0; i < length; i++) {
    135 	uintpass[i] = password[i];
    136     }
    137     uint8_t salt[16];
    138     crypto_wipe(password, length);
    139     void *work_area = malloc(nb_blocks * 1024);	/* Work area       */
    140     if (work_area == NULL) {
    141 	/* Handle malloc() failure */
    142 	/* Wipe secrets if they are no longer needed */
    143 	puts("malloc failed");
    144 	crypto_wipe(uintpass, length);
    145     }
    146     arc4random_buf(salt, 16);
    147     crypto_argon2i(key, 32, work_area, nb_blocks, nb_iterations, uintpass,
    148 		   length, salt, 16);
    149     free(work_area);
    150     uint8_t hash[32];
    151     crypto_blake2b_general(hash, 32, NULL, 0, key, 32);
    152     crypto_wipe(key, 32);
    153     char hashpath[300];
    154     sprintf(hashpath, "%s/passman-hash", passmanpath);
    155     FILE *hashfile = fopen(hashpath, "wb");
    156     if (hashfile == NULL) {
    157 	puts("file couldn't be opened");
    158 	return;
    159     }
    160     fwrite(hash, 1, 32, hashfile);
    161     fwrite(salt, 1, 16, hashfile);
    162     crypto_wipe(hash, 32);
    163     fclose(hashfile);
    164 }
    165 
    166 void add(char *path)
    167 {
    168     char password[64];
    169     puts("put your key password");
    170     getpasswd(password);
    171     struct verifyreturn key = verify(password);
    172     if (key.matches == 0) {
    173 	puts("the password you entered does not match with the key");
    174 	return;
    175     }
    176     puts("put the password you'd like to add");
    177     char password2[1000];
    178     getpasswd(password2);
    179     uint8_t nonce[24];
    180     uint8_t mac[16];
    181     int length = strlen(password2);
    182     uint8_t encrypted_password[length];
    183     uint8_t uintpassword[length];
    184     for (int i = 0; i < length; i++) {
    185 	uintpassword[i] = password2[i];
    186     }
    187     crypto_wipe(password2, length);
    188     arc4random_buf(nonce, 24);
    189     crypto_lock(mac, encrypted_password, key.key, nonce, uintpassword,
    190 		length);
    191     crypto_wipe(key.key, 32);
    192     crypto_wipe(uintpassword, length);
    193     char passwordpath[300];
    194     sprintf(passwordpath, "%s/.passman-store/%s", getenv("HOME"), path);
    195     FILE *passwordfile = fopen(passwordpath, "wb");
    196     fwrite(mac, 1, 16, passwordfile);
    197     fwrite(nonce, 1, 24, passwordfile);
    198     fwrite(encrypted_password, 1, length, passwordfile);
    199     fclose(passwordfile);
    200 }
    201 
    202 void generate(char *path, int length)
    203 {
    204     uint8_t generatedpass[length];
    205     password_gen(generatedpass, length);
    206     char password[64];
    207     puts("put your key password");
    208     getpasswd(password);
    209     struct verifyreturn key = verify(password);
    210     if (key.matches == 0) {
    211 	puts("the password you entered does not match with the key");
    212 	return;
    213     }
    214     uint8_t nonce[24];
    215     uint8_t mac[16];
    216     uint8_t encrypted_password[length];
    217     arc4random_buf(nonce, 24);
    218     crypto_lock(mac, encrypted_password, key.key, nonce, generatedpass,
    219 		length);
    220     char passwordpath[300];
    221     sprintf(passwordpath, "%s/.passman-store/%s", getenv("HOME"), path);
    222     FILE *passwordfile = fopen(passwordpath, "wb");
    223     fwrite(mac, 1, 16, passwordfile);
    224     fwrite(nonce, 1, 24, passwordfile);
    225     fwrite(encrypted_password, 1, length, passwordfile);
    226     fclose(passwordfile);
    227 }
    228 
    229 void walk(char *path, int len)
    230 {
    231     struct dirent *de;
    232     DIR *dr = opendir(path);
    233     if (dr == NULL)		// opendir returns NULL if couldn't open directory
    234     {
    235 	printf("Could not open current directory");
    236 	return;
    237     }
    238     while ((de = readdir(dr)) != NULL) {
    239 	if (strcmp(de->d_name, ".") == 0) {
    240 	    /* do nothing */
    241 	} else if (strcmp(de->d_name, "..") == 0) {
    242 	    /* do nothing */
    243 	} else if (strcmp(de->d_name, "passman-hash") == 0) {
    244 	    /* do nothing */
    245 	} else if (de->d_type == DT_DIR) {
    246 	    int length = strlen(path) + strlen(de->d_name) + 3;
    247 	    char dir[length];
    248 	    sprintf(dir, "%s/%s", path, de->d_name);
    249 	    walk(dir, len);
    250 	} else {
    251 	    printf("%s/%s\n", path + len, de->d_name);
    252 	}
    253     }
    254     closedir(dr);
    255     return;
    256 }
    257 
    258 void show(char *path)
    259 {
    260     puts("put your key password");
    261     char password[64];
    262     getpasswd(password);
    263     struct verifyreturn key = verify(password);
    264     if (key.matches == 0) {
    265 	puts("the password you entered does not match with the key");
    266 	return;
    267     }
    268     uint8_t nonce[24];
    269     uint8_t mac[16];
    270     char passwordpath[500];
    271     sprintf(passwordpath, "%s/.passman-store/%s", getenv("HOME"), path);
    272     FILE *passfile = fopen(passwordpath, "rb");
    273     fseek(passfile, 0, SEEK_END);
    274     long filesize = ftell(passfile);
    275     fseek(passfile, 0, SEEK_SET);
    276     fread(mac, 1, 16, passfile);
    277     fread(nonce, 1, 24, passfile);
    278     int length = filesize - 40;
    279     uint8_t uint_password[length];
    280     fread(uint_password, 1, length, passfile);
    281     fclose(passfile);
    282     uint8_t plain_text[length + 1];
    283     if (crypto_unlock
    284 	(plain_text, key.key, nonce, mac, uint_password, length)) {
    285 	printf("message is corrupted\n");
    286 	crypto_wipe(key.key, 32);
    287 	return;
    288     } else {
    289 	for (int i = 0; i < length; i++) {
    290 	    printf("%c", plain_text[i]);
    291 	}
    292 	printf("\n");
    293 	crypto_wipe(plain_text, 12);
    294 	crypto_wipe(key.key, 32);
    295     }
    296 
    297 }
    298 
    299 int main(int argc, char *argv[])
    300 {
    301     if(argv[1] == NULL || strcmp(argv[1], "help") == 0 ){
    302 	printf("Usage:\n\tpassman help\tdisplays help message\n\tpassman generate [name] [length]\tgenerates a password and securely stores it\n\tpassman show [name]\tprints the unencrypted value of a stored password\n\tpassman add\tprompts for a password and securely stores it\n\tpassman list\tlists the passwords that are stored\n");
    303 	return 0;
    304     }
    305     if (strcmp(argv[1], "init") == 0) {
    306 	init();
    307 	return 0;
    308     } else if (strcmp(argv[1], "generate") == 0) {
    309 	if (argc < 4) {
    310 	    puts("not enough arguments passed for generate");
    311 	    return 1;
    312 	}
    313 	generate(argv[2], atoi(argv[3]));
    314 	return 0;
    315     } else if (strcmp(argv[1], "show") == 0) {
    316 	if (argc < 3) {
    317 	    puts("not enough arguments passed for show");
    318 	    return 1;
    319 	}
    320 	show(argv[2]);
    321 	return 0;
    322     } else if (strcmp(argv[1], "add") == 0
    323 	       || strcmp(argv[1], "insert") == 0) {
    324 	if (argc < 3) {
    325 	    puts("not enough arguments passed for add/insert");
    326 	    return 1;
    327 	}
    328 	add(argv[2]);
    329 	return 0;
    330     } else if (strcmp(argv[1], "list") == 0) {
    331 	char path[500];
    332 	sprintf(path, "%s/.passman-store/", getenv("HOME"));
    333 	walk(path, strlen(path)+1);
    334     } else {
    335 	puts("command not found");
    336 	return 0;
    337     }
    338 }