purr-c/encrypt.c

295 lines
8.2 KiB
C

/*
* The functions in this file are responsible for most of the special
* functionality of the purr/meow utilities, which is encrypted pastes
* that the server can't access. They can be configured with the RANDOMIZE_IV,
* ENCODE_BASE_64 and DECODE_BASE_64 macros from purr.h.
*
* RANDOMIZE_IV: IV used for AES encryption is randomized or all zero.
*
* ENCODE_BASE_64: the binary data generated by the encryption process is
* encoded as base64.
*
* DECODE_BASE_64: the data returned by the server is decoded, assuming its
* encoding is base64.
*
* Having all these macros defined is the default mode of operation, and what's
* expected by the other PurritoBin clients.
*/
#define _DEFAULT_SOURCE /* getentropy */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <sys/random.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <bearssl.h>
#include "purr.h"
#include "mmap_file.h"
#define PEM_HEADER "TOP-SECRET"
// 5 slashes + "BEGIN " + <header> + 5 slashes + LR
#define PEM_HEADER_BEGIN_SIZE (sizeof PEM_HEADER - 1 + 5 + sizeof("BEGIN ") - 1 + 5 + 1)
// LR + 5 slashes + "END " + <header> + 5 slashes
#define PEM_HEADER_END_SIZE (1 + sizeof PEM_HEADER - 1 + 5 + sizeof("END ") - 1 + 5)
/*
* This function takes an mmap_file struct, and returns an encrypted mmap_file from it.
* Args:
* file: mmap_file for the input file
* keyp: will receive the newly generated random key
* ivp: will receive the newly generated random IV (if enabled in purr.h)
*/
struct mmap_file encrypt_mmap(struct mmap_file file, uint8_t **keyp, uint8_t **ivp)
{
off_t file_size = file.size;
ssize_t blocks = file_size / br_aes_big_BLOCK_SIZE;
if (blocks * br_aes_big_BLOCK_SIZE < file_size) blocks++;
file_size = blocks * br_aes_big_BLOCK_SIZE;
ssize_t padding = file_size - file.size;
struct mmap_file rv = {.size = file_size, .prot = PROT_MEM, .flags = MAP_MEM};
uint8_t *key = calloc(KEY_LEN, 1);
uint8_t *iv = calloc(IV_LEN, 1);
uint8_t iv_throwaway[IV_LEN] = { 0 };
if (key == NULL || iv == NULL) {
perror("allocation failure");
return rv;
}
if (getentropy(key, KEY_LEN) < 0) {
perror("getentropy()");
return rv;
}
#ifdef RANDOMIZE_IV
if (getentropy(iv, IV_LEN) < 0) {
perror("getentropy()");
return rv;
}
memcpy(iv_throwaway, iv, IV_LEN);
#endif /* RANDOMIZE_IV */
if (!allocate_mmap(&rv)) {
return rv;
}
memcpy(rv.data, file.data, file.size);
// PKCS7 padding
memset(rv.data + file.size, padding, padding);
br_aes_big_cbcenc_keys br = { 0 };
br_aes_big_cbcenc_init(&br, key, KEY_LEN);
br_aes_big_cbcenc_run(&br, iv_throwaway, rv.data, file_size);
#ifdef ENCODE_BASE_64
struct mmap_file rv_64 = {.prot = PROT_MEM, .flags = MAP_MEM};
// default mode -> PEM object with 76 characters per line
size_t encsize = br_pem_encode(NULL, rv.data, rv.size, PEM_HEADER, 0);
char *pem_enc = malloc(encsize + 1);
if (pem_enc == NULL) {
return rv_64;
}
br_pem_encode(pem_enc, rv.data, rv.size, PEM_HEADER, 0);
int newlines = 0;
char *new_line_search = pem_enc;
while((new_line_search = strchr(new_line_search, '\n'))) {
new_line_search++;
newlines++;
}
if (newlines < 2) {
fputs("encoding error!", stderr);
return rv_64;
} else {
newlines -= 2;
}
rv_64.size = encsize - newlines - PEM_HEADER_BEGIN_SIZE - PEM_HEADER_END_SIZE;
if (!allocate_mmap(&rv_64)) {
return rv_64;
}
for (int i = 0; i < newlines; i++) {
memcpy(rv_64.data + i * 76,
pem_enc + PEM_HEADER_BEGIN_SIZE + i * (76 + 1),
(i == newlines) ? rv_64.size % 76 : 76);
}
free(pem_enc);
free_mmap(&rv);
rv = rv_64;
#endif /* ENCODE_BASE_64 */
free_mmap(&file);
// pass pointers to caller
*keyp = key;
*ivp = iv;
return rv;
}
#ifdef DECODE_BASE_64
struct b64_decoding_struct {
uint8_t *data;
size_t n, size;
bool dead;
};
enum b64_decoding_status {
B64_BEGIN,
B64_MAIN,
B64_END
};
static void push_decoded_b64(void *dest_ctx, const void *src, size_t len)
{
struct b64_decoding_struct *ct = dest_ctx;
if (ct->dead) return;
if (ct->n + len > ct->size) {
if (ct->size == 0) ct->size = 64;
while (ct->n + len > (ct->size *= 2));
ct->data = realloc(ct->data, ct->size);
if (ct->data == NULL) {
perror("realloc()");
ct->dead = true;
return;
}
}
memcpy(ct->data + ct->n, src, len);
ct->n += len;
}
#endif /* DECODE_BASE_64 */
/*
* This function takes an mmap_file struct, and returns a decrypted buffer from it.
* Args:
* file: mmap_file for the input file
* keyp: the key used to encrypt the paste
* ivp: the iv used to encrypt the paste
*/
struct mmap_file decrypt_mmap(struct mmap_file file, const uint8_t *key, const uint8_t *iv)
{
struct mmap_file rv = {.size = file.offset, .prot = PROT_MEM, .flags = MAP_MEM};
#ifdef DECODE_BASE_64
br_pem_decoder_context pem;
br_pem_decoder_init(&pem);
struct b64_decoding_struct dec = { 0 };
br_pem_decoder_setdest(&pem, push_decoded_b64, &dec);
const char *b64_begin_header = "-----BEGIN TOP-SECRET-----\n";
const size_t beginlen_c = strlen(b64_begin_header);
size_t beginlen = beginlen_c;
static const char *b64_end_header = "\n-----END TOP-SECRET-----";
const size_t endlen_c = strlen(b64_end_header);
size_t endlen = endlen_c;
size_t wlen = file.offset;
enum b64_decoding_status dec_step = B64_BEGIN;
while (1) {
bool should_break = false;
switch (dec_step) {
size_t pushed;
case B64_BEGIN:
pushed = br_pem_decoder_push(
&pem,
b64_begin_header + (beginlen_c - beginlen),
beginlen);
beginlen -= pushed;
if (beginlen == 0) dec_step = B64_MAIN;
break;
case B64_MAIN:
pushed = br_pem_decoder_push(
&pem,
file.data + (file.offset - wlen),
wlen);
wlen -= pushed;
if (wlen == 0) dec_step = B64_END;
break;
case B64_END:
pushed = br_pem_decoder_push(
&pem,
b64_end_header + (endlen_c - endlen),
endlen);
endlen -= pushed;
if (endlen == 0) should_break = true;
break;
}
switch (br_pem_decoder_event(&pem)) {
case BR_PEM_ERROR:
fputs("base64 decoding error!\n", stderr);
dec.dead = true;
// fall through
case BR_PEM_END_OBJ:
should_break = true;
// fall through
case BR_PEM_BEGIN_OBJ:
case 0:
break;
}
if (should_break) break;
}
if (dec.dead) {
return rv;
}
rv.size = dec.n;
#endif /* DECODE_BASE_64 */
if (rv.size % br_aes_big_BLOCK_SIZE) {
fputs("bad input size!\n", stderr);
return rv;
}
if (!allocate_mmap(&rv)) {
return rv;
}
#ifdef DECODE_BASE_64
write_into_mmap(&rv, dec.data, rv.size);
free(dec.data);
#else
write_into_mmap(&rv, file.data, rv.size);
#endif /* DECODE_BASE_64 */
free_mmap(&file);
uint8_t iv_throwaway[IV_LEN];
memcpy(iv_throwaway, iv, IV_LEN);
br_aes_big_cbcdec_keys br = { 0 };
br_aes_big_cbcdec_init(&br, key, KEY_LEN);
br_aes_big_cbcdec_run(&br, iv_throwaway, rv.data, rv.offset);
// remove padding - only knows PKCS7
int padding = rv.data[rv.size - 1];
if (padding < br_aes_big_BLOCK_SIZE) {
// data might contain padding
bool found_padding = true;
for (int i = 0; i < padding; i++) {
if (rv.data[rv.offset - 1 - i] != padding) {
found_padding = false;
}
}
if (found_padding) {
rv.offset -= padding;
}
}
return rv;
}