891 lines
25 KiB
C
891 lines
25 KiB
C
#include <assert.h>
|
|
#include <fcntl.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
// Adjusting this will render the file format incompatible
|
|
// KDF_WORKFACTOR must be a power of two between 1 and 2^32
|
|
#define KDF_BLOCKSIZE 1024
|
|
#define KDF_WORKFACTOR (16 * 1024)
|
|
unsigned char workbuf[KDF_WORKFACTOR * KDF_BLOCKSIZE];
|
|
|
|
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
|
|
// Wikipedia knows of, since both depend on only running 6 rounds
|
|
// per block, and we will run 6 + 52//4 = 6 + 13 = 19
|
|
|
|
uint32_t roundconstant = 0;
|
|
for (unsigned round = 0; round < 19; round++) {
|
|
// This took a while to puzzle out since the original
|
|
// specification is a mess, and the mess is only added to
|
|
// by needing to support custom blockwidths.
|
|
//
|
|
// The algorithm is as follows:
|
|
//
|
|
// 1. Set the round constant (sum) to round * 0x9e3779b9
|
|
// (implemented by addition)
|
|
//
|
|
// 2. Create a reduced version of the round constant (e)
|
|
// which is the bits 3…2 of the round constant. The
|
|
// reduced version is needed for changing the pattern of
|
|
// key accesses, since key is only 4 words long
|
|
//
|
|
// 3. Go through each word in the block and derive its new
|
|
// value based on its current value (v[p]), the next (y)
|
|
// and the previous word (z), wrapping around the ends
|
|
// of the block as needed.
|
|
//
|
|
// The function for deriving the new value of a word is a
|
|
// xor of sums of xors, followed by an in-place addition.
|
|
// The first sum adds together combinations of the next and
|
|
// previous word, and the second sum adds together
|
|
// previous/next combined with a value dependant on the
|
|
// round constant. The key is also mixed into the word in
|
|
// the first xor of second sum. After this the result is
|
|
// added back into the original word.
|
|
//
|
|
// I have changed the operand order in the second xor of
|
|
// first add and in the second add. This is to keep the
|
|
// part dependant of previous word on the left and the part
|
|
// dependant on the next word on the right.
|
|
|
|
roundconstant += 0x9e3779b9;
|
|
uint32_t reduced = (roundconstant >> 2) & 3;
|
|
|
|
block[0] += ((block[3]>>5 ^ block[1]<<2) + (block[3]<<4 ^ block[1]>>3)) ^ ((key[reduced ^ 0] ^ block[3]) + (roundconstant ^ block[1]));
|
|
block[1] += ((block[0]>>5 ^ block[2]<<2) + (block[0]<<4 ^ block[2]>>3)) ^ ((key[reduced ^ 1] ^ block[0]) + (roundconstant ^ block[2]));
|
|
block[2] += ((block[1]>>5 ^ block[3]<<2) + (block[1]<<4 ^ block[3]>>3)) ^ ((key[reduced ^ 2] ^ block[1]) + (roundconstant ^ block[3]));
|
|
block[3] += ((block[2]>>5 ^ block[0]<<2) + (block[2]<<4 ^ block[0]>>3)) ^ ((key[reduced ^ 3] ^ block[2]) + (roundconstant ^ block[0]));
|
|
}
|
|
}
|
|
|
|
uint32_t bytes2word(unsigned char const bytes[4]) {
|
|
return (uint32_t)bytes[0] | (uint32_t)bytes[1]<<8 | (uint32_t)bytes[2]<<16 | (uint32_t)bytes[3]<<24;
|
|
}
|
|
|
|
void word2bytes(unsigned char *bytes, uint32_t word) {
|
|
bytes[0] = word;
|
|
bytes[1] = word>>8;
|
|
bytes[2] = word>>16;
|
|
bytes[3] = word>>24;
|
|
}
|
|
|
|
void block2words(uint32_t words[4], unsigned char const bytes[16]) {
|
|
words[0] = bytes2word(&bytes[0]);
|
|
words[1] = bytes2word(&bytes[4]);
|
|
words[2] = bytes2word(&bytes[8]);
|
|
words[3] = bytes2word(&bytes[12]);
|
|
}
|
|
|
|
void words2block(unsigned char bytes[16], uint32_t const words[4]) {
|
|
word2bytes(&bytes[0], words[0]);
|
|
word2bytes(&bytes[4], words[1]);
|
|
word2bytes(&bytes[8], words[2]);
|
|
word2bytes(&bytes[12], words[3]);
|
|
}
|
|
|
|
struct hashstate {
|
|
// A_n and B_n of the MDC-2 algorithm
|
|
uint32_t a[4];
|
|
uint32_t b[4];
|
|
// Buffer to hold data until next full block
|
|
unsigned char buffer[16];
|
|
size_t length;
|
|
// Counter that keeps tracks of how much data we've hashed
|
|
uint64_t totalbits;
|
|
};
|
|
|
|
void initialize_hash(struct hashstate *state) {
|
|
// Hash function is MDC-2 with xxtea128, which is nice since it
|
|
// gives us a 256 bit hash. The constants are based on binary
|
|
// expansion of the square root of two, A1 being the first 128 bits
|
|
// and B1 the next 128.
|
|
//
|
|
// If we treat A1 and B1 as 128bit little endian integers, they
|
|
// have the values:
|
|
//
|
|
// A1 = 6a09e667 f3bcc908 b2fb1366 ea957d3e
|
|
// A2 = 3adec175 12775099 da2f590b 0667322a
|
|
|
|
state->a[0] = 0xea957d3eUL;
|
|
state->a[1] = 0xb2fb1366UL;
|
|
state->a[2] = 0xf3bcc908UL;
|
|
state->a[3] = 0x6a09e667UL;
|
|
|
|
state->b[0] = 0x0667322aUL;
|
|
state->b[1] = 0xda2f590bUL;
|
|
state->b[2] = 0x12775099UL;
|
|
state->b[3] = 0x3adec175UL;
|
|
|
|
memset(state->buffer, 0, sizeof(state->buffer));
|
|
state->length = 0;
|
|
state->totalbits = 0;
|
|
}
|
|
|
|
void compress_hash(struct hashstate *state) {
|
|
assert(state->length == 16);
|
|
|
|
// M_i
|
|
uint32_t message[4];
|
|
block2words(message, state->buffer);
|
|
|
|
// V_i = M_i ^ E(M_i, A_i)
|
|
// Note: In this description A_i is the *key*, not the plaintext
|
|
uint32_t v[4];
|
|
memcpy(v, message, sizeof(v));
|
|
xxtea128(state->a, v);
|
|
v[0] ^= message[0];
|
|
v[1] ^= message[1];
|
|
v[2] ^= message[2];
|
|
v[3] ^= message[3];
|
|
|
|
// W_i = M_i ^ E(M_i, B_i);
|
|
uint32_t w[4];
|
|
memcpy(w, message, sizeof(w));
|
|
xxtea128(state->b, w);
|
|
w[0] ^= message[0];
|
|
w[1] ^= message[1];
|
|
w[2] ^= message[2];
|
|
w[3] ^= message[3];
|
|
|
|
// A_{i+1} = Vwi^L || W_i^R
|
|
state->a[0] = v[0];
|
|
state->a[1] = v[1];
|
|
state->a[2] = w[2];
|
|
state->a[3] = w[3];
|
|
|
|
// B_{i+1} = W_i^L || V_i^R
|
|
state->b[0] = w[0];
|
|
state->b[1] = w[1];
|
|
state->b[2] = v[2];
|
|
state->b[3] = v[3];
|
|
|
|
// Mark that we have consumed the buffer
|
|
state->length = 0;
|
|
}
|
|
|
|
void feed_hash(struct hashstate *state, unsigned char input[], size_t length) {
|
|
// Invariant: The buffer will be filled somewhere between 0 and 15
|
|
// when we enter this loop. This is because once it reaches 16, the
|
|
// hash compression function is executed.
|
|
for (size_t i = 0; i < length; i++) {
|
|
// Must not overflow the internat counter. In practice we will not
|
|
// hit this.
|
|
assert(state->totalbits <= UINT64_MAX - 8);
|
|
|
|
state->buffer[state->length++] = input[i];
|
|
state->totalbits += 8;
|
|
|
|
if (state->length == 16) {
|
|
compress_hash(state);
|
|
}
|
|
}
|
|
}
|
|
|
|
void finalize_hash(struct hashstate *state, unsigned char hash[32]) {
|
|
// Feed the padding. It consists of one-bit, followed by zero-bits,
|
|
// followed by the number of bits in the message as big-endian
|
|
// uint64. This is the same padding as in SHA-2.
|
|
|
|
// We can assume that this works due to the invariant that buffer
|
|
// fill when entering this function is between 0 and 15
|
|
state->buffer[state->length++] = 0x80;
|
|
|
|
while(state->length != 8) {
|
|
if (state->length == 16) {
|
|
compress_hash(state);
|
|
}
|
|
state->buffer[state->length++] = 0;
|
|
}
|
|
|
|
// Add the number of bits, and do one last compression
|
|
state->buffer[8] = state->totalbits >> 56;
|
|
state->buffer[9] = state->totalbits >> 48;
|
|
state->buffer[10] = state->totalbits >> 40;
|
|
state->buffer[11] = state->totalbits >> 32;
|
|
state->buffer[12] = state->totalbits >> 24;
|
|
state->buffer[13] = state->totalbits >> 16;
|
|
state->buffer[14] = state->totalbits >> 8;
|
|
state->buffer[15] = state->totalbits;
|
|
state->length += 8;
|
|
compress_hash(state);
|
|
|
|
// Extract the hash state
|
|
for (size_t i = 0; i < 4; i++) {
|
|
word2bytes(&hash[i*4], state->a[i]);
|
|
}
|
|
for (size_t i = 0; i < 4; i++) {
|
|
word2bytes(&hash[i*4 + 16], state->b[i]);
|
|
}
|
|
|
|
// Clear all of the hash state, in case there was sth important
|
|
// there
|
|
explicit_bzero(state, sizeof(struct hashstate));
|
|
}
|
|
|
|
void pbkdf2_1_block(unsigned char output[32], unsigned char passphrase[], size_t passphraselen, unsigned char salt[], size_t saltlen, uint32_t blockindex) {
|
|
// NOTE: This implementation is hardcoded to one round, as required
|
|
// by the MFcrypt (see Stronger Key Derivation Via Sequential
|
|
// Memory-hard Functions by Colin Percival) algorithm. This is not
|
|
// suitable as a general purpose password-based KDF.
|
|
|
|
// This is equivalent to
|
|
// F(Password, Salt, 1, i)
|
|
// = U_1
|
|
// = PRF(Password, Salt + INT_32_BE(i))
|
|
// We use HMAC-MDC2-XXTEA128 as our PRF
|
|
|
|
// The blocksize of the underlying hash function is 128 bits (16B)
|
|
// but HMAC is specified assuming that the hash function output (in
|
|
// our case 256 bits or 32B) fits in one block. As far as I can
|
|
// tell extending the key to be two blocks long is not a problem.
|
|
|
|
unsigned char padded_key[32];
|
|
if (passphraselen > 16) {
|
|
// We hash it even if it is shorter than our extended key
|
|
// length to avoid giving attacker any funny surfaces to
|
|
// play with at the interface of two blocks
|
|
struct hashstate state;
|
|
initialize_hash(&state);
|
|
feed_hash(&state, passphrase, passphraselen);
|
|
finalize_hash(&state, padded_key);
|
|
} else {
|
|
// Copy the key and zero-pad if necessary
|
|
memset(padded_key, 0, 32);
|
|
memcpy(padded_key, passphrase, passphraselen);
|
|
}
|
|
|
|
// Outer and inner key derivation
|
|
unsigned char outer_key[32], inner_key[32];
|
|
for (size_t i = 0; i < 32; i++) {
|
|
outer_key[i] = padded_key[i] ^ 0x5c;
|
|
inner_key[i] = padded_key[i] ^ 0x36;
|
|
}
|
|
|
|
// Inner hash
|
|
unsigned char inner_hash[32];
|
|
struct hashstate state;
|
|
initialize_hash(&state);
|
|
feed_hash(&state, inner_key, 32);
|
|
// Our message is salt plus big endian encoding of blockindex
|
|
feed_hash(&state, salt, saltlen);
|
|
unsigned char be_blockindex[4];
|
|
be_blockindex[0] = blockindex >> 24;
|
|
be_blockindex[1] = blockindex >> 16;
|
|
be_blockindex[2] = blockindex >> 8;
|
|
be_blockindex[3] = blockindex;
|
|
feed_hash(&state, be_blockindex, 4);
|
|
finalize_hash(&state, inner_hash);
|
|
|
|
// Outer hash
|
|
initialize_hash(&state);
|
|
feed_hash(&state, outer_key, 32);
|
|
feed_hash(&state, inner_hash, 32);
|
|
finalize_hash(&state, output);
|
|
}
|
|
|
|
void mfcrypt_hash(unsigned char chunk[16]) {
|
|
uint32_t key[4], words[4];
|
|
block2words(key, chunk);
|
|
block2words(words, chunk);
|
|
xxtea128(key, words);
|
|
words2block(chunk, words);
|
|
}
|
|
|
|
void blockmix(unsigned char block[KDF_BLOCKSIZE]) {
|
|
// r = KDF_BLOCKSIZE / 32, since block is 2r times the width of our
|
|
// hash function (xxtea128)
|
|
const size_t r = KDF_BLOCKSIZE / 32;
|
|
|
|
// accumulator (X) starts off as chunk 2r-1. Chunk k is at memory
|
|
// location 16*k and is 16 bytes long. Substituting we get:
|
|
// start = 16*(2*(KDF_BLOCKSIZE / 32) - 1)
|
|
// start = 16*(KDF_BLOCKSIZE / 16 - 1)
|
|
// start = KDF_BLOCKSIZE - 16
|
|
unsigned char accumulator[16];
|
|
memcpy(accumulator, &block[16 * (2*r - 1)], 16);
|
|
|
|
// Chunk i is at memory location 16*i. We go through chunks < 2r
|
|
unsigned char hashedchunks[KDF_BLOCKSIZE];
|
|
for (size_t i = 0; i < 2*r; i++) {
|
|
// X = H(X xor B_i)
|
|
for (size_t index = 0; index < 16; index++) {
|
|
accumulator[index] ^= block[16 * i + index];
|
|
}
|
|
mfcrypt_hash(accumulator);
|
|
// Y_i = X
|
|
memcpy(&hashedchunks[16 * i], accumulator, 16);
|
|
}
|
|
|
|
// Interleave the blocks back into the buffer. We go through B's
|
|
// chunks < r which corresponds to indices every 16 bytes smaller
|
|
// than 16*(KDF_BLOCKSIZE / 32) = KDF_BLOCKSIZE / 2
|
|
size_t i = 0;
|
|
for (; i < r; i++) {
|
|
// B_i = Y_{2*i}
|
|
memcpy(&block[16*i], &hashedchunks[16*2*i], 16);
|
|
}
|
|
// Now we go through B's chunks < 2r but >= r
|
|
for (; i < 2*r; i++) {
|
|
// B_i = Y_{2*(i - r) + 1}
|
|
memcpy(&block[16*i], &hashedchunks[16*(2*(i - r) + 1)], 16);
|
|
}
|
|
}
|
|
|
|
void romix(unsigned char block[KDF_BLOCKSIZE]) {
|
|
// Block i starts at location KDF_BLOCKSIZE * i
|
|
for (size_t i = 0; i < KDF_WORKFACTOR; i++) {
|
|
// V_i = X
|
|
memcpy(&workbuf[KDF_BLOCKSIZE * i], block, KDF_BLOCKSIZE);
|
|
// X = H(X)
|
|
blockmix(block);
|
|
}
|
|
|
|
for (size_t i = 0; i < sizeof(workbuf) / KDF_BLOCKSIZE; i++) {
|
|
// j = Integrify(X) mod N
|
|
// N is a power of two
|
|
uint32_t j = bytes2word(&block[KDF_BLOCKSIZE - 4]) & (KDF_WORKFACTOR - 1);
|
|
// X = H(X xor V_j)
|
|
for (size_t index = 0; index < KDF_BLOCKSIZE; index++) {
|
|
block[index] ^= workbuf[KDF_BLOCKSIZE * j + index];
|
|
}
|
|
blockmix(block);
|
|
}
|
|
}
|
|
|
|
void kdf(unsigned char key[16], unsigned char passphrase[], size_t passphraselen, unsigned char salt[32]) {
|
|
unsigned char block[KDF_BLOCKSIZE];
|
|
for (size_t i = 0; i < KDF_BLOCKSIZE / 32; i++) {
|
|
pbkdf2_1_block(&block[i * 32], passphrase, passphraselen, salt, 32, i);
|
|
}
|
|
|
|
romix(block);
|
|
|
|
unsigned char result[32];
|
|
pbkdf2_1_block(result, passphrase, passphraselen, block, KDF_BLOCKSIZE, 0);
|
|
|
|
memcpy(key, result, 16);
|
|
}
|
|
|
|
// 16 bit authentication tag
|
|
const int mprime = (16-2)/2;
|
|
// 32 bit = 4 byte length field
|
|
const int lprime = 4-1;
|
|
|
|
void ccm_mac(unsigned char mac[16], uint32_t key[4], uint64_t messageindex, unsigned char message[], uint32_t length) {
|
|
// CCM specifies that the length field is big endian while we are
|
|
// natively little endian. Flip it.
|
|
unsigned char length_bytes[4];
|
|
length_bytes[0] = length >> 24;
|
|
length_bytes[1] = length >> 16;
|
|
length_bytes[2] = length >> 8;
|
|
length_bytes[3] = length;
|
|
uint32_t be_length = bytes2word(length_bytes);
|
|
|
|
// First block is special
|
|
uint32_t mac_words[4] = {mprime<<3 | lprime, messageindex, messageindex >> 32, be_length};
|
|
xxtea128(key, mac_words);
|
|
|
|
// Process all full blocks
|
|
size_t index = 0;
|
|
for (; index + 16 <= length; index += 16) {
|
|
// Xor the plaintext block and previous encrypted block
|
|
uint32_t block[4];
|
|
block2words(block, &message[index]);
|
|
mac_words[0] ^= block[0];
|
|
mac_words[1] ^= block[1];
|
|
mac_words[2] ^= block[2];
|
|
mac_words[3] ^= block[3];
|
|
// Encrypt
|
|
xxtea128(key, mac_words);
|
|
}
|
|
if (index < length) {
|
|
// Pad with zeros to block width
|
|
unsigned char fullblock[16];
|
|
memset(fullblock, 0, 16);
|
|
memcpy(fullblock, &message[index], length - index);
|
|
// Xor the plaintext block and previous encrypted block
|
|
uint32_t block[4];
|
|
block2words(block, fullblock);
|
|
mac_words[0] ^= block[0];
|
|
mac_words[1] ^= block[1];
|
|
mac_words[2] ^= block[2];
|
|
mac_words[3] ^= block[3];
|
|
// Encrypt
|
|
xxtea128(key, mac_words);
|
|
}
|
|
|
|
words2block(mac, mac_words);
|
|
}
|
|
|
|
void ccm_xor_block(unsigned char block[16], uint32_t key[4], uint64_t messageindex, uint32_t counter) {
|
|
// CCM specifies that the counter field is big endian while we are
|
|
// natively little endian. Flip it.
|
|
unsigned char counter_bytes[4];
|
|
counter_bytes[0] = counter >> 24;
|
|
counter_bytes[1] = counter >> 16;
|
|
counter_bytes[2] = counter >> 8;
|
|
counter_bytes[3] = counter;
|
|
uint32_t be_counter = bytes2word(counter_bytes);
|
|
|
|
uint32_t words[4] = {lprime, messageindex, messageindex >> 32, be_counter};
|
|
xxtea128(key, words);
|
|
unsigned char keystream[16];
|
|
words2block(keystream, words);
|
|
|
|
for (size_t i = 0; i < 16; i++) {
|
|
block[i] ^= keystream[i];
|
|
}
|
|
}
|
|
|
|
void ccm_encrypt(unsigned char key[16], uint64_t messageindex, unsigned char message[], uint32_t length, unsigned char mac[16]) {
|
|
uint32_t key_words[4];
|
|
block2words(key_words, key);
|
|
|
|
// Authenticate
|
|
ccm_mac(mac, key_words, messageindex, message, length);
|
|
|
|
// Encrypt
|
|
// MAC is xored with first block of keystream
|
|
ccm_xor_block(mac, key_words, messageindex, 0);
|
|
|
|
// Xor full blocks
|
|
size_t index = 0;
|
|
uint32_t counter = 1;
|
|
for (; index + 16 <= length; index += 16) {
|
|
ccm_xor_block(&message[index], key_words, messageindex, counter++);
|
|
}
|
|
// Xor partial block, if any
|
|
if (index < length) {
|
|
unsigned char fullblock[16];
|
|
memcpy(fullblock, &message[index], length - index);
|
|
ccm_xor_block(fullblock, key_words, messageindex, counter++);
|
|
memcpy(&message[index], fullblock, length - index);
|
|
}
|
|
}
|
|
|
|
bool ccm_decrypt(unsigned char key[16], uint64_t messageindex, unsigned char message[], uint32_t length, unsigned char mac[16]) {
|
|
uint32_t key_words[4];
|
|
block2words(key_words, key);
|
|
|
|
// Decrypt
|
|
// MAC is xored with first block of keystream
|
|
ccm_xor_block(mac, key_words, messageindex, 0);
|
|
|
|
// Xor full blocks
|
|
size_t index = 0;
|
|
uint32_t counter = 1;
|
|
for (; index + 16 <= length; index += 16) {
|
|
// Message blocks are numbered from index 1 onwards
|
|
ccm_xor_block(&message[index], key_words, messageindex, counter++);
|
|
}
|
|
// Xor partial block, if any
|
|
if (index < length) {
|
|
unsigned char fullblock[16];
|
|
memset(fullblock, 0, 16);
|
|
memcpy(fullblock, &message[index], length - index);
|
|
ccm_xor_block(fullblock, key_words, messageindex, counter++);
|
|
memcpy(&message[index], fullblock, length - index);
|
|
}
|
|
|
|
// Compute the expected authentication tag
|
|
unsigned char computed_mac[16];
|
|
ccm_mac(computed_mac, key_words, messageindex, message, length);
|
|
|
|
// Compare the expected and actual tag in constant time
|
|
unsigned char different = 0;
|
|
for (size_t i = 0; i < 16; i++) {
|
|
different |= computed_mac[i] ^ mac[i];
|
|
}
|
|
|
|
// Do the tags match?
|
|
if (different) {
|
|
// Nope. Wipe what we decrypted and return false
|
|
explicit_bzero(message, length);
|
|
return false;
|
|
}
|
|
|
|
// They do, return true
|
|
return true;
|
|
}
|
|
|
|
ssize_t passphrase_prompt(unsigned char *passphrase, size_t size, const char *prompt) {
|
|
// Read from controlling TTY, even if stdio has been redirected
|
|
int tty = open("/dev/tty", O_RDWR);
|
|
if (tty == -1) {
|
|
perror("Failed to open controlling tty");
|
|
return -1;
|
|
}
|
|
|
|
if (write(tty, prompt, strlen(prompt)) == -1) {
|
|
perror("Failed to write to terminal");
|
|
close(tty);
|
|
return -1;
|
|
}
|
|
|
|
// Turn off echo
|
|
struct termios saved;
|
|
if (tcgetattr(tty, &saved) != 0) {
|
|
perror("Failed to get terminal attributes");
|
|
close(tty);
|
|
return -1;
|
|
}
|
|
struct termios altered;
|
|
altered = saved;
|
|
altered.c_lflag &= ~ECHO;
|
|
if (tcsetattr(tty, TCSANOW, &altered) != 0) {
|
|
perror("Failed to turn echoing off");
|
|
close(tty);
|
|
return -1;
|
|
}
|
|
|
|
// Read until newline
|
|
size_t index = 0;
|
|
for (;;) {
|
|
if (index >= size) {
|
|
fprintf(stderr, "Passphrase too long, maximum size is %zu bytes\n", size - 1);
|
|
// Clean any line buffer
|
|
char tmp;
|
|
for (;;) {
|
|
if (read(tty, &tmp, 1) <= 0) {
|
|
break;
|
|
}
|
|
if (tmp == '\n') {
|
|
break;
|
|
}
|
|
}
|
|
tcsetattr(tty, TCSANOW, &saved);
|
|
close(tty);
|
|
return -1;
|
|
}
|
|
|
|
ssize_t bytes_read = read(tty, &passphrase[index], size - index);
|
|
if (bytes_read == -1) {
|
|
perror("Failed to read passphrase");
|
|
tcsetattr(tty, TCSANOW, &saved);
|
|
close(tty);
|
|
return -1;
|
|
} else if (bytes_read == 0) {
|
|
fprintf(stderr, "Unexpected EOF\n");
|
|
tcsetattr(tty, TCSANOW, &saved);
|
|
close(tty);
|
|
return -1;
|
|
}
|
|
|
|
index += bytes_read;
|
|
if (passphrase[index-1] == '\n') {
|
|
// Got end of line
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Write a newline (since the user's is not visible) and restore
|
|
// terminal settings
|
|
if (write(tty, "\n", 1) == -1) {
|
|
perror("Failed to write to terminal");
|
|
tcsetattr(tty, TCSANOW, &saved);
|
|
close(tty);
|
|
return -1;
|
|
}
|
|
if (tcsetattr(tty, TCSANOW, &saved) != 0) {
|
|
perror("Failed to restore terminal state");
|
|
close(tty);
|
|
return -1;
|
|
}
|
|
close(tty);
|
|
|
|
return index - 1;
|
|
}
|
|
|
|
ssize_t passphrase_file(char *passfilepath, unsigned char passphrase[], size_t size) {
|
|
int file = open(passfilepath, O_RDONLY);
|
|
|
|
// Check permissions
|
|
struct stat statbuf;
|
|
if (fstat(file, &statbuf) != 0) {
|
|
perror("Could not stat passphrase file");
|
|
close(file);
|
|
return -1;
|
|
}
|
|
if (statbuf.st_mode & S_IROTH) {
|
|
fprintf(stderr, "Warning: Passphrase file is world-readable\n");
|
|
}
|
|
|
|
// Read until newline
|
|
size_t index = 0;
|
|
for (;;) {
|
|
if (index >= size) {
|
|
fprintf(stderr, "Passphrase too long, maximum size is %zu bytes\n", size - 1);
|
|
close(file);
|
|
return -1;
|
|
}
|
|
|
|
ssize_t bytes_read = read(file, &passphrase[index], size - index);
|
|
if (bytes_read == -1) {
|
|
perror("Failed to read passphrase");
|
|
close(file);
|
|
return -1;
|
|
} else if (bytes_read == 0) {
|
|
fprintf(stderr, "Unexpected EOF\n");
|
|
close(file);
|
|
return -1;
|
|
}
|
|
|
|
index += bytes_read;
|
|
if (passphrase[index-1] == '\n') {
|
|
// Got end of line
|
|
break;
|
|
}
|
|
}
|
|
|
|
close(file);
|
|
|
|
return index - 1;
|
|
}
|
|
|
|
void usage(char *name) {
|
|
fprintf(stderr, "Usage: %s -d | -e [-f] [-p passfile] [-i infile] [-o outfile]\n\n", name);
|
|
fprintf(stderr, "-d Decrypt\n");
|
|
fprintf(stderr, "-e Encrypt\n");
|
|
fprintf(stderr, "-f Force output to terminal\n");
|
|
fprintf(stderr, "-p passfile Read passphrase from a file instead of the terminal.\n");
|
|
fprintf(stderr, "-i infile Read from a file instead of the terminal.\n");
|
|
fprintf(stderr, "-o outfile Write to a file instead of the terminal.\n");
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
bool encrypting = false;
|
|
bool decrypting = false;
|
|
bool force = false;
|
|
char *passfilepath = NULL;
|
|
char *infilepath = NULL;
|
|
char *outfilepath = NULL;
|
|
|
|
int opt;
|
|
while ((opt = getopt(argc, argv, "defp:i:o:")) != -1) {
|
|
switch (opt) {
|
|
case 'd':
|
|
decrypting = true;
|
|
break;
|
|
case 'e':
|
|
encrypting = true;
|
|
break;
|
|
case 'f':
|
|
force = true;
|
|
break;
|
|
case 'p':
|
|
passfilepath = optarg;
|
|
break;
|
|
case 'i':
|
|
infilepath = optarg;
|
|
break;
|
|
case 'o':
|
|
outfilepath = optarg;
|
|
break;
|
|
default:
|
|
usage(argv[0]);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (optind != argc) {
|
|
usage(argv[0]);
|
|
exit(1);
|
|
}
|
|
|
|
if ((!encrypting && !decrypting) || (encrypting && decrypting)) {
|
|
usage(argv[0]);
|
|
exit(1);
|
|
}
|
|
|
|
FILE *infile = stdin;
|
|
FILE *outfile = stdout;
|
|
|
|
if (infilepath != NULL) {
|
|
infile = fopen(infilepath, "r");
|
|
if (infile == NULL) {
|
|
perror("Failed to open input file");
|
|
exit(1);
|
|
}
|
|
}
|
|
if (outfilepath != NULL) {
|
|
outfile = fopen(outfilepath, "w");
|
|
if (outfile == NULL) {
|
|
perror("Failed to open output file");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (passfilepath == NULL) {
|
|
// Read from terminal if no passfile specified
|
|
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[sizeof(passphrase)];
|
|
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));
|
|
}
|
|
} else {
|
|
passphrase_len = passphrase_file(passfilepath, passphrase, sizeof(passphrase));
|
|
if (passphrase_len == -1) {
|
|
explicit_bzero(passphrase, sizeof(passphrase));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
// Derive key
|
|
unsigned char key[16];
|
|
kdf(key, passphrase, passphrase_len, salt);
|
|
explicit_bzero(passphrase, sizeof(passphrase));
|
|
|
|
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);
|
|
}
|
|
|
|
// MAC is after the message
|
|
unsigned char *mac = &workbuf[bytes];
|
|
|
|
ccm_encrypt(key, messageindex++, workbuf, bytes, mac);
|
|
|
|
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;
|
|
}
|