/* timelock -- timed release encryption utility Copyright (C) 2017 Arun I 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 . */ // TODO: Implement in-place // TODO: Strengthen randomness #include #include #include #include #include #include #include #include #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 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)); }