aboutsummaryrefslogtreecommitdiff
path: root/timelock.c
diff options
context:
space:
mode:
Diffstat (limited to 'timelock.c')
-rw-r--r--timelock.c442
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, &times, &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, &times, &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, &times_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));
+}