SlunkCrypt/frontend/src/main.c

962 lines
28 KiB
C
Raw Normal View History

/******************************************************************************/
/* SlunkCrypt, by LoRd_MuldeR <MuldeR2@GMX.de> */
/* This work has been released under the CC0 1.0 Universal license! */
/******************************************************************************/
#ifdef _WIN32
# define _CRT_SECURE_NO_WARNINGS 1
#else
# define _GNU_SOURCE 1
#endif
/* API */
#include <slunkcrypt.h>
/* CLI */
2020-10-13 15:04:59 +02:00
#include "utils.h"
#include "blake2.h"
2020-10-15 21:56:36 +02:00
#include "test.h"
2020-11-04 23:17:59 +01:00
/* CRT */
#include <string.h>
#include <time.h>
2020-10-13 15:37:40 +02:00
#include <inttypes.h>
#include <ctype.h>
#include <signal.h>
#include <assert.h>
// ==========================================================================
// Constants
// ==========================================================================
2020-10-20 15:21:00 +02:00
#define BUFFER_SIZE 4096U
#define MODE_HELP 0
#define MODE_VERS 1
#define MODE_ENCR 2
#define MODE_DECR 3
#define MODE_PASS 4
#define MODE_TEST 5
#define PW_FROM_ENV (!(argc > 4))
static const CHR* const ENV_PASSWORD = T("SLUNK_PASSPHRASE");
static const CHR* const ENV_KEEPFILE = T("SLUNK_KEEP_INCOMPLETE");
static const CHR* const PREFIX_PASS = T("pass:");
static const CHR* const PREFIX_FILE = T("file:");
static const char PASSWD_SYMBOLS[] =
{
'!', '#', '$', '%', '&', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1',
'2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', ']', '^', '_',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'
};
static const size_t RCMD_PWDLEN_LENGTH = 12U;
static const size_t DFLT_PWDLEN_LENGTH = 20U;
2021-03-27 16:19:26 +01:00
static const clock_t UPDATE_INTERVAL = (clock_t)(1.5708 * CLOCKS_PER_SEC);
static const uint64_t MAGIC_NUMBER = 0x243F6A8885A308D3ull;
2020-10-20 15:21:00 +02:00
// ==========================================================================
// Auxiliary functions
// ==========================================================================
static int parse_slunk_mode(const CHR* const command)
2020-10-20 15:21:00 +02:00
{
if ((!STRICMP(command, T("-h"))) || (!STRICMP(command, T("/?"))) || (!STRICMP(command, T("--help"))))
{
return MODE_HELP;
2020-10-20 15:21:00 +02:00
}
else if ((!STRICMP(command, T("-v"))) || (!STRICMP(command, T("--version"))))
{
return MODE_VERS;
2020-10-20 15:21:00 +02:00
}
else if ((!STRICMP(command, T("-e"))) || (!STRICMP(command, T("--encrypt"))))
{
return MODE_ENCR;
2020-10-20 15:21:00 +02:00
}
else if ((!STRICMP(command, T("-d"))) || (!STRICMP(command, T("--decrypt"))))
{
return MODE_DECR;
2020-10-20 15:21:00 +02:00
}
else if ((!STRICMP(command, T("-p"))) || (!STRICMP(command, T("--make-pw"))))
{
return MODE_PASS;
}
2020-10-20 15:21:00 +02:00
else if ((!STRICMP(command, T("-t"))) || (!STRICMP(command, T("--self-test"))))
{
return MODE_TEST;
2020-10-20 15:21:00 +02:00
}
else
{
return -1; /*invalid command*/
2020-10-20 15:21:00 +02:00
}
}
static void print_manpage(const CHR *const program)
{
FPUTS(T("====================================================================\n"), stderr);
FPUTS(T("This software has been released under the CC0 1.0 Universal license:\n"), stderr);
FPUTS(T("https://creativecommons.org/publicdomain/zero/1.0/legalcode\n"), stderr);
FPUTS(T("====================================================================\n\n"), stderr);
2020-10-20 15:33:03 +02:00
FPUTS(T("Usage:\n"), stderr);
FPRINTF(stderr, T(" %") T(PRISTR) T(" --encrypt [pass:<pass>|file:<file>] <input.txt> <output.enc>\n"), program);
FPRINTF(stderr, T(" %") T(PRISTR) T(" --decrypt [pass:<pass>|file:<file>] <input.enc> <output.txt>\n"), program);
FPRINTF(stderr, T(" %") T(PRISTR) T(" --make-pw [<length>]\n\n"), program);
FPRINTF(stderr, T("Optionally, reads passphrase from the %") T(PRISTR) T(" environment variable.\n\n"), ENV_PASSWORD);
}
static char *copy_passphrase(const CHR *const passphrase)
{
if ((!passphrase) || (!passphrase[0U]))
{
FPUTS(T("Error: The passphrase input string must not be empty!\n\n"), stderr);
return NULL;
}
char *const buffer = CHR_to_utf8(passphrase);
if (!buffer)
{
FPUTS(T("Error: Failed to allocate the string buffer!\n\n"), stderr);
}
return buffer;
2020-10-20 15:21:00 +02:00
}
static char *read_passphrase(const CHR *const file_name)
{
if ((!file_name) || (!file_name[0U]))
{
FPUTS(T("Error: The passphrase input file name must not be empty!\n\n"), stderr);
return NULL;
}
const size_t max_len = SLUNKCRYPT_PWDLEN_MAX + 2U;
char *const buffer = (char*) malloc(max_len * sizeof(char));
if (!buffer)
{
FPUTS(T("Error: Failed to allocate the passphrase buffer!\n\n"), stderr);
return NULL;
}
const int use_stdin = (!file_name) || (!STRICMP(file_name, T("-")));
FILE *const file_in = use_stdin ? stdin : FOPEN(file_name, T("rb"));
if (!file_in)
{
FPRINTF(stderr, T("Error: Failed to open input file \"%") T(PRISTR) T("\" for reading!\n\n"), file_name);
free(buffer);
return NULL;
}
do
{
if (!fgets(buffer, (int)max_len, file_in))
{
buffer[0U] = '\0';
goto finish;
}
size_t length = strlen(buffer);
while ((length > 0U) && ((buffer[length - 1U] == '\r') || (buffer[length - 1U] == '\n')))
{
buffer[--length] = '\0';
}
}
while (!buffer[0U]);
finish:
if ((!use_stdin) && file_in)
{
fclose(file_in);
}
return buffer;
}
static int weak_passphrase(const char *str)
{
int flags[4U] = { 0, 0, 0, 0 };
while (*str)
{
const int c = *str++;
if (isalpha(c))
{
flags[isupper(c) ? 0U : 1U] = 1;
}
else
{
flags[isdigit(c) ? 2U : 3U] = 1;
}
}
const int strong = flags[0U] && flags[1U] && flags[2U] && flags[3U];
return !strong;
}
static int generate_passphrase(const size_t length)
{
int result = EXIT_FAILURE;
const size_t passwd_len = BOUND(SLUNKCRYPT_PWDLEN_MIN, length, SLUNKCRYPT_PWDLEN_MAX);
char *const buffer = (char*) malloc((passwd_len + 1U) * sizeof(char));
if (!buffer)
{
FPUTS(T("Error: Failed to allocate memory buffer!\n\n"), stderr);
return EXIT_FAILURE;
}
do
{
for (size_t i = 0U; i < passwd_len; ++i)
{
uint64_t value;
if (slunkcrypt_generate_nonce(&value) != SLUNKCRYPT_SUCCESS)
{
FPUTS(T("Error: Failed to generate next random number!\n\n"), stderr);
goto clean_up;
}
buffer[i] = PASSWD_SYMBOLS[value % ARRAY_SIZE(PASSWD_SYMBOLS)];
}
buffer[passwd_len] = '\0';
}
while ((!isalpha((int)buffer[0U])) || (!isalnum((int)buffer[passwd_len - 1U])) || weak_passphrase(buffer));
FPRINTF(stdout, T("%") T(PRIstr) T("\n\n"), buffer);
fflush(stdout);
result = EXIT_SUCCESS;
clean_up:
if (buffer)
{
slunkcrypt_bzero(buffer, passwd_len * sizeof(char));
free(buffer);
}
return result;
}
static int open_files(FILE **const file_in, FILE **const file_out, const CHR *const input_path, const CHR *const output_path)
{
if (!(*file_in = FOPEN(input_path, T("rb"))))
{
FPRINTF(stderr, T("Error: Failed to open input file \"%") T(PRISTR) T("\" for reading!\n\n"), input_path);
*file_out = NULL;
2020-10-20 22:13:39 +02:00
return EXIT_FAILURE;
}
if (!(*file_out = FOPEN(output_path, T("wb"))))
{
FPRINTF(stderr, T("Error: Failed to open output file \"%") T(PRISTR) T("\" for writing!\n\n"), output_path);
2020-10-20 22:13:39 +02:00
return EXIT_FAILURE;
}
2020-10-20 22:13:39 +02:00
return EXIT_SUCCESS;
2020-10-13 00:43:57 +02:00
}
static int remove_incomplete_files(void)
{
const CHR* const keep_files = GETENV(ENV_KEEPFILE);
if (keep_files)
{
return (!STRTOUL(keep_files));
}
return 1;
}
2020-10-20 15:21:00 +02:00
static void sigint_handler(const int sig)
{
if (sig == SIGINT)
{
g_slunkcrypt_abort_flag = 1;
}
}
// ==========================================================================
// Encrypt
// ==========================================================================
2020-10-14 21:55:39 +02:00
static int encrypt(const char* const passphrase, const CHR* const input_path, const CHR* const output_path)
2020-10-13 00:43:57 +02:00
{
slunkcrypt_t ctx = SLUNKCRYPT_NULL;
2020-12-01 02:24:35 +01:00
FILE *file_in = NULL, *file_out = NULL;
int result = EXIT_FAILURE, status;
2020-10-13 00:43:57 +02:00
2020-10-20 22:13:39 +02:00
if (open_files(&file_in, &file_out, input_path, output_path) != EXIT_SUCCESS)
2020-10-13 00:43:57 +02:00
{
goto clean_up;
}
2020-10-14 21:55:39 +02:00
const uint64_t file_size = get_file_size(file_in);
2020-10-13 15:04:59 +02:00
if (file_size == UINT64_MAX)
{
FPUTS(T("I/O error: Failed to determine size of input file!\n\n"), stderr);
goto clean_up;
}
2020-10-14 21:55:39 +02:00
else if (file_size < 1U)
2020-10-13 15:04:59 +02:00
{
2020-10-14 21:55:39 +02:00
FPUTS(T("Error: Input file is empty or an unsupported type!\n\n"), stderr);
2020-10-13 15:04:59 +02:00
goto clean_up;
}
FPUTS(T("Encrypting file contents, please be patient... "), stderr);
fflush(stderr);
uint64_t nonce;
if (slunkcrypt_generate_nonce(&nonce) != SLUNKCRYPT_SUCCESS)
{
FPUTS(T("\n\nSlunkCrypt error: Failed to generate nonce!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
ctx = slunkcrypt_alloc(nonce, (const uint8_t*)passphrase, strlen(passphrase), SLUNKCRYPT_ENCRYPT);
if (!ctx)
{
FPUTS(g_slunkcrypt_abort_flag ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to initialize encryption!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
if (fwrite_ui64(nonce ^ MAGIC_NUMBER, file_out) < 1U)
{
FPUTS(T("\n\nI/O error: Failed to write nonce value!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
unsigned refresh_cycles = 0U;
clock_t clk_update = clock();
uint64_t bytes_read = 0U;
2020-10-20 15:21:00 +02:00
uint8_t buffer[BUFFER_SIZE];
blake2s_t blake2s_state;
blake2s_init(&blake2s_state);
FPRINTF(stderr, T("%5.1f%% "), 0.0);
fflush(stderr);
2020-10-14 21:55:39 +02:00
while (bytes_read < file_size)
{
2020-10-14 21:55:39 +02:00
const uint64_t bytes_remaining = file_size - bytes_read;
2020-10-20 15:21:00 +02:00
const size_t request_len = (bytes_remaining < BUFFER_SIZE) ? ((size_t)bytes_remaining) : BUFFER_SIZE;
2020-10-14 21:55:39 +02:00
const size_t count = fread(buffer, sizeof(uint8_t), request_len, file_in);
if (count > 0U)
{
blake2s_update(&blake2s_state, buffer, count);
2020-10-13 15:04:59 +02:00
bytes_read += count;
2021-04-02 16:37:19 +02:00
if ((status = slunkcrypt_inplace(ctx, buffer, count)) != SLUNKCRYPT_SUCCESS)
{
FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to encrypt data!\n\n"), stderr);
2020-12-01 02:24:35 +01:00
goto clean_up;
}
2020-10-14 21:55:39 +02:00
if (fwrite(buffer, sizeof(uint8_t), count, file_out) < count)
{
2020-10-13 15:04:59 +02:00
FPUTS(T("\n\nI/O error: Failed to write encrypted data!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
}
2020-10-14 21:55:39 +02:00
if (count < request_len)
{
break; /*EOF*/
}
if (!(++refresh_cycles & 0x1F))
2020-10-13 15:04:59 +02:00
{
const clock_t clk_now = clock();
if ((clk_now < clk_update) || (clk_now - clk_update > UPDATE_INTERVAL))
{
FPRINTF(stderr, T("\b\b\b\b\b\b\b%5.1f%% "), (bytes_read / ((double)file_size)) * 100.0);
fflush(stderr);
clk_update = clk_now;
}
2020-10-13 15:04:59 +02:00
}
}
2020-10-14 21:55:39 +02:00
if (ferror(file_in))
{
FPUTS(T("\n\nI/O error: Failed to read input data!\n\n"), stderr);
goto clean_up;
}
2020-10-13 15:04:59 +02:00
2020-12-01 02:24:35 +01:00
if (bytes_read != file_size)
2020-10-13 15:04:59 +02:00
{
FPUTS(T("\n\nI/O error: Input file could not be fully read!\n\n"), stderr);
goto clean_up;
}
2020-12-01 02:24:35 +01:00
const size_t padding = sizeof(uint64_t) - (file_size % sizeof(uint64_t));
assert(padding && (padding <= sizeof(uint64_t)));
2020-12-01 02:24:35 +01:00
if (slunkcrypt_random_bytes(buffer, padding) < padding)
{
FPUTS(T("\n\nSlunkCrypt error: Failed to generate random data!\n\n"), stderr);
goto clean_up;
}
SET_LOWBITS(buffer[padding - 1U], padding - 1U);
2021-04-02 16:37:19 +02:00
if ((status = slunkcrypt_inplace(ctx, buffer, padding)) != SLUNKCRYPT_SUCCESS)
2020-12-01 02:24:35 +01:00
{
FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to encrypt data!\n\n"), stderr);
goto clean_up;
}
if (fwrite(buffer, sizeof(uint8_t), padding, file_out) < padding)
{
FPUTS(T("\n\nI/O error: Failed to write padding data!\n\n"), stderr);
goto clean_up;
}
uint8_t checksum_buffer[sizeof(uint64_t)];
store_ui64(checksum_buffer, blake2s_final(&blake2s_state));
2020-10-20 22:13:39 +02:00
2021-04-02 16:37:19 +02:00
if ((status = slunkcrypt_inplace(ctx, checksum_buffer, sizeof(uint64_t))) != SLUNKCRYPT_SUCCESS)
2020-10-20 22:13:39 +02:00
{
FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to encrypt checksum!\n\n"), stderr);
goto clean_up;
}
2020-10-16 18:07:45 +02:00
if (fwrite(checksum_buffer, sizeof(uint8_t), sizeof(uint64_t), file_out) < sizeof(uint64_t))
{
FPUTS(T("\n\nI/O error: Failed to write the checksum!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
2020-10-20 22:13:39 +02:00
FPRINTF(stderr, T("\b\b\b\b\b\b\b%5.1f%%\n\n"), 100.0);
fflush(stderr);
result = EXIT_SUCCESS;
2020-10-13 00:43:57 +02:00
2020-10-13 15:04:59 +02:00
FPUTS(T("All is done.\n\n"), stderr);
2020-10-13 00:43:57 +02:00
fflush(stderr);
clean_up:
if (ctx)
{
slunkcrypt_free(ctx);
2020-10-13 00:43:57 +02:00
}
2020-10-14 21:55:39 +02:00
if (file_out)
2020-10-13 00:43:57 +02:00
{
2020-10-14 21:55:39 +02:00
fclose(file_out);
if ((result != EXIT_SUCCESS) && remove_incomplete_files())
{
if (REMOVE(output_path))
{
FPUTS(T("Warning: Failed to remove incomplete output file!\n\n"), stderr);
}
}
2020-10-13 00:43:57 +02:00
}
2020-10-14 21:55:39 +02:00
if (file_in)
2020-10-13 00:43:57 +02:00
{
2020-10-14 21:55:39 +02:00
fclose(file_in);
}
slunkcrypt_bzero(buffer, BUFFER_SIZE);
slunkcrypt_bzero(checksum_buffer, sizeof(uint64_t));
slunkcrypt_bzero(&blake2s_state, sizeof(blake2s_t));
slunkcrypt_bzero(&nonce, sizeof(uint64_t));
2020-10-13 00:43:57 +02:00
return result;
}
2020-10-20 15:21:00 +02:00
// ==========================================================================
// Decrypt
// ==========================================================================
2020-10-14 21:55:39 +02:00
static int decrypt(const char* const passphrase, const CHR* const input_path, const CHR* const output_path)
{
slunkcrypt_t ctx = SLUNKCRYPT_NULL;
2020-10-14 21:55:39 +02:00
FILE *file_in = NULL, *file_out = NULL;
2020-12-01 02:24:35 +01:00
int result = EXIT_FAILURE, status;
2020-10-13 00:43:57 +02:00
2020-10-20 22:13:39 +02:00
if (open_files(&file_in, &file_out, input_path, output_path) != EXIT_SUCCESS)
{
2020-10-13 00:43:57 +02:00
goto clean_up;
}
2020-10-14 21:55:39 +02:00
const uint64_t file_size = get_file_size(file_in);
2020-10-13 00:43:57 +02:00
if (file_size == UINT64_MAX)
{
2020-10-13 15:04:59 +02:00
FPUTS(T("I/O error: Failed to determine size of input file!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
2020-12-01 02:24:35 +01:00
else if (file_size < (3U * sizeof(uint64_t)))
{
2020-10-13 15:04:59 +02:00
FPUTS(T("Error: Input file is too small! Truncated?\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
else if ((file_size % sizeof(uint64_t)) != 0)
{
2021-04-08 01:58:54 +02:00
FPRINTF(stderr, T("Warning: File size is *not* an integer multiple of %u, ignoring excess bytes!\n\n"), (unsigned)sizeof(uint64_t));
}
FPUTS(T("Decrypting file contents, please be patient... "), stderr);
fflush(stderr);
uint64_t nonce;
if (fread_ui64(&nonce, file_in) < 1U)
{
FPUTS(T("\n\nI/O error: Failed to read nonce value!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
ctx = slunkcrypt_alloc(nonce ^ MAGIC_NUMBER, (const uint8_t*)passphrase, strlen(passphrase), SLUNKCRYPT_DECRYPT);
if (!ctx)
{
FPUTS(g_slunkcrypt_abort_flag ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to initialize decryption!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
unsigned refresh_cycles = 0U;
clock_t clk_update = clock();
uint64_t bytes_read = sizeof(uint64_t);
2020-10-20 15:21:00 +02:00
uint8_t buffer[BUFFER_SIZE];
const uint64_t read_limit = round_down(file_size, sizeof(uint64_t)) - (2U * sizeof(uint64_t));
blake2s_t blake2s_state;
blake2s_init(&blake2s_state);
FPRINTF(stderr, T("%5.1f%% "), 0.0);
fflush(stderr);
2020-10-14 21:55:39 +02:00
while (bytes_read < read_limit)
{
2020-10-13 15:04:59 +02:00
const uint64_t bytes_remaining = read_limit - bytes_read;
2020-10-20 15:21:00 +02:00
const size_t request_len = (bytes_remaining < BUFFER_SIZE) ? ((size_t)bytes_remaining) : BUFFER_SIZE;
2020-10-14 21:55:39 +02:00
const size_t count = fread(buffer, sizeof(uint8_t), request_len, file_in);
if (count > 0U)
{
bytes_read += count;
2021-04-02 16:37:19 +02:00
if ((status = slunkcrypt_inplace(ctx, buffer, count)) != SLUNKCRYPT_SUCCESS)
{
FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to decrypt data!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
blake2s_update(&blake2s_state, buffer, count);
2020-10-14 21:55:39 +02:00
if (fwrite(buffer, sizeof(uint8_t), count, file_out) < count)
{
2020-10-13 15:04:59 +02:00
FPUTS(T("failed!\n\nI/O error: Failed to write decrypted data!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
}
2020-10-14 21:55:39 +02:00
if (count < request_len)
{
break; /*EOF*/
}
if (!(++refresh_cycles & 0x1F))
2020-10-13 15:04:59 +02:00
{
const clock_t clk_now = clock();
if ((clk_now < clk_update) || (clk_now - clk_update > UPDATE_INTERVAL))
{
FPRINTF(stderr, T("\b\b\b\b\b\b\b%5.1f%% "), (bytes_read / ((double)read_limit)) * 100.0);
fflush(stderr);
clk_update = clk_now;
}
2020-10-13 15:04:59 +02:00
}
}
2020-10-14 21:55:39 +02:00
if (ferror(file_in))
{
FPUTS(T("\n\nI/O error: Failed to read input data!\n\n"), stderr);
goto clean_up;
}
2020-12-01 02:24:35 +01:00
if (bytes_read != read_limit)
{
2020-10-13 15:04:59 +02:00
FPUTS(T("\n\nI/O error: Input file could not be fully read!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
2020-12-01 02:24:35 +01:00
if (fread(buffer, sizeof(uint8_t), sizeof(uint64_t), file_in) < sizeof(uint64_t))
{
FPUTS(T("\n\nI/O error: Failed to read final block!\n\n"), stderr);
goto clean_up;
}
2021-04-02 16:37:19 +02:00
if ((status = slunkcrypt_inplace(ctx, buffer, sizeof(uint64_t))) != SLUNKCRYPT_SUCCESS)
2020-12-01 02:24:35 +01:00
{
FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to decrypt data!\n\n"), stderr);
goto clean_up;
}
const size_t padding = GET_LOWBITS(buffer[sizeof(uint64_t) - 1U]) + 1U;
assert(padding && (padding <= sizeof(uint64_t)));
2020-12-01 02:24:35 +01:00
if (padding != sizeof(uint64_t))
{
const size_t count = sizeof(uint64_t) - padding;
if (fwrite(buffer, sizeof(uint8_t), count, file_out) < count)
{
FPUTS(T("failed!\n\nI/O error: Failed to write decrypted data!\n\n"), stderr);
goto clean_up;
}
blake2s_update(&blake2s_state, buffer, count);
2020-12-01 02:24:35 +01:00
}
const uint64_t checksum_actual = blake2s_final(&blake2s_state);
2020-10-13 15:04:59 +02:00
uint8_t checksum_buffer[sizeof(uint64_t)];
if (fread(checksum_buffer, sizeof(uint8_t), sizeof(uint64_t), file_in) < sizeof(uint64_t))
{
FPUTS(T("\n\nI/O error: Failed to read the checksum!\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
2021-04-02 16:37:19 +02:00
if ((status = slunkcrypt_inplace(ctx, checksum_buffer, sizeof(uint64_t))) != SLUNKCRYPT_SUCCESS)
2020-10-20 22:13:39 +02:00
{
FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nSlunkCrypt error: Failed to decrypt checksum!\n\n"), stderr);
2020-12-01 02:24:35 +01:00
goto clean_up;
2020-10-20 22:13:39 +02:00
}
FPRINTF(stderr, T("\b\b\b\b\b\b\b%5.1f%%\n\n"), 100.0);
fflush(stderr);
const uint64_t checksum_stored = load_ui64(checksum_buffer);
if (checksum_actual != checksum_stored)
{
FPRINTF(stderr, T("Error: Checksum mismatch detected! [expected: 0x%016") T(PRIX64) T(", actual: 0x%016") T(PRIX64) T("]\n\n"), checksum_stored, checksum_actual);
2020-10-13 15:37:40 +02:00
FPUTS(T("Wrong passphrase or corrupted file?\n\n"), stderr);
2020-10-13 00:43:57 +02:00
goto clean_up;
}
2020-10-20 22:13:39 +02:00
result = EXIT_SUCCESS;
2020-10-13 00:43:57 +02:00
FPUTS(T("Checksum is correct.\n\n"), stderr);
2020-10-13 00:43:57 +02:00
fflush(stderr);
clean_up:
if (ctx)
{
slunkcrypt_free(ctx);
2020-10-13 00:43:57 +02:00
}
2020-10-14 21:55:39 +02:00
if (file_out)
2020-10-13 00:43:57 +02:00
{
2020-10-14 21:55:39 +02:00
fclose(file_out);
if ((result != EXIT_SUCCESS) && remove_incomplete_files())
{
if (REMOVE(output_path))
{
FPUTS(T("Warning: Failed to remove incomplete output file!\n\n"), stderr);
}
}
2020-10-13 00:43:57 +02:00
}
2020-10-14 21:55:39 +02:00
if (file_in)
2020-10-13 00:43:57 +02:00
{
2020-10-14 21:55:39 +02:00
fclose(file_in);
}
slunkcrypt_bzero(buffer, BUFFER_SIZE);
slunkcrypt_bzero(checksum_buffer, sizeof(uint64_t));
slunkcrypt_bzero(&blake2s_state, sizeof(blake2s_t));
slunkcrypt_bzero(&nonce, sizeof(uint64_t));
slunkcrypt_bzero((void*)&checksum_stored, sizeof(uint64_t));
slunkcrypt_bzero((void*)&checksum_actual, sizeof(uint64_t));
2020-10-13 00:43:57 +02:00
return result;
}
2020-10-20 15:21:00 +02:00
// ==========================================================================
// Self-test
// ==========================================================================
static int run_test_case(const char *const message, const uint64_t nonce, const uint64_t checksum_message, const uint64_t checksum_expected)
2020-10-14 13:14:47 +02:00
{
static const char* const TEST_PASSPHRASE = "OrpheanBeh0lderScry!Doubt";
2020-10-15 21:56:36 +02:00
2020-10-20 22:13:39 +02:00
int status, result = EXIT_FAILURE;
2020-10-15 21:56:36 +02:00
const size_t length = strlen(message) + 1U;
slunkcrypt_t ctx = SLUNKCRYPT_NULL;
2020-10-14 13:14:47 +02:00
2020-10-15 21:56:36 +02:00
char* const text_temp = strdup(message);
2020-10-14 13:14:47 +02:00
if (!text_temp)
{
2020-10-15 23:11:07 +02:00
FPUTS(T("\n\nWhoops: Failed to allocate text buffer!\n\n"), stderr);
2020-10-14 13:14:47 +02:00
goto clean_up;
}
const uint64_t checksum_original = blake2s_compute((uint8_t*)text_temp, length);
if (checksum_original != checksum_message)
2020-10-24 15:06:12 +02:00
{
FPRINTF(stderr, T("\n\nWhoops: Checksum mismatch detected! [expected: 0x%016") T(PRIX64) T(", actual: 0x%016") T(PRIX64) T("]\n\n"), checksum_message, checksum_original);
2020-10-24 15:06:12 +02:00
goto clean_up;
}
ctx = slunkcrypt_alloc(nonce, (const uint8_t*)TEST_PASSPHRASE, strlen(TEST_PASSPHRASE), SLUNKCRYPT_ENCRYPT);
2020-10-14 17:57:40 +02:00
if (!ctx)
2020-10-14 13:14:47 +02:00
{
FPUTS(g_slunkcrypt_abort_flag ? T("\n\nProcess interrupted!\n\n") : T("\n\nWhoops: Failed to initialize encoder!\n\n"), stderr);
2020-10-14 13:14:47 +02:00
goto clean_up;
}
2021-04-02 16:37:19 +02:00
status = slunkcrypt_inplace(ctx, (uint8_t*)text_temp, length);
if (status != SLUNKCRYPT_SUCCESS)
2020-10-14 13:14:47 +02:00
{
FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nWhoops: Failed to encrypt the message!\n\n"), stderr);
2020-10-14 13:14:47 +02:00
goto clean_up;
}
2020-10-15 21:56:36 +02:00
if (strncmp(message, text_temp, length) == 0)
2020-10-14 13:14:47 +02:00
{
2020-10-15 23:11:07 +02:00
FPUTS(T("\n\nWhoops: Encrypted message equals the original message!\n\n"), stderr);
2020-10-14 13:14:47 +02:00
goto clean_up;
}
const uint64_t checksum_encrypted = blake2s_compute((uint8_t*)text_temp, length);
if (checksum_encrypted != checksum_expected)
{
FPRINTF(stderr, T("\n\nWhoops: Checksum mismatch detected! [expected: 0x%016") T(PRIX64) T(", actual: 0x%016") T(PRIX64) T("]\n\n"), checksum_expected, checksum_encrypted);
goto clean_up;
}
status = slunkcrypt_reset(ctx, nonce, (const uint8_t*)TEST_PASSPHRASE, strlen(TEST_PASSPHRASE), SLUNKCRYPT_DECRYPT);
if (status != SLUNKCRYPT_SUCCESS)
2020-10-14 13:14:47 +02:00
{
FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nWhoops: Failed to initialize decoder!\n\n"), stderr);
2020-10-14 13:14:47 +02:00
goto clean_up;
}
2021-04-02 16:37:19 +02:00
status = slunkcrypt_inplace(ctx, (uint8_t*)text_temp, length);
if (status != SLUNKCRYPT_SUCCESS)
2020-10-14 13:14:47 +02:00
{
FPUTS((status == SLUNKCRYPT_ABORTED) ? T("\n\nProcess interrupted!\n\n") : T("\n\nWhoops: Failed to decrypt the message!\n\n"), stderr);
2020-10-14 13:14:47 +02:00
goto clean_up;
}
if (memcmp(message, text_temp, length * sizeof(char)) != 0)
2020-10-14 13:14:47 +02:00
{
2020-10-15 23:11:07 +02:00
FPUTS(T("\n\nWhoops: Decrypted message does *not* match the original message!\n\n"), stderr);
2020-10-14 13:14:47 +02:00
goto clean_up;
}
const uint64_t checksum_decrypted = blake2s_compute((uint8_t*)text_temp, length);
if (checksum_decrypted != checksum_original)
2020-10-24 15:06:12 +02:00
{
FPRINTF(stderr, T("\n\nWhoops: Checksum mismatch detected! [expected: 0x%016") T(PRIX64) T(", actual: 0x%016") T(PRIX64) T("]\n\n"), checksum_original, checksum_decrypted);
2020-10-24 15:06:12 +02:00
goto clean_up;
}
2020-10-20 22:13:39 +02:00
result = EXIT_SUCCESS;
2020-10-14 13:14:47 +02:00
clean_up:
2020-10-14 17:57:40 +02:00
if (ctx)
2020-10-14 13:14:47 +02:00
{
slunkcrypt_free(ctx);
2020-10-14 13:14:47 +02:00
}
if (text_temp)
{
slunkcrypt_bzero(text_temp, strlen(text_temp));
2020-10-14 13:14:47 +02:00
free(text_temp);
}
return result;
}
2020-10-20 15:21:00 +02:00
static int run_self_test(void)
2020-10-15 21:56:36 +02:00
{
static const uint64_t TEST_NONCE[] = { 0x243F6A8885A308D3, 0x13198A2E03707344 };
const struct
{
const char* text;
uint64_t check_orig, check_encr[2U];
}
TEST_STAGE[] =
{
{ TEST_DATA_0, TEST_CHCK_ORIG_0, { TEST_CHCK_ENCR_0[0U], TEST_CHCK_ENCR_0[1U] } },
{ TEST_DATA_1, TEST_CHCK_ORIG_1, { TEST_CHCK_ENCR_1[0U], TEST_CHCK_ENCR_1[1U] } },
{ TEST_DATA_2, TEST_CHCK_ORIG_2, { TEST_CHCK_ENCR_2[0U], TEST_CHCK_ENCR_2[1U] } },
{ TEST_DATA_3, TEST_CHCK_ORIG_3, { TEST_CHCK_ENCR_3[0U], TEST_CHCK_ENCR_3[1U] } },
};
2021-04-08 01:58:54 +02:00
const size_t total = ARRAY_SIZE(TEST_NONCE) * ARRAY_SIZE(TEST_STAGE);
2021-04-08 01:58:54 +02:00
FPRINTF(stderr, T("Self-test is in progress, please be patient... stage %u/%u "), 0U, (unsigned)total);
fflush(stderr);
2020-10-15 21:56:36 +02:00
for (size_t i = 0U, count = 0U; i < ARRAY_SIZE(TEST_STAGE); ++i)
2020-10-15 21:56:36 +02:00
{
for (size_t j = 0U; j < ARRAY_SIZE(TEST_NONCE); ++j)
2020-10-15 21:56:36 +02:00
{
FPRINTF(stderr, T("\b\b\b\b%u/%u "), (unsigned)++count, (unsigned)total);
fflush(stderr);
if (run_test_case(TEST_STAGE[i].text, TEST_NONCE[j], TEST_STAGE[i].check_orig, TEST_STAGE[i].check_encr[j]) != EXIT_SUCCESS)
{
return EXIT_FAILURE;
}
2020-10-15 21:56:36 +02:00
}
}
2021-04-08 01:58:54 +02:00
FPRINTF(stderr, T("\b\b\b\b%u/%u\n\nCompleted successfully.\n\n"), (unsigned)total, (unsigned)total);
2020-10-16 18:07:45 +02:00
fflush(stderr);
2020-10-20 22:13:39 +02:00
return EXIT_SUCCESS;
2020-10-15 21:56:36 +02:00
}
2020-10-20 15:21:00 +02:00
// ==========================================================================
// Main function
// ==========================================================================
2020-10-20 15:21:00 +02:00
int MAIN(const int argc, CHR *const argv[])
{
2020-10-13 19:33:01 +02:00
init_terminal();
2020-10-20 19:13:11 +02:00
setup_signal_handler(SIGINT, sigint_handler);
2020-10-20 22:13:39 +02:00
int result = EXIT_FAILURE;
char *passphrase_buffer = NULL;
FPRINTF(stderr, T("SlunkCrypt Utility (%") T(PRIstr) T("-%") T(PRIstr) T("), by LoRd_MuldeR <MuldeR2@GMX.de>\n"), OS_TYPE, CPU_ARCH);
2020-10-20 15:21:00 +02:00
FPRINTF(stderr, T("Using libSlunkCrypt v%u.%u.%u [%") T(PRIstr) T("]\n\n"), SLUNKCRYPT_VERSION_MAJOR, SLUNKCRYPT_VERSION_MINOR, SLUNKCRYPT_VERSION_PATCH, SLUNKCRYPT_BUILD);
2020-10-13 19:33:01 +02:00
2020-10-20 15:21:00 +02:00
/* ----------------------------------------------------- */
/* Parse arguments */
/* ----------------------------------------------------- */
2020-10-20 22:13:39 +02:00
if (argc < 2)
2020-10-14 13:14:47 +02:00
{
2020-10-20 15:21:00 +02:00
FPRINTF(stderr, T("Error: Nothing to do. Please type '%") T(PRISTR) T(" --help' for details!\n\n"), get_file_name(argv[0U]));
goto clean_up;
2020-10-20 22:13:39 +02:00
}
const int slunk_mode = parse_slunk_mode(argv[1U]);
switch (slunk_mode)
2020-10-20 22:13:39 +02:00
{
case MODE_HELP:
2020-10-20 15:21:00 +02:00
print_manpage(get_file_name(argv[0U]));
case MODE_VERS:
result = EXIT_SUCCESS;
goto clean_up;
case MODE_ENCR:
case MODE_DECR:
break; /*fallthrough*/
case MODE_PASS:
result = generate_passphrase((argc > 2) ? STRTOUL(argv[2U]) : DFLT_PWDLEN_LENGTH);
goto clean_up;
case MODE_TEST:
result = run_self_test();
goto clean_up;
default:
FPRINTF(stderr, T("Error: The specified command \"%") T(PRISTR) T("\" is unknown!\n\n"), argv[1U]);
goto clean_up;
2020-10-14 13:14:47 +02:00
}
if (argc < 4)
{
2020-10-20 15:21:00 +02:00
FPRINTF(stderr, T("Error: Required argument is missing. Please type '%") T(PRISTR) T(" --help' for details!\n\n"), get_file_name(argv[0U]));
goto clean_up;
}
const CHR *const passphrase = PW_FROM_ENV ? GETENV(ENV_PASSWORD) : argv[2U];
const CHR *const input_file = argv[PW_FROM_ENV ? 2U : 3U], *const output_file = argv[PW_FROM_ENV ? 3U : 4U];
if ((!passphrase) || (!passphrase[0U]))
{
FPUTS(T("Error: The passphrase must be specified, directly or indirectly!\n\n"), stderr);
goto clean_up;
}
if ((!PW_FROM_ENV) && STRICMP(passphrase, T("-")))
{
if ((!STARTS_WITH(passphrase, PREFIX_PASS)) && (!STARTS_WITH(passphrase, PREFIX_FILE)))
{
FPRINTF(stderr, T("Error: The passphrase must start with a '%") T(PRISTR) T("' or '%") T(PRISTR) T("' prefix!\n\n"), PREFIX_PASS, PREFIX_FILE);
goto clean_up;
}
}
2020-10-13 19:33:01 +02:00
if ((!input_file[0U]) || (!output_file[0U]))
{
FPUTS(T("Error: The input file and/or output file must not be empty!\n\n"), stderr);
goto clean_up;
2020-10-13 19:33:01 +02:00
}
2020-10-20 15:21:00 +02:00
/* ----------------------------------------------------- */
/* Initialize passphrase */
/* ----------------------------------------------------- */
if (!(passphrase_buffer = PW_FROM_ENV ? copy_passphrase(passphrase) :
(STARTS_WITH(passphrase, PREFIX_PASS) ? copy_passphrase(passphrase + STRLEN(PREFIX_PASS)) :
(STARTS_WITH(passphrase, PREFIX_FILE) ? read_passphrase(passphrase + STRLEN(PREFIX_FILE)) : read_passphrase(T("-"))))))
2020-10-13 19:33:01 +02:00
{
goto clean_up;
2020-10-13 19:33:01 +02:00
}
slunkcrypt_bzero((CHR*)passphrase, STRLEN(passphrase) * sizeof(CHR));
2020-10-20 15:21:00 +02:00
const size_t passphrase_len = strlen(passphrase_buffer);
if (passphrase_len < SLUNKCRYPT_PWDLEN_MIN)
{
FPRINTF(stderr, T("Error: Passphrase must be at least %u characters in length!\n\n"), (unsigned)SLUNKCRYPT_PWDLEN_MIN);
goto clean_up;
2020-10-20 15:21:00 +02:00
}
else if (passphrase_len > SLUNKCRYPT_PWDLEN_MAX)
{
FPRINTF(stderr, T("Error: Passphrase must be at most %u characters in length!\n\n"), (unsigned)SLUNKCRYPT_PWDLEN_MAX);
goto clean_up;
2020-10-20 15:21:00 +02:00
}
if (passphrase_len < RCMD_PWDLEN_LENGTH)
{
FPRINTF(stderr, T("Warning: Using a *short* passphrase; a length of %u characters or more is recommended!\n\n"), (unsigned)RCMD_PWDLEN_LENGTH);
}
else if (weak_passphrase(passphrase_buffer))
{
FPUTS(T("Warning: Using a *weak* passphrase; a mix of upper-case letters, lower-case letters, digits and 'special' characters is recommended!\n\n"), stderr);
}
2020-10-20 15:21:00 +02:00
/* ----------------------------------------------------- */
/* Encrypt or decrypt */
/* ----------------------------------------------------- */
const clock_t clk_start = clock();
switch (slunk_mode)
{
case MODE_ENCR:
result = encrypt(passphrase_buffer, input_file, output_file);
2020-10-20 15:21:00 +02:00
break;
case MODE_DECR:
result = decrypt(passphrase_buffer, input_file, output_file);
2020-10-20 15:21:00 +02:00
break;
default:
2020-10-20 22:13:39 +02:00
FPUTS(T("Unexpected mode encountered!\n\n"), stderr);
}
if (!g_slunkcrypt_abort_flag)
2020-10-14 21:55:39 +02:00
{
FPUTS(T("--------\n\n"), stderr);
fflush(stderr);
const clock_t clk_end = clock();
FPRINTF(stderr, T("Operation completed after %.1f seconds.\n\n"), (clk_end - clk_start) / ((double)CLOCKS_PER_SEC));
}
2020-10-13 19:33:01 +02:00
2020-10-20 15:21:00 +02:00
/* ----------------------------------------------------- */
/* Final clean-up */
/* ----------------------------------------------------- */
clean_up:
2020-10-13 19:33:01 +02:00
if (passphrase_buffer)
2020-10-13 19:50:29 +02:00
{
slunkcrypt_bzero(passphrase_buffer, strlen(passphrase_buffer));
free(passphrase_buffer);
2020-10-13 19:50:29 +02:00
}
2020-10-15 22:41:28 +02:00
return result;
}
#if defined(_WIN32) && defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
void __wgetmainargs(int*, wchar_t***, wchar_t***, int, int*);
int main()
{
wchar_t** enpv, ** argv;
int argc, si = 0;
__wgetmainargs(&argc, &argv, &enpv, 1, &si);
return wmain(argc, argv);
}
#endif