diff options
Diffstat (limited to 'timelock.c')
-rw-r--r-- | timelock.c | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/timelock.c b/timelock.c new file mode 100644 index 0000000..486082b --- /dev/null +++ b/timelock.c @@ -0,0 +1,442 @@ +/* timelock -- timed release encryption utility + + Copyright (C) 2017 Arun I <arunisaac@systemreboot.net> + + This file is part of timelock. + + timelock is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + timelock is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with timelock. If not, see <http://www.gnu.org/licenses/>. */ + +// TODO: Implement in-place +// TODO: Strengthen randomness + +#include <stdio.h> +#include <math.h> +#include <gcrypt.h> +#include <inttypes.h> +#include <time.h> +#include <argp.h> +#include <error.h> +#include <boost/preprocessor/seq.hpp> +#include "config.h" + +#define KEY_SIZE 32 +#define NONCE_SIZE 8 +#define READ_BUFFER_SIZE 1024 + +#define PRIME_BITS 1024 +#define KEY_BITS (KEY_SIZE * 8) +#define SQUARINGS_PER_ITERATION 1023 +#define TEST_ITERATIONS 10000 + +#define DEFAULT_TIME 600 + +// Magic bytes +#define MAGIC_BYTES "timelock" +// Output format version number +#define OUTPUT_VERSION 0 +// Chunk types in output +enum chunk_type {END_OF_CHUNKS, CHUNK_N, CHUNK_A, CHUNK_TIMES, CHUNK_CKEY}; +void print_parameter (enum chunk_type, gcry_mpi_t); +// Cipher algorithms +enum cipher_algo {SALSA20}; + +#define _ALLOCATE_MPI(r, _, var) gcry_mpi_t var = gcry_mpi_new(1); +#define ALLOCATE_MPI(mpi) BOOST_PP_SEQ_FOR_EACH(_ALLOCATE_MPI, _, mpi) +#define _ALLOCATE_SECURE_MPI(r, _, var) gcry_mpi_t var = gcry_mpi_snew(1); +#define ALLOCATE_SECURE_MPI(mpi) BOOST_PP_SEQ_FOR_EACH(_ALLOCATE_SECURE_MPI, _, mpi) +#define _CLEANUP(r, _, var) _Generic((var), gcry_mpi_t: gcry_mpi_release, \ + gcry_cipher_hd_t: gcry_cipher_close, \ + default: free)(var); +#define CLEANUP(vars) BOOST_PP_SEQ_FOR_EACH(_CLEANUP, _, vars) +#define WITH_MPI(mpi, body) { ALLOCATE_MPI(mpi); body; CLEANUP(mpi); } + +#define TRY_CATCH_GCRYPT(try, message, handle) if (handle_gcrypt_error(try, message)) { handle; exit(1); } +#define SAFE_FREAD(buffer, len, handle) \ + if (fread(buffer, 1, len, stdin) < len) \ + { handle; error(1, 0, "Incomplete input\n"); } +#define SAFE_FWRITE(buffer, len, handle) \ + if (fwrite(buffer, 1, len, stdout) < len) \ + { handle; error(1, 0, "Error writing to standard output"); } +#define SAFE_MALLOC(ptr, size, handle) if (!(ptr = malloc(size))) { handle; perror(__func__); exit(1); } +#define SAFE_REALLOC(ptr, size, handle) if (!(ptr = realloc(ptr, size))) { handle; perror(__func__); exit(1); } + +// gcrypt helper functions +gcry_mpi_t mpi_set_ulli (gcry_mpi_t, unsigned long long); +unsigned long long mpi_to_ulli (gcry_mpi_t w); +gcry_error_t generate_prime (gcry_mpi_t*); +int handle_gcrypt_error (gcry_error_t, const char*); +gcry_error_t open_cipher (gcry_cipher_hd_t* handle, const void *key, const unsigned char *nonce); + +size_t read_stdin (unsigned char**); +void read_parameters (gcry_mpi_t* n, gcry_mpi_t* a, unsigned long long* times, gcry_mpi_t* ckey); +void lock (unsigned long); +void unlock (int); +void info (); +int create_puzzle (gcry_mpi_t, unsigned long long, gcry_mpi_t, gcry_mpi_t, gcry_mpi_t); +void solve_puzzle (gcry_mpi_t, gcry_mpi_t, gcry_mpi_t, unsigned long long, gcry_mpi_t, int); +double squarings_per_second (); + +const char* argp_program_version = PACKAGE_STRING; +const char* argp_program_bug_address = PACKAGE_BUGREPORT; +static char doc[] = "timelock -- encrypt files which can be opened only in the future"; +static char args_doc[] = "[lock|unlock]"; + +static struct argp_option options[] = { + {"time", 't', "SECONDS", 0, "Time (in seconds) for which the input should be locked", 0}, + {"quiet", 'q', 0, 0, "Don't produce any output", 0}, + {"help", 'h', 0, 0, "Give this help list", 0}, + {"usage", 1, 0, OPTION_HIDDEN, "Give a short usage message", 0}, + { 0 } +}; + +struct arguments { + enum {LOCK, UNLOCK, INFO} command; + unsigned long seconds; + int quiet; + char* input_file; +}; + +static error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + struct arguments *arguments = state->input; + switch (key) { + case 't': + sscanf(arg, "%lu", &arguments->seconds); + break; + case 'i': + arguments->input_file = arg; + break; + case 'q': + arguments->quiet = 1; + break; + case 'h': + argp_state_help(state, stderr, ARGP_HELP_STD_HELP); + break; + case 1: + argp_state_help(state, stderr, ARGP_HELP_STD_USAGE); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) + argp_usage(state); + if (!strcmp(arg, "lock")) + arguments->command = LOCK; + else if (!strcmp(arg, "unlock")) + arguments->command = UNLOCK; + else { + fprintf(stderr, "%s: Invalid command\n", arg); + argp_usage(state); + exit(1); + } + break; + case ARGP_KEY_END: + if (((arguments->command == UNLOCK) || (arguments->command == INFO)) && (arguments->seconds != 0)) { + fprintf(stderr, "-t|--times: Option only valid while locking\n"); + argp_usage(state); + exit(1); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; + +int main (int argc, char *argv[]) +{ + struct arguments arguments; + arguments.command = INFO; + arguments.seconds = 0; + arguments.input_file = "-"; + arguments.quiet = 0; + argp_parse(&argp, argc, argv, ARGP_NO_HELP, 0, &arguments); + + if (!gcry_check_version (GCRYPT_VERSION)) + error(2, 0, "libgcrypt version mismatch\n"); + gcry_control(GCRYCTL_SUSPEND_SECMEM_WARN); + gcry_control(GCRYCTL_INIT_SECMEM, 16384, 0); + gcry_control(GCRYCTL_RESUME_SECMEM_WARN); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + + if (arguments.command == LOCK) + lock(arguments.seconds == 0 ? DEFAULT_TIME : arguments.seconds); + else if (arguments.command == UNLOCK) unlock(arguments.quiet != 1); + else if (arguments.command == INFO) info(); + return 0; +} + +size_t read_stdin (unsigned char **buffer) +{ + // Allocate memory for read buffer + SAFE_MALLOC(*buffer, READ_BUFFER_SIZE, return 0); + size_t bytes_read = 0; + + // Read READ_BUFFER_SIZE bytes at a time + for (size_t size=READ_BUFFER_SIZE; ; size += READ_BUFFER_SIZE) { + size_t len = fread(*buffer + size - READ_BUFFER_SIZE, 1, READ_BUFFER_SIZE, stdin); + bytes_read += len; + if (len < READ_BUFFER_SIZE) + if (ferror(stdin)) { + perror(__func__); + CLEANUP((*buffer)); + return 0; + } + else return bytes_read; + else SAFE_REALLOC(*buffer, size + READ_BUFFER_SIZE, return 0); + } +} + +gcry_error_t generate_prime (gcry_mpi_t* p) +{ + return gcry_prime_generate(p, PRIME_BITS, 0, NULL, NULL, NULL, GCRY_WEAK_RANDOM, 0); +} + +void print_parameter (enum chunk_type type, gcry_mpi_t param) +{ + unsigned char* buffer; + size_t nwritten; + gcry_mpi_aprint(GCRYMPI_FMT_PGP, &buffer, &nwritten, param); + SAFE_FWRITE(&type, 1, ); + SAFE_FWRITE(buffer, nwritten, ); + CLEANUP((buffer)); +} + +gcry_mpi_t mpi_set_ulli (gcry_mpi_t w, unsigned long long u) +{ + unsigned long long u_be = htobe64(u); + gcry_mpi_scan(&w, GCRYMPI_FMT_USG, &u_be, sizeof(u_be), NULL); + return w; +} + +unsigned long long mpi_to_ulli (gcry_mpi_t w) +{ + unsigned long long u; + size_t nwritten; + gcry_mpi_print(GCRYMPI_FMT_USG, (unsigned char*)&u, sizeof(u), &nwritten, w); + u = be64toh(u) >> (sizeof(u) - nwritten)*8; + return u; +} + +void lock (const unsigned long seconds) +{ + unsigned char key_bytes[KEY_SIZE]; + WITH_MPI + ((n)(a)(times_mpi)(ckey)(key_mpi), + unsigned long long times = seconds * squarings_per_second(); + + // Create and output puzzle + if (create_puzzle(key_mpi, times, n, a, ckey)) { + CLEANUP((n)(a)(ckey)(key_mpi)); + exit(1); + } + printf("%s%c", MAGIC_BYTES, OUTPUT_VERSION); + print_parameter(CHUNK_N, n); + print_parameter(CHUNK_A, a); + times_mpi = mpi_set_ulli(times_mpi, times); + print_parameter(CHUNK_TIMES, times_mpi); + print_parameter(CHUNK_CKEY, ckey); + printf("%c", END_OF_CHUNKS); + TRY_CATCH_GCRYPT(gcry_mpi_print(GCRYMPI_FMT_USG, key_bytes, KEY_SIZE, NULL, key_mpi), + "Convert MPI key to bytes", CLEANUP((n)(a)(times_mpi)(ckey)(key_mpi)));); + + // Read plaintext of message + unsigned char* buffer; + size_t len = read_stdin(&buffer); + if (!len) exit(1); + + // Generate and print nonce + unsigned char nonce[NONCE_SIZE]; + gcry_create_nonce(nonce, NONCE_SIZE); + printf("%c", SALSA20); + SAFE_FWRITE(nonce, NONCE_SIZE, CLEANUP((buffer))); + + // Write ciphertext of message + gcry_cipher_hd_t handle; + TRY_CATCH_GCRYPT(open_cipher(&handle, key_bytes, nonce), + "Open cipher handle", CLEANUP((handle)(buffer))); + TRY_CATCH_GCRYPT(gcry_cipher_encrypt(handle, buffer, len, NULL, 0), + "Encrypt message", CLEANUP((handle)(buffer))); + SAFE_FWRITE(buffer, len, CLEANUP((handle)(buffer))); + + CLEANUP((handle)(buffer)); +} + +double squarings_per_second () +{ + double squarings = 0; + WITH_MPI + ((n)(a)(ckey)(key), + if (create_puzzle(key, TEST_ITERATIONS, n, a, ckey)) { + CLEANUP((n)(a)(ckey)(key)); + exit(1); + } + clock_t start = clock(); + solve_puzzle(key, n, a, TEST_ITERATIONS, ckey, 0); + clock_t stop = clock(); + squarings = CLOCKS_PER_SEC * TEST_ITERATIONS / ((double)(stop - start));); + return squarings; +} + +void unlock (int progress) +{ + unsigned char key_bytes[KEY_SIZE]; + unsigned long long times; + WITH_MPI + ((n)(a)(ckey)(key), + read_parameters(&n, &a, ×, &ckey); + solve_puzzle(key, n, a, times, ckey, progress); + TRY_CATCH_GCRYPT(gcry_mpi_print(GCRYMPI_FMT_USG, key_bytes, KEY_SIZE, NULL, key), + "Convert MPI key to bytes", CLEANUP((n)(a)(ckey)(key)));); + + // Read nonce + enum cipher_algo cipher; + SAFE_FREAD(&cipher, 1, ); + if ((uint8_t)cipher != SALSA20) { + error(1, 0, "Cipher algorithm not supported\n"); + exit(1); + } + unsigned char nonce[NONCE_SIZE]; + SAFE_FREAD(nonce, NONCE_SIZE, ); + + // Open cipher handle + gcry_cipher_hd_t handle; + TRY_CATCH_GCRYPT(open_cipher(&handle, key_bytes, nonce), + "Open cipher handle", CLEANUP((handle))); + + // Read stdin + unsigned char* buffer; + size_t len = read_stdin(&buffer); + if (!len) CLEANUP((handle)); + + // Decrypt + TRY_CATCH_GCRYPT(gcry_cipher_decrypt(handle, buffer, len, NULL, 0), + "Decrypt message", CLEANUP((handle)(buffer))); + SAFE_FWRITE(buffer, len, ); + CLEANUP((handle)(buffer)); +} + +int create_puzzle (const gcry_mpi_t key, const unsigned long long _times, gcry_mpi_t n, gcry_mpi_t a, gcry_mpi_t ckey) +{ + ALLOCATE_SECURE_MPI((p)(q)(phi)(two)(times)); + times = mpi_set_ulli(times, _times); + two = mpi_set_ulli(two, 2); + int rv = 1; + + // Primes p, q; n, phi and a + TRY_CATCH_GCRYPT((generate_prime(&p) || generate_prime(&q)), "Generate primes", goto cleanup); + gcry_mpi_mul(n, p, q); + gcry_mpi_sub_ui(p, p, 1); + gcry_mpi_sub_ui(q, q, 1); + gcry_mpi_mul(phi, p, q); + while (!((gcry_mpi_cmp_ui(a, 1) > 0) && (gcry_mpi_cmp(a, n) < 0))) + gcry_mpi_randomize(a, gcry_mpi_get_nbits(n), GCRY_WEAK_RANDOM); + + // ciphertext of key + gcry_mpi_powm(ckey, two, times, phi); + gcry_mpi_powm(ckey, a, ckey, n); + while (!((gcry_mpi_cmp_ui(key, 1) > 0) && (gcry_mpi_cmp(key, n) < 0))) + gcry_mpi_randomize(key, KEY_BITS, GCRY_WEAK_RANDOM); + gcry_mpi_addm(ckey, key, ckey, n); + rv = 0; + + cleanup: + CLEANUP((p)(q)(phi)(two)(times)); + return rv; +} + +void solve_puzzle (gcry_mpi_t key, const gcry_mpi_t n, const gcry_mpi_t a, const unsigned long long times, const gcry_mpi_t ckey, int progress) +{ + // Compute number of squarings + unsigned long long full = times / SQUARINGS_PER_ITERATION; + unsigned long long partial = times % SQUARINGS_PER_ITERATION; + + // Perform squarings + gcry_mpi_set(key, a); + WITH_MPI + ((one)(exponent), + one = mpi_set_ulli(one, 1); + gcry_mpi_mul_2exp(exponent, one, SQUARINGS_PER_ITERATION); + for (unsigned long long i=0; i<full; i++) { + if (progress) fprintf(stderr, "\rProgress: %.2f%%", (100.0*i)/full); + gcry_mpi_powm(key, key, exponent, n); + } + gcry_mpi_mul_2exp(exponent, one, partial); + gcry_mpi_powm(key, key, exponent, n); + if (progress) fprintf(stderr, "\rProgress: 100.00%%\n"); + gcry_mpi_subm(key, ckey, key, n);); +} + +gcry_error_t open_cipher (gcry_cipher_hd_t* handle, const void *key, const unsigned char *nonce) +{ + gcry_error_t err; + if (gcry_cipher_open(handle, GCRY_CIPHER_SALSA20, GCRY_CIPHER_MODE_STREAM, 0)) return err; + if (gcry_cipher_setkey(*handle, key, KEY_SIZE)) return err; + if (gcry_cipher_setiv(*handle, nonce, NONCE_SIZE)) return err; + return 0; +} + +int handle_gcrypt_error (gcry_error_t err, const char* function) +{ + if (err) fprintf(stderr, "%s: %s/%s\n", function, gcry_strsource(err), gcry_strerror(err)); + return err ? 1 : 0; +} + +void info () +{ + unsigned long long times; + double sps = squarings_per_second(); + if (sps == 0) exit(1); + WITH_MPI + ((n)(a)(ckey), + read_parameters(&n, &a, ×, &ckey); + printf("n: %u bits\n", gcry_mpi_get_nbits(n)); + printf("a: %u bits\n", gcry_mpi_get_nbits(a)); + printf("squarings: %llu [ETA: %.0f second(s)]\n", times, round(times/sps)); + printf("ckey: %u bits\n", gcry_mpi_get_nbits(ckey));); +} + +void read_parameters (gcry_mpi_t* n, gcry_mpi_t* a, unsigned long long* times, gcry_mpi_t* ckey) +{ + unsigned char* buffer; + SAFE_MALLOC(buffer, strlen(MAGIC_BYTES) + 1, exit(1)); + ALLOCATE_MPI((times_mpi)); + + // Check for magic bytes + buffer[strlen(MAGIC_BYTES)] = 0; + SAFE_FREAD(buffer, strlen(MAGIC_BYTES), ); + if (!strstr((char*)buffer, MAGIC_BYTES)) + error(1, 0, "Magic bytes missing. Input data is invalid.\n"); + + // Check for output version + uint8_t version = fgetc(stdin); + if (version > OUTPUT_VERSION) + error(1, 0, "Input data format version %d not supported. Please upgrade %s.\n", version, PACKAGE); + + // Read parameters + enum chunk_type type; + gcry_mpi_t* params[] = {NULL, n, a, ×_mpi, ckey}; + SAFE_REALLOC(buffer, 2, exit(1)); + while ((type = fgetc(stdin))) { + SAFE_FREAD(buffer, 2, ); + size_t len = be16toh(*(uint16_t*)buffer); + len = (len / 8) + (len % 8 ? 1 : 0); + SAFE_REALLOC(buffer, len + 2, exit(1)); + SAFE_FREAD(buffer + 2, len, ); + TRY_CATCH_GCRYPT(gcry_mpi_scan(params[type], GCRYMPI_FMT_PGP, buffer, len + 2, NULL), + "Read parameter", exit(1)); + } + *times = mpi_to_ulli(times_mpi); + CLEANUP((times_mpi)(buffer)); +} |