You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
557 lines
12 KiB
557 lines
12 KiB
/* (C) folkert van heusden |
|
* released under AGPL v3.0 |
|
*/ |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <unistd.h> |
|
#include <time.h> |
|
#include <string.h> |
|
#include <signal.h> |
|
#include <errno.h> |
|
#include <ctype.h> |
|
#include <gpgme.h> |
|
|
|
#define MODE_RANDOM 1 |
|
#define MODE_INCREMENTAL 2 |
|
#define MODE_FILE 3 |
|
|
|
#define MAX_PP_LEN 128 |
|
|
|
unsigned char passphrase[MAX_PP_LEN + 2]; |
|
unsigned char passphrase_template[MAX_PP_LEN + 2]; |
|
int min_length = 1; |
|
int max_length = MAX_PP_LEN; |
|
|
|
char *pp_file_in = NULL; |
|
FILE *pp_file_in_fh = NULL; |
|
long pp_file_in_offset = 0; /* this kludge is to make sure we have the file- |
|
* offset *before* the current passphrase as we |
|
* might get the signal while the current one |
|
* still is being processed |
|
*/ |
|
char *pp_file_out = NULL; |
|
|
|
unsigned char charset[256]; |
|
int charset_n = 1; |
|
|
|
int mode = MODE_INCREMENTAL; |
|
/* int mode = MODE_RANDOM; */ |
|
|
|
long long int n_done = 0; |
|
|
|
void error_exit(char *str, gpgme_error_t err) |
|
{ |
|
fprintf(stderr, "%s: %s\n", str, gpgme_strerror(err)); |
|
|
|
exit(1); |
|
} |
|
|
|
char valtohexdigit(int n) |
|
{ |
|
if (n > 9) |
|
return 'A' + (n - 10); |
|
|
|
return '0' + n; |
|
} |
|
|
|
int hexdigittoval(char hex) |
|
{ |
|
hex = toupper(hex); |
|
|
|
if (hex >= 'A') |
|
return hex - 'A' + 10; |
|
|
|
return hex - '0'; |
|
} |
|
|
|
int cvt_from_hex_string(char *in, char *out) |
|
{ |
|
int loop, len = strlen(in), index = 0; |
|
|
|
for(loop=0; loop<len; loop+=2) |
|
{ |
|
out[index++] = (hexdigittoval(in[loop]) << 4) + hexdigittoval(in[loop + 1]); |
|
} |
|
|
|
return index; |
|
} |
|
|
|
void sighandler(int sig) |
|
{ |
|
FILE *fh = fopen("nasty.state", "w"); |
|
if (fh) |
|
{ |
|
int loop, len; |
|
|
|
fprintf(fh, "# Do not edit this file (unless you're really sure what you're doing)!\n\n"); |
|
|
|
fprintf(fh, "mode=%d\n", mode); |
|
|
|
len = strlen((char *)passphrase_template); |
|
fprintf(fh, "cur="); |
|
for(loop=0; loop<len; loop++) |
|
fprintf(fh, "%c%c", valtohexdigit(passphrase_template[loop] >> 4), valtohexdigit(passphrase_template[loop] & 15)); |
|
fprintf(fh, "\n"); |
|
|
|
fprintf(fh, "charset="); |
|
for(loop=0; loop<charset_n; loop++) |
|
fprintf(fh, "%c%c", valtohexdigit(charset[loop] >> 4), valtohexdigit(charset[loop] & 15)); |
|
fprintf(fh, "\n"); |
|
|
|
fprintf(fh, "min_length=%d\n", min_length); |
|
fprintf(fh, "max_length=%d\n", max_length); |
|
|
|
fprintf(fh, "n_done=%lld\n", n_done); |
|
|
|
if (mode == MODE_FILE) |
|
{ |
|
fprintf(fh, "file=%s\n", pp_file_in); |
|
fprintf(fh, "file_offset=%ld\n", pp_file_in_offset); |
|
} |
|
|
|
fclose(fh); |
|
|
|
printf("\nState saved to 'nasty.state'\n"); |
|
|
|
exit(1); |
|
} |
|
} |
|
|
|
void not_found(void) |
|
{ |
|
if (pp_file_out) |
|
{ |
|
FILE *fh = fopen(pp_file_out, "w"); |
|
if (fh) |
|
{ |
|
fprintf(fh, "passphrase_not_found\n"); |
|
fclose(fh); |
|
} |
|
else |
|
fprintf(stderr, "Failed to create file '%s': %s\n", pp_file_out, strerror(errno)); |
|
} |
|
else |
|
{ |
|
printf("Passphrase not found.\n"); |
|
} |
|
|
|
exit(1); |
|
} |
|
|
|
/* Request a passphrase from the user. */ |
|
gpgme_error_t passphrase_cb(void *hook, const char *uid_hint, const char *passphrase_info, int prev_was_bad, int fd) |
|
{ |
|
int loop, len = 0; |
|
|
|
if (mode == MODE_INCREMENTAL) |
|
{ |
|
int index = 0; |
|
|
|
for(;;) |
|
{ |
|
if (passphrase_template[index] < (charset_n - 1)) |
|
{ |
|
passphrase_template[index]++; |
|
break; |
|
} |
|
else |
|
{ |
|
passphrase_template[index] = 1; |
|
index++; |
|
|
|
if (index == max_length) |
|
not_found(); |
|
} |
|
} |
|
|
|
len = strlen((char *)passphrase_template); |
|
} |
|
else if (mode == MODE_RANDOM) |
|
{ |
|
do |
|
{ |
|
len = (rand() % (max_length - min_length)) + min_length; |
|
} |
|
while(len < min_length || len > max_length); |
|
|
|
for(loop=0; loop<len; loop++) |
|
{ |
|
passphrase_template[loop] = (rand() % (charset_n - 1)) + 1; |
|
} |
|
|
|
passphrase_template[len] = 0x00; |
|
} |
|
else if (mode == MODE_FILE) |
|
{ |
|
pp_file_in_offset = ftell(pp_file_in_fh); |
|
|
|
char c; |
|
int idx = 0; |
|
while ((c=fgetc(pp_file_in_fh)) != '\n' |
|
&& c != -1 |
|
&& idx < MAX_PP_LEN) { |
|
passphrase[idx] = c; |
|
++idx; |
|
} |
|
passphrase[idx] = '\0'; |
|
if (idx == 0) { |
|
not_found(); |
|
exit(1); |
|
} |
|
} |
|
|
|
if (mode != MODE_FILE) |
|
{ |
|
for(loop=0; loop<len; loop++) |
|
passphrase[loop] = charset[passphrase_template[loop]]; |
|
passphrase[loop] = 0x00; |
|
} |
|
|
|
n_done++; |
|
|
|
strcat((char *)passphrase, "\n"); |
|
write(fd, (char *)passphrase, strlen((char *)passphrase)); |
|
|
|
return 0; |
|
} |
|
|
|
void open_pp_file_in(void) |
|
{ |
|
pp_file_in_fh = fopen(pp_file_in, "r"); |
|
if (!pp_file_in_fh) |
|
{ |
|
printf("Failed to open passphrase-file %s: %s\n", pp_file_in, strerror(errno)); |
|
exit(1); |
|
} |
|
} |
|
|
|
int load_state(void) |
|
{ |
|
int ok = 0; |
|
FILE *fh = fopen("nasty.state", "r"); |
|
if (fh) |
|
{ |
|
char buffer[4096], *dummy; |
|
|
|
while(!feof(fh)) |
|
{ |
|
memset(buffer, 0x00, 4096); |
|
|
|
if (!fgets(buffer, 4096, fh)) |
|
break; |
|
|
|
dummy = strchr(buffer, '\n'); |
|
if (dummy) *dummy = 0x00; |
|
|
|
if (buffer[0] == '#' || buffer[0] == ';' || strlen(buffer) == 0) |
|
continue; |
|
|
|
dummy = strchr(buffer, '='); |
|
if (!dummy) goto state_file_corrupted; |
|
|
|
*dummy = 0x00; |
|
dummy++; |
|
|
|
if (strcmp(buffer, "cur") == 0) |
|
{ |
|
int len = cvt_from_hex_string(dummy, (char *)passphrase_template); |
|
|
|
passphrase_template[len] = 0x00; |
|
passphrase_template[0]--; /* make sure that it is fully checked */ |
|
} |
|
else if (strcmp(buffer, "charset") == 0) |
|
{ |
|
charset_n = cvt_from_hex_string(dummy, (char *)charset); |
|
} |
|
else if (strcmp(buffer, "min_length") == 0) |
|
{ |
|
min_length = atoi(dummy); |
|
} |
|
else if (strcmp(buffer, "max_length") == 0) |
|
{ |
|
max_length = atoi(dummy); |
|
} |
|
else if (strcmp(buffer, "n_done") == 0) |
|
{ |
|
n_done = atoll(dummy); |
|
} |
|
else if (strcmp(buffer, "mode") == 0) |
|
{ |
|
mode = atoi(dummy); |
|
} |
|
else if (strcmp(buffer, "file") == 0) |
|
{ |
|
pp_file_in = strdup(dummy); |
|
if (!pp_file_in) |
|
{ |
|
printf("Memory allocation failure.\n"); |
|
exit(1); |
|
} |
|
|
|
open_pp_file_in(); |
|
} |
|
else if (strcmp(buffer, "file_offset") == 0) |
|
{ |
|
if (!pp_file_in_fh) goto state_file_corrupted; |
|
|
|
if (fseek(pp_file_in_fh, atol(dummy), SEEK_SET) == -1) |
|
{ |
|
printf("Failed to seek in passphrase-file: %s\n", strerror(errno)); |
|
exit(1); |
|
} |
|
} |
|
else |
|
{ |
|
state_file_corrupted: |
|
fprintf(stderr, "nasty.state is corrupted!\n"); |
|
fprintf(stderr, "(%s=%s is not understood)\n", buffer, dummy); |
|
exit(1); |
|
} |
|
} |
|
fclose(fh); |
|
} |
|
else |
|
{ |
|
ok = -1; |
|
} |
|
|
|
if (ok == 0) |
|
printf("Using state from previous run\n"); |
|
|
|
return ok; |
|
} |
|
|
|
void add_charset_range(int start, int end) |
|
{ |
|
int loop; |
|
|
|
for(loop=start; loop<=end; loop++) |
|
{ |
|
charset[charset_n++] = (unsigned char)loop; |
|
} |
|
} |
|
|
|
void usage(void) |
|
{ |
|
fprintf(stderr, "-a x set minimum length of passphrase\n"); |
|
fprintf(stderr, "-b x set maximum length\n"); |
|
fprintf(stderr, "-m x set guessing mode:\n"); |
|
fprintf(stderr, " incremental: try them all\n"); |
|
fprintf(stderr, " random: try at random\n"); |
|
fprintf(stderr, " file: read phrases from file (use -i)\n"); |
|
fprintf(stderr, "-i x file to read the passphrases from\n"); |
|
fprintf(stderr, "-f x file to write the found passphrase to\n"); |
|
fprintf(stderr, "-c x... charset, one or more from the following:\n"); |
|
fprintf(stderr, " a: a-z\n"); |
|
fprintf(stderr, " A: A-Z\n"); |
|
fprintf(stderr, " 0: 0-9\n"); |
|
fprintf(stderr, " .: all ascii values (32...126)\n"); |
|
fprintf(stderr, " +: 32...255 (default(!))\n"); |
|
fprintf(stderr, "-k x filter string to select a key\n"); |
|
} |
|
|
|
void filter_keys_and_select_first(gpgme_ctx_t context, const char *key_filter_string, gpgme_key_t *selected_key) |
|
{ |
|
gpgme_error_t err; |
|
gpgme_key_t key; |
|
|
|
err = gpgme_op_keylist_start(context, key_filter_string, 1); |
|
if (err != GPG_ERR_NO_ERROR) error_exit("gpgme_op_keylist_start failed", err); |
|
err = gpgme_op_keylist_next(context, &key); |
|
if (err != GPG_ERR_NO_ERROR) error_exit("gpgme_op_keylist_next failed", err); |
|
err = gpgme_op_keylist_end(context); |
|
if (err != GPG_ERR_NO_ERROR) error_exit("gpgme_op_keylist_end failed", err); |
|
err = gpgme_signers_add(context, key); |
|
if (err != GPG_ERR_NO_ERROR) error_exit("gpgme_signers_add failed", err); |
|
gpgme_key_unref(key); |
|
|
|
printf ("Using %s:", key->subkeys->keyid); |
|
if (key->uids && key->uids->name) printf(" %s", key->uids->name); |
|
if (key->uids && key->uids->email) printf(" <%s>", key->uids->email); |
|
putchar ('\n'); |
|
*selected_key = key; |
|
} |
|
|
|
int main(int argc, char *argv[]) |
|
{ |
|
gpgme_ctx_t ctx; |
|
gpgme_error_t err; |
|
gpgme_data_t in, out; |
|
time_t start, then = time(NULL); |
|
int c, loop; |
|
char charset_set = 0; |
|
char *key_filter_string = NULL; |
|
|
|
printf("nasty v" VERSION ", (C) 2005-2017 by folkert@vanheusden.com\n\n"); |
|
|
|
start = then; |
|
srand(then); |
|
|
|
signal(SIGPIPE, SIG_IGN); |
|
|
|
memset(passphrase, 0, min_length); |
|
memset(passphrase_template, 0, min_length); |
|
charset[0] = 0x00; |
|
charset_n = 1; |
|
|
|
if (load_state() == 0) |
|
{ |
|
if (argc > 1) |
|
{ |
|
fprintf(stderr, "Cannot use commandline switches when using state-file from a previous run.\n"); |
|
fprintf(stderr, "Delete 'nasty.state' to start with other parameters.\n"); |
|
return 1; |
|
} |
|
|
|
goto skip_switches; |
|
} |
|
|
|
while((c = getopt(argc, argv, "a:b:m:c:f:i:k:h")) != -1) |
|
{ |
|
switch(c) |
|
{ |
|
case 'a': |
|
min_length = atoi(optarg); |
|
if (min_length < 1) |
|
{ |
|
fprintf(stderr, "Min. passphrase length is 1\n"); |
|
return 1; |
|
} |
|
break; |
|
|
|
case 'b': |
|
max_length = atoi(optarg); |
|
if (max_length > MAX_PP_LEN) |
|
{ |
|
fprintf(stderr, "Max. passphrase length is %d\n", MAX_PP_LEN); |
|
return 1; |
|
} |
|
break; |
|
|
|
case 'm': |
|
if (strcasecmp(optarg, "incremental") == 0) |
|
mode = MODE_INCREMENTAL; |
|
else if (strcasecmp(optarg, "random") == 0) |
|
mode = MODE_RANDOM; |
|
else if (strcasecmp(optarg, "file") == 0) |
|
mode = MODE_FILE; |
|
else |
|
{ |
|
fprintf(stderr, "%s: unknown mode\n", optarg); |
|
return 1; |
|
} |
|
break; |
|
|
|
case 'c': |
|
for(loop=0; loop<strlen(optarg); loop++) |
|
{ |
|
if (optarg[loop] == 'a') |
|
add_charset_range('a', 'z'); |
|
else if (optarg[loop] == 'A') |
|
add_charset_range('A', 'Z'); |
|
else if (optarg[loop] == '0') |
|
add_charset_range('0', '9'); |
|
else if (optarg[loop] == '.') |
|
add_charset_range(32, 126); |
|
else if (optarg[loop] == '+') |
|
add_charset_range(32, 255); |
|
else |
|
{ |
|
fprintf(stderr, "-c: %c is not understood\n", optarg[loop]); |
|
return 1; |
|
} |
|
|
|
charset_set = 1; |
|
} |
|
break; |
|
|
|
case 'f': |
|
pp_file_out = optarg; |
|
break; |
|
|
|
case 'i': |
|
pp_file_in = optarg; |
|
break; |
|
|
|
case 'k': |
|
key_filter_string = optarg; |
|
break; |
|
|
|
case '?': |
|
case 'h': |
|
usage(); |
|
return 1; |
|
} |
|
} |
|
|
|
if (!charset_set) |
|
{ |
|
/* set default charset */ |
|
add_charset_range(32, 255); |
|
} |
|
|
|
if (mode == MODE_FILE) |
|
{ |
|
if (pp_file_in == NULL) |
|
{ |
|
fprintf(stderr, "Please select the file to read the passphrases from with '-i'.\n"); |
|
return 1; |
|
} |
|
|
|
open_pp_file_in(); |
|
} |
|
|
|
skip_switches: |
|
signal(SIGTERM, sighandler); |
|
signal(SIGINT, sighandler); |
|
signal(SIGKILL, sighandler); |
|
|
|
(void)gpgme_check_version(NULL); |
|
err = gpgme_new(&ctx); |
|
if (err != GPG_ERR_NO_ERROR) error_exit("gpgme_new failed", err); |
|
|
|
err = gpgme_data_new_from_mem(&in, "1234", 4, 0); |
|
if (err != GPG_ERR_NO_ERROR) error_exit("gpgme_data_new_from_mem failed", err); |
|
err = gpgme_data_new(&out); |
|
if (err != GPG_ERR_NO_ERROR) error_exit("gpgme_data_new failed", err); |
|
|
|
gpgme_set_passphrase_cb(ctx, passphrase_cb, NULL); |
|
|
|
gpgme_key_t selected_key; |
|
filter_keys_and_select_first(ctx, key_filter_string, &selected_key); |
|
|
|
do |
|
{ |
|
err = gpgme_op_sign(ctx, in, out, GPGME_SIG_MODE_DETACH); |
|
|
|
if ((time(NULL) - then) > 2) |
|
{ |
|
then = time(NULL); |
|
printf("# tried: %lld (%f per second), last tried: %s\r", n_done, n_done / (double)(then - start), passphrase); |
|
fflush(stdout); |
|
} |
|
} |
|
while(err != 0); |
|
gpgme_key_unref(selected_key); |
|
|
|
if (err == 0) |
|
printf("Passphrase is: %s\n", passphrase); |
|
else |
|
error_exit("gpgme_op_sign failed", err); |
|
|
|
if (pp_file_out) |
|
{ |
|
FILE *fh = fopen(pp_file_out, "w"); |
|
if (fh) |
|
{ |
|
fprintf(fh, "passphrase=%s\n", passphrase); |
|
fclose(fh); |
|
} |
|
else |
|
fprintf(stderr, "Failed to create file '%s': %s\n", pp_file_out, strerror(errno)); |
|
} |
|
|
|
(void)unlink("nasty.state"); |
|
|
|
return 0; |
|
}
|
|
|