gPass

gPass Git Source Tree

Root/cli/main.c

1/*
2 Copyright (C) 2013-2017 Grégory Soutadé
3
4 This file is part of gPass.
5
6 gPass is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 gPass is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with gPass. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include <stdio.h>
21#include <unistd.h>
22#include <stdlib.h>
23#include <string.h>
24
25#include <curl/curl.h>
26#include <openssl/opensslv.h>
27#include <openssl/evp.h>
28
29#include "ini.h"
30
31#define STRNCMP(a, b) strncmp(a, b, sizeof(b)-1)
32
33#define DEFAULT_CONFIG_FILE ".local/share/gpass/gpass.ini"
34
35#define DEFAULT_PBKDF2_LEVEL 1000
36#define MASTER_KEY_LENGTH (256/8)
37#define GLOBAL_IV_LENGTH 16
38#define BLOCK_SIZE (128/8)
39#define DEFAULT_SERVER_PORT 443
40#define SERVER_PROTOCOL 4
41#define RESPONSE_SIZE 2048
42#define MAX_SUBDOMAINS 10
43
44struct gpass_parameters {
45 unsigned pbkdf2_level;
46 char *server;
47 char *salt;
48 char *domain;
49 char *username;
50 char *orig_master_key;
51 unsigned char *derived_master_key;
52 unsigned server_port;
53 unsigned verbose;
54 char *ca_path;
55 unsigned verify_ssl_peer;
56 unsigned port_set;
57 unsigned crypto_v1_compatible;
58 unsigned char *global_iv;
59} ;
60
61#if OPENSSL_VERSION_NUMBER >= 0x10010000
62// OpenSSL >= 1.1
63static EVP_MD_CTX * s_md_ctx;
64#else
65static EVP_MD_CTX * s_md_ctx;
66static EVP_MD_CTX ss_md_ctx;
67#define EVP_MD_CTX_new(...) &ss_md_ctx
68#define EVP_MD_CTX_free(...)
69#endif
70static const EVP_MD * s_md_256;
71
72static EVP_CIPHER_CTX * s_cipher_ctx;
73
74static int digest(unsigned char** out, unsigned char* in, unsigned size)
75{
76 *out = NULL;
77 EVP_DigestInit(s_md_ctx, s_md_256);
78 EVP_DigestUpdate(s_md_ctx, in, size);
79 *out = malloc(32);
80 return EVP_DigestFinal(s_md_ctx, *out, NULL);
81}
82
83static void derive_master_key(struct gpass_parameters* params)
84{
85 if (!params->derived_master_key)
86 params->derived_master_key = malloc(MASTER_KEY_LENGTH);
87
88 if (!params->global_iv)
89 params->global_iv = malloc(GLOBAL_IV_LENGTH);
90
91 PKCS5_PBKDF2_HMAC(params->orig_master_key, strlen(params->orig_master_key),
92 (unsigned char*)params->salt, strlen(params->salt),
93 params->pbkdf2_level, EVP_sha256(),
94 MASTER_KEY_LENGTH, params->derived_master_key);
95
96 PKCS5_PBKDF2_HMAC(params->salt, strlen(params->salt),
97 (unsigned char*)params->orig_master_key, strlen(params->orig_master_key),
98 params->pbkdf2_level, EVP_sha256(),
99 GLOBAL_IV_LENGTH, params->global_iv);
100}
101
102static void bin_to_hex(unsigned char* bin, unsigned char* hex, unsigned bin_size)
103{
104 unsigned char tmp;
105
106 for (; bin_size--; bin++)
107 {
108 tmp = (*bin >> 4) & 0xf;
109 if (tmp <= 9)
110 *hex++ = '0' + tmp;
111 else
112 *hex++ = 'a' + (tmp-10);
113
114 tmp = *bin & 0xf;
115 if (tmp <= 9)
116 *hex++ = '0' + tmp;
117 else
118 *hex++ = 'a' + (tmp-10);
119 }
120}
121
122static void hex_to_bin(unsigned char* bin, unsigned char* hex, long hex_size)
123{
124 unsigned char tmp;
125
126 // Round to 2
127 hex_size &= ~1;
128
129 for (; hex_size; hex_size-=2, bin++)
130 {
131 tmp = *hex++;
132 if (tmp >= '0' && tmp <= '9')
133 *bin = (tmp - '0') << 4;
134 else if (tmp >= 'a' && tmp <= 'f')
135 *bin = ((tmp - 'a')+10) << 4;
136 else
137 *bin = ((tmp - 'A')+10) << 4;
138
139 tmp = *hex++;
140 if (tmp >= '0' && tmp <= '9')
141 *bin |= (tmp - '0');
142 else if (tmp >= 'a' && tmp <= 'f')
143 *bin |= ((tmp - 'a')+10);
144 else
145 *bin |= ((tmp - 'A')+10);
146 }
147}
148
149static void encrypt_domain_v1(struct gpass_parameters* params, char* domain,
150 unsigned char** res, unsigned* out_size)
151{
152 unsigned size = 2+strlen(domain)+1+strlen(params->username);
153 unsigned char* buffer, *tmp;
154
155 if (params->verbose)
156 printf("%s: %s\n", __func__, domain);
157
158 if ((size % BLOCK_SIZE))
159 size = ((size/BLOCK_SIZE)+1)*BLOCK_SIZE;
160
161 buffer = malloc(size+1); // Cause snprintf() add a final \0
162 memset(buffer, 0, size+1);
163
164 snprintf((char*)buffer, size+1, "@@%s;%s", domain, params->username);
165
166 tmp = malloc(size);
167 *res = malloc(size*2);
168
169 EVP_EncryptInit(s_cipher_ctx, EVP_aes_256_ecb(), params->derived_master_key, NULL);
170 EVP_CipherUpdate(s_cipher_ctx, tmp, (int*)out_size, buffer, size);
171
172 bin_to_hex(tmp, *res, size);
173
174 *out_size *= 2;
175
176 free(buffer);
177 free(tmp);
178}
179
180static void encrypt_domain(struct gpass_parameters* params, char* domain,
181 unsigned char** res, unsigned* out_size)
182{
183 unsigned size = strlen(domain)+1+strlen(params->username);
184 unsigned padded_size;
185 unsigned char* buffer, *tmp;
186
187 if (params->verbose)
188 printf("%s: %s\n", __func__, domain);
189
190 if ((size % BLOCK_SIZE))
191 size = ((size/BLOCK_SIZE)+1)*BLOCK_SIZE;
192 padded_size = size;
193
194 size += 16; // For digest
195
196 buffer = malloc(size);
197 memset(buffer, 0, size);
198
199 snprintf((char*)buffer, size, "%s;%s", domain, params->username);
200
201 // Append digest
202 digest(&tmp, buffer, padded_size);
203 memcpy(&buffer[padded_size], &tmp[8], 16);
204 free(tmp);
205
206 tmp = malloc(size);
207 *res = malloc(size*2);
208
209 EVP_EncryptInit(s_cipher_ctx, EVP_aes_256_cbc(), params->derived_master_key, params->global_iv);
210 EVP_CipherUpdate(s_cipher_ctx, tmp, (int*)out_size, buffer, size);
211
212 bin_to_hex(tmp, *res, size);
213
214 *out_size *= 2;
215
216 free(buffer);
217 free(tmp);
218}
219
220static void append_to_request(char** request, char* new_req, unsigned new_req_size)
221{
222 static int cur_req_idx = 0;
223 int size_added;
224
225 if (!cur_req_idx)
226 {
227 *request = malloc(3+new_req_size+1);
228 snprintf(*request, 3+new_req_size+1, "k0=%s", new_req);
229 }
230 else
231 {
232 size_added = 4+new_req_size;
233 if (cur_req_idx >= 10)
234 size_added++;
235
236 *request = realloc(*request, strlen(*request)+1+size_added);
237
238 snprintf(&((*request)[strlen(*request)]), size_added+1, "&k%d=%s",
239 cur_req_idx, new_req);
240 }
241
242 cur_req_idx++;
243}
244
245static char* wildcard_domain(char* domain)
246{
247 int cur_level = 1;
248 char* level_ptr[MAX_SUBDOMAINS], *tmp, *res = NULL;
249 int level_length[MAX_SUBDOMAINS];
250
251 memset(level_ptr, 0, sizeof(level_ptr));
252 memset(level_length, 0, sizeof(level_length));
253 level_ptr[0] = domain;
254
255 for (tmp=domain; *tmp && cur_level < MAX_SUBDOMAINS; tmp++)
256 {
257 if (*tmp == '.')
258 {
259 level_ptr[cur_level] = tmp+1;
260 level_length[cur_level-1] = tmp - level_ptr[cur_level-1];
261 cur_level++;
262 }
263 }
264
265 // Too much levels
266 if (cur_level >= MAX_SUBDOMAINS)
267 {
268 fprintf(stderr, "Error: Too much levels for domain %s\n", domain);
269 return NULL;
270 }
271
272 // Final level
273 level_length[cur_level] = tmp - level_ptr[cur_level-1];
274
275 tmp = NULL;
276 if (cur_level >= 3)
277 {
278 // Seems to be a two level root domain (ie zzz.xxx.co.uk ...)
279 if (level_length[cur_level-2] <= 3)
280 {
281 if (cur_level > 3)
282 tmp = level_ptr[cur_level-3];
283 }
284 else
285 // Standard root domain (zzz.xxx.com)
286 tmp = level_ptr[cur_level-2];
287 }
288 // Simple xxx.com
289 else if (cur_level == 2)
290 tmp = level_ptr[0];
291
292 if (tmp)
293 {
294 res = malloc(2+strlen(tmp)+1);
295 sprintf(res, "*.%s", tmp);
296 }
297
298 return res;
299}
300
301static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
302{
303 if ((size*nmemb) > RESPONSE_SIZE)
304 {
305 fprintf(stderr, "Error curl response is too big (%d bytes, max %d bytes)\n",
306 (int)(size*nmemb), RESPONSE_SIZE);
307 }
308 else
309 memcpy(userdata, ptr, size*nmemb);
310
311 return size*nmemb;
312}
313
314static int ask_server(struct gpass_parameters* params)
315{
316 char* wc_domain, *saveptr, *token, *cur_ptr;
317 unsigned char* enc_domain;
318 unsigned enc_size, matched_key = 0, crypto_v1_index = 1;
319 char* request = NULL;
320 int ret = -1, res, len;
321 CURL *curl;
322 char response[RESPONSE_SIZE];
323 unsigned char password[256];
324
325 if (params->verbose)
326 printf("Username: %s\n", params->username);
327
328 encrypt_domain(params, params->domain, &enc_domain, &enc_size);
329 append_to_request(&request, (char*)enc_domain, enc_size);
330 free(enc_domain);
331
332 wc_domain = wildcard_domain(params->domain);
333 if (wc_domain)
334 {
335 crypto_v1_index++;
336 encrypt_domain(params, wc_domain, &enc_domain, &enc_size);
337 append_to_request(&request, (char*)enc_domain, enc_size);
338 free(enc_domain);
339 }
340
341 if (params->crypto_v1_compatible)
342 {
343 encrypt_domain_v1(params, params->domain, &enc_domain, &enc_size);
344 append_to_request(&request, (char*)enc_domain, enc_size);
345 free(enc_domain);
346 if (wc_domain)
347 {
348 encrypt_domain_v1(params, wc_domain, &enc_domain, &enc_size);
349 append_to_request(&request, (char*)enc_domain, enc_size);
350 free(enc_domain);
351 }
352
353 }
354
355 if (params->verbose)
356 printf("Request: %s\n", request);
357
358 curl = curl_easy_init();
359 curl_easy_setopt(curl, CURLOPT_URL, params->server);
360 curl_easy_setopt(curl, CURLOPT_PORT, params->server_port);
361 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, params->verify_ssl_peer);
362 if (params->ca_path)
363 curl_easy_setopt(curl, CURLOPT_CAINFO, params->ca_path);
364 curl_easy_setopt(curl, CURLOPT_POST, 1);
365 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
366 if (params->verbose)
367 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
368 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
369 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)response);
370
371 res = curl_easy_perform(curl);
372 curl_easy_cleanup(curl);
373
374 if (res != CURLE_OK)
375 {
376 fprintf(stderr, "curl_easy_perform() failed: %s\n",
377 curl_easy_strerror(res));
378 goto end;
379 }
380
381 token = strtok_r(response, "\n", &saveptr);
382
383 while (token)
384 {
385 if (params->verbose)
386 printf("Parse %s\n", token);
387 cur_ptr = token;
388 if (!strcmp(token, "<end>"))
389 break;
390 else if (!STRNCMP(token, "protocol"))
391 {
392 cur_ptr += sizeof("protocol"); // includes "="
393 if (STRNCMP(cur_ptr, "gpass-"))
394 {
395 fprintf(stderr, "Error: Unknown server protocol %s\n", token);
396 break;
397 }
398 else
399 {
400 cur_ptr += sizeof("gpass-")-1;
401 if (atoi(cur_ptr) > SERVER_PROTOCOL)
402 {
403 fprintf(stderr, "Error: Cannot handle server protocol %s\n", token);
404 break;
405 }
406 }
407 }
408 else if (!STRNCMP(token, "pass"))
409 {
410 cur_ptr += sizeof("pass"); // includes "="
411
412 if ((strlen(cur_ptr)/2) > sizeof(password))
413 {
414 fprintf(stderr, "Error: retrieved password is too big !\n");
415 goto end;
416 }
417
418 hex_to_bin(password, (unsigned char*)cur_ptr, strlen(cur_ptr));
419
420 if (matched_key >= crypto_v1_index)
421 {
422 // Crypto v1
423 EVP_DecryptInit(s_cipher_ctx, EVP_aes_256_ecb(), params->derived_master_key, NULL);
424 EVP_CipherUpdate(s_cipher_ctx, password, &res, password, strlen(cur_ptr)/2);
425 // Remove salt
426 password[strlen((char*)password)-3] = 0;
427 }
428 else
429 {
430 EVP_DecryptInit(s_cipher_ctx, EVP_aes_256_cbc(), params->derived_master_key, params->global_iv);
431 EVP_CipherUpdate(s_cipher_ctx, password, &res, password, strlen(cur_ptr)/2);
432 // Remove salt
433 len = strlen((char*)password);
434 memmove(password, &password[3], len-3);
435 password[len-3] = 0;
436 }
437 printf("Password found: %s\n", password);
438 ret = 0;
439 goto end;
440 }
441 else if (!STRNCMP(token, "pbkdf2_level"))
442 {
443 cur_ptr += sizeof("pbkdf2_level"); // includes "="
444
445 if (atoi(cur_ptr) != params->pbkdf2_level)
446 {
447 params->pbkdf2_level = atoi(cur_ptr);
448 ret = 1;
449 break;
450 }
451 }
452 else if (!STRNCMP(token, "matched_key"))
453 {
454 cur_ptr += sizeof("matched_key"); // includes "="
455
456 matched_key = atoi(cur_ptr);
457 }
458 else
459 {
460 fprintf(stderr, "Error: Unknown server response %s\n", token);
461 break;
462 }
463 token = strtok_r(NULL, "\n", &saveptr);
464 }
465
466 if (ret)
467 printf("Password not found\n");
468
469end:
470 free(request);
471
472 return ret;
473}
474
475static void init_parameters(struct gpass_parameters* params)
476{
477 memset (params, 0, sizeof(*params));
478 params->pbkdf2_level = DEFAULT_PBKDF2_LEVEL;
479 params->server_port = DEFAULT_SERVER_PORT;
480 params->verify_ssl_peer = 1;
481 params->crypto_v1_compatible = 1; // For now, in the next version it must a command line parameter
482}
483
484static void release_parameters(struct gpass_parameters* params)
485{
486 if (params->server) free(params->server);
487 if (params->salt) free(params->salt);
488 if (params->domain) free(params->domain);
489 if (params->username) free(params->username);
490 if (params->orig_master_key) free(params->orig_master_key);
491 if (params->derived_master_key) free(params->derived_master_key);
492 if( params->ca_path) free(params->ca_path);
493 if (params->global_iv) free(params->global_iv);
494}
495
496static int check_parameters(struct gpass_parameters* params)
497{
498 if (!params->server)
499 {
500 fprintf(stderr, "Error: server not set\n");
501 return 1;
502 }
503
504 if (!params->domain)
505 {
506 fprintf(stderr, "Error: gpass domain not set\n");
507 return 1;
508 }
509
510 if (!params->username)
511 {
512 fprintf(stderr, "Error: username not set\n");
513 return 1;
514 }
515
516 return 0;
517}
518
519static int gpass_ini_handler(void* user, const char* section,
520 const char* name, const char* value)
521{
522 struct gpass_parameters* params = (struct gpass_parameters*) user;
523
524 if (!STRNCMP(name, "ca_path"))
525 {
526 if (params->ca_path) free(params->ca_path);
527 params->ca_path = strdup(value);
528 }
529 else if (!STRNCMP(name, "pbkdf2_level"))
530 params->pbkdf2_level = atoi(value);
531 else if (!STRNCMP(name, "verify_ssl_peer"))
532 params->verify_ssl_peer = atoi(value);
533 else if (!STRNCMP(name, "server_port"))
534 {
535 params->server_port = atoi(value);
536 params->port_set = 1;
537 }
538 else if (!STRNCMP(name, "server"))
539 {
540 if (params->server) free(params->server);
541 params->server = strdup(value);
542 }
543 else
544 fprintf(stderr, "Error: Unknown key '%s' in config file\n", name);
545
546 return 1;
547}
548
549static void usage(char* program_name)
550{
551 fprintf(stderr, "Usage: %s [-f config_file] [-p server_port] [-c CA_certificate_path] [-l PBKDF2_level] [-s gpass_server] [-v] -d domain -u username\n",
552 program_name);
553 exit(EXIT_FAILURE);
554}
555
556int main(int argc, char** argv)
557{
558 struct gpass_parameters params;
559 int opt, ret = 0;
560 char* tmp;
561 char* config_file, *home;
562
563 if (argc == 1)
564 usage(argv[0]);
565
566 init_parameters(&params);
567
568 home = getenv("HOME");
569 if (home)
570 {
571 config_file = malloc(strlen(home)+1+sizeof(DEFAULT_CONFIG_FILE));
572 sprintf(config_file, "%s/" DEFAULT_CONFIG_FILE, home);
573
574 ini_parse(config_file, gpass_ini_handler, &params);
575
576 free(config_file);
577 }
578
579 while ((opt = getopt(argc, argv, "c:d:f:l:np:s:u:vh")) != -1) {
580 switch (opt) {
581 case 'c':
582 if (params.ca_path) free(params.ca_path);
583 params.ca_path = strdup(optarg);
584 break;
585 case 'd':
586 if (params.domain) free(params.domain);
587 params.domain = strdup(optarg);
588 break;
589 case 'f':
590 ini_parse(optarg, gpass_ini_handler, &params);
591 break;
592 case 'l':
593 params.pbkdf2_level = atoi(optarg);
594 break;
595 case 'n':
596 params.verify_ssl_peer = 0;
597 break;
598 case 'p':
599 params.server_port = atoi(optarg);
600 params.port_set = 1;
601 break;
602 case 's':
603 if (params.server) free(params.server);
604 params.server = strdup(optarg);
605 break;
606 case 'u':
607 if (params.username) free(params.username);
608 params.username = strdup(optarg);
609 break;
610 case 'v':
611 params.verbose++;
612 break;
613 case 'h':
614 case '?':
615 default: /* '?' */
616 usage(argv[0]);
617 }
618 }
619
620 ret = check_parameters(&params);
621
622 if (ret)
623 goto end;
624
625 // Manage server, server_port and salt
626 if (!STRNCMP(params.server, "http://"))
627 {
628 if (!params.port_set)
629 params.server_port = 80;
630 params.salt = strdup(&params.server[7]);
631 }
632 else if (!STRNCMP(params.server, "https://"))
633 {
634 if (!params.port_set)
635 params.server_port = 443;
636 params.salt = strdup(&params.server[8]);
637 }
638
639 // Manage domain
640 if (!STRNCMP(params.domain, "http://"))
641 {
642 tmp = strdup(&params.domain[7]);
643 free(params.domain);
644 params.domain = tmp;
645 }
646 else if (!STRNCMP(params.domain, "https://"))
647 {
648 tmp = strdup(&params.domain[8]);
649 free(params.domain);
650 params.domain = tmp;
651 }
652
653 // Remove query part of domain (a.com[/XXXX])
654 for (tmp=params.domain; *tmp; tmp++)
655 {
656 if (*tmp == '/')
657 {
658 *tmp = 0;
659 break;
660 }
661 }
662
663 s_md_ctx = EVP_MD_CTX_new();
664 s_md_256 = EVP_sha256();
665 EVP_DigestInit(s_md_ctx, s_md_256);
666
667 s_cipher_ctx = EVP_CIPHER_CTX_new();
668
669 // Let's go
670 tmp = getpass("Enter master key: ");
671
672 if (!tmp)
673 goto end;
674
675 params.orig_master_key = strdup(tmp);
676 derive_master_key(&params);
677
678 ret = ask_server(&params);
679
680 // try again with new parameters
681 if (ret > 0)
682 {
683 derive_master_key(&params);
684 ask_server(&params);
685 }
686
687end:
688 release_parameters(&params);
689
690 if (s_md_ctx) EVP_MD_CTX_free(s_md_ctx);
691 if (s_cipher_ctx) EVP_CIPHER_CTX_free(s_cipher_ctx);
692
693 return ret;
694}

Archive Download this file