432 lines
12 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
/* Internal */
#include <slunkcrypt.h>
2020-10-13 15:04:59 +02:00
#include "utils.h"
#include "crypt.h"
#include "selftest.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>
// ==========================================================================
// Constants
// ==========================================================================
#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;
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 keep_incomplete_files(void)
{
const CHR *const keep_files = GETENV(ENV_KEEPFILE);
if (keep_files)
{
return BOOLIFY(STRTOUL(keep_files));
}
return 0;
2020-10-13 00:43:57 +02:00
}
static void check_excess_arguments(const int argc, int maximum)
{
if (argc > maximum)
{
FPUTS(T("Warning: Excess command-line argument(s) will be ignored!\n\n"), stderr);
fflush(stderr);
}
}
2020-10-20 15:21:00 +02:00
static void sigint_handler(const int sig)
{
if (sig == SIGINT)
{
g_slunkcrypt_abort_flag = 1;
}
}
// ==========================================================================
// 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
fflush(stderr);
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:
check_excess_arguments(argc, 3);
result = generate_passphrase((argc > 2) ? STRTOUL(argv[2U]) : DFLT_PWDLEN_LENGTH);
goto clean_up;
case MODE_TEST:
check_excess_arguments(argc, 2);
result = run_selftest_routine();
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;
}
check_excess_arguments(argc, 5);
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);
}
fflush(stderr);
2020-10-20 15:21:00 +02:00
/* ----------------------------------------------------- */
/* Encrypt or decrypt */
/* ----------------------------------------------------- */
const clock_t clk_start = clock();
const int keep_incomplete = keep_incomplete_files();
switch (slunk_mode)
{
case MODE_ENCR:
result = encrypt(passphrase_buffer, input_file, output_file, keep_incomplete);
2020-10-20 15:21:00 +02:00
break;
case MODE_DECR:
result = decrypt(passphrase_buffer, input_file, output_file, keep_incomplete);
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
{
const clock_t clk_end = clock();
FPRINTF(stderr, T("--------\n\nOperation completed after %.1f seconds.\n\n"), (clk_end - clk_start) / ((double)CLOCKS_PER_SEC));
2020-10-14 21:55:39 +02:00
}
2020-10-13 19:33:01 +02:00
2020-10-20 15:21:00 +02:00
/* ----------------------------------------------------- */
/* Final clean-up */
/* ----------------------------------------------------- */
clean_up:
fflush(stderr);
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