From 6079530e1de6126cc07793725139ecaa1131089b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Fri, 9 Apr 2021 22:35:10 +0300 Subject: [PATCH] Add UI to puer --- puer.c | 216 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 180 insertions(+), 36 deletions(-) diff --git a/puer.c b/puer.c index 0179959..3f9880a 100644 --- a/puer.c +++ b/puer.c @@ -10,6 +10,10 @@ #include #include +// Adjusting this will render the file format incompatible +// The minimum possible buffer size is 64 +unsigned char workbuf[8 * 1024 * 1024]; + void xxtea128(uint32_t const key[4], uint32_t block[4]) { // Encryption half of the XXTEA algorithm, with block size limited // to 128 bits or 4 words. This avoids all the weaknesses that @@ -268,7 +272,6 @@ void hmac(unsigned char output[32], unsigned char key[], size_t keylen, unsigned finalize_hash(&state, output); } -unsigned char workbuf[8 * 1024 * 1024]; #define KDF_ROUNDS (sizeof(workbuf) / 32) void kdf(unsigned char key[16], unsigned char salt[32], unsigned char passphrase[], size_t passphraselen) { @@ -540,54 +543,195 @@ ssize_t passphrase_prompt(unsigned char *passphrase, size_t size, const char *pr return index - 1; } -int main(void) { - unsigned char salt[32]; - if (getentropy(salt, 32) != 0) { - perror("getentropy"); +void usage(char *name) { + fprintf(stderr, "Usage: %s -d | -e [-f]\n\n", name); + fprintf(stderr, "-d Decrypt\n"); + fprintf(stderr, "-e Encrypt\n"); + fprintf(stderr, "-f Force output to terminal\n"); +} + +int main(int argc, char *argv[]) { + bool encrypting = false; + bool decrypting = false; + bool force = false; + + int opt; + while ((opt = getopt(argc, argv, "def")) != -1) { + switch (opt) { + case 'd': + decrypting = true; + break; + case 'e': + encrypting = true; + break; + case 'f': + force = true; + break; + default: + usage(argv[0]); + exit(1); + } } - unsigned char passphrase[128] = {0}; + if ((!encrypting && !decrypting) || (encrypting && decrypting)) { + usage(argv[0]); + exit(1); + } + + FILE *infile = stdin; + FILE *outfile = stdout; + + if (encrypting && !force && isatty(fileno(outfile))) { + fprintf(stderr, "Refusing to print encrypted (binary) data to terminal. Use -f to force output.\n"); + exit(1); + } + + // Get the salt for key derivation + unsigned char salt[32]; + if (encrypting) { + // Generate salt randomly + if (getentropy(salt, 32) != 0) { + perror("Could not generate salt (getentropy)"); + exit(1); + } + // Write salt to the beginning of the file + if (fwrite(&salt, 32, 1, outfile) != 1) { + fprintf(stderr, "Could not write salt\n"); + exit(1); + } + } else { + // Read salt from the beginning of the file + if (fread(&salt, 32, 1, infile) != 1) { + fprintf(stderr, "Could not read salt\n"); + exit(1); + } + } + + // Read passphrase + unsigned char passphrase[128]; ssize_t passphrase_len = passphrase_prompt(passphrase, sizeof(passphrase), "passphrase: "); if (passphrase_len == -1) { explicit_bzero(passphrase, sizeof(passphrase)); exit(1); } + if (encrypting) { + // Have the user confirm the passphrase if encrypting, to avoid losing data + unsigned char confirm[128]; + ssize_t confirm_len = passphrase_prompt(confirm, sizeof(confirm), "confirm passphrase: "); + if (confirm_len == -1) { + explicit_bzero(passphrase, sizeof(passphrase)); + explicit_bzero(confirm, sizeof(confirm)); + exit(1); + } + + if (confirm_len != passphrase_len || memcmp(passphrase, confirm, passphrase_len) != 0) { + fprintf(stderr, "Passphrases do not match\n"); + explicit_bzero(passphrase, sizeof(passphrase)); + explicit_bzero(confirm, sizeof(confirm)); + exit(1); + } + + explicit_bzero(confirm, sizeof(confirm)); + } + + // Derive key unsigned char key[16]; kdf(key, salt, passphrase, passphrase_len); - for (size_t i = 0; i < 16; i++) { - printf("%02hhx ", key[i]); - } - printf("\n\n"); + explicit_bzero(passphrase, sizeof(passphrase)); - char *message = "Syökää parsaa ja palvokaa saatanaa"; - unsigned char buf[64]; - memset(buf, 0xab, sizeof(buf)); - strcpy((char*)buf, message); - for (size_t i = 0; i < sizeof(buf); i++) { - printf("%02hhx ", buf[i]); - if (i % 16 == 15) printf("\n"); - } - printf("\n"); + uint64_t messageindex = 0; + if (encrypting) { + for (;;) { + // Leave space for the MAC in the work buffer + size_t bytes = fread(workbuf, 1, sizeof(workbuf) - 16, infile); + if (bytes == 0 && ferror(infile)) { + perror("Failure reading"); + explicit_bzero(key, sizeof(key)); + exit(1); + } - ccm_encrypt(key, 25, buf, strlen(message), &buf[48]); - for (size_t i = 0; i < sizeof(buf); i++) { - printf("%02hhx ", buf[i]); - if (i % 16 == 15) printf("\n"); - } - printf("\n"); + // MAC is after the message + unsigned char *mac = &workbuf[bytes]; - memset(&buf[strlen(message)], 0x0f, 48-strlen(message)); - for (size_t i = 0; i < sizeof(buf); i++) { - printf("%02hhx ", buf[i]); - if (i % 16 == 15) printf("\n"); - } - printf("\n"); + ccm_encrypt(key, messageindex++, workbuf, bytes, mac); - bool auth = ccm_decrypt(key, 25, buf, strlen(message), &buf[48]); - printf("auth %s\n", auth ? "succeeded" : "failed"); - for (size_t i = 0; i < sizeof(buf); i++) { - printf("%02hhx ", buf[i]); - if (i % 16 == 15) printf("\n"); + size_t written = fwrite(workbuf, 1, bytes + 16, outfile); + if (written != bytes + 16) { + perror("Failure writing"); + explicit_bzero(key, sizeof(key)); + exit(1); + } + + // If this chunk was short, that means we're done + if (bytes < sizeof(workbuf) - 16) { + break; + } + + if (messageindex == 0) { + // We will not run into this, but I feel like it's cleaner to check + fprintf(stderr, "Chunk counter overflow\n"); + explicit_bzero(key, sizeof(key)); + exit(1); + } + } + } else { + for (;;) { + size_t bytes = fread(workbuf, 1, sizeof(workbuf), infile); + if (bytes == 0 && ferror(infile)) { + perror("Failure reading"); + explicit_bzero(key, sizeof(key)); + exit(1); + } else if (bytes < 16) { + fprintf(stderr, "Chunk too short. File likely corrupt.\n"); + explicit_bzero(key, sizeof(key)); + exit(1); + } + + // MAC is after the message + unsigned char *mac = &workbuf[bytes - 16]; + + bool auth = ccm_decrypt(key, messageindex++, workbuf, bytes - 16, mac); + + if (!auth) { + if (messageindex == 1) { + // First chunk + fprintf(stderr, "Authentication failed. Either the passphrase is wrong or the file is corrupt.\n"); + } else { + fprintf(stderr, "Authentication failed. The file is likely corrupt.\n"); + } + explicit_bzero(key, sizeof(key)); + exit(1); + } + + size_t written = fwrite(workbuf, 1, bytes - 16, outfile); + if (written != bytes - 16) { + perror("Failure writing"); + explicit_bzero(key, sizeof(key)); + exit(1); + } + + // If this chunk was short, that means we're done + if (bytes < sizeof(workbuf)) { + break; + } + + if (messageindex == 0) { + // We will not run into this, but I feel like it's cleaner to check + fprintf(stderr, "Chunk counter overflow\n"); + explicit_bzero(key, sizeof(key)); + exit(1); + } + } } + + // Remove the key from memory + explicit_bzero(key, sizeof(key)); + + if (fflush(stdout) != 0) { + perror("Failure writing"); + exit(1); + } + + return 0; }