ungeli/ungeli.c

740 lines
22 KiB
C

/* Copyright © 2013 Jeff Epler <jepler@unpythonic.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you
* do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source
* files in the program, then also delete it here.
*
* When compiled and linked "This product includes software developed by the
* OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)"
*/
#include <fcntl.h>
#include <inttypes.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/md5.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#ifdef __linux__
#include <sys/ioctl.h>
#include <linux/nbd.h>
#include <linux/fs.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#ifndef NBD_CMD_MASK_COMMAND
#define NBD_CMD_MASK_COMMAND 0x0000ffff
#endif
#endif
#define CRYPTO_AES_XTS (22)
#define IVSIZE (16)
#define SHA512_MDLEN (SHA512_DIGEST_LENGTH)
#define ELI_MAXKEYLEN (64)
#define ELI_KEY_SHIFT (20)
#define ELI_SALTLEN (64)
#define ELI_MAXMKEYS (2)
#define ELI_DATAKEYLEN (ELI_MAXKEYLEN)
#define ELI_USERKEYLEN (ELI_MAXKEYLEN)
#define ELI_IVKEYLEN (ELI_MAXKEYLEN)
#define ELI_DATAIVKEYLEN (ELI_DATAKEYLEN + ELI_IVKEYLEN)
#define ELI_MKEYLEN (ELI_DATAIVKEYLEN + SHA512_MDLEN)
static unsigned char mkey[192];
static int
hexcharvalue(char c) {
if(c >= '0' && c <= '9') return c - '0';
if(c >= 'a' && c <= 'f') return c - 'a' + 10;
if(c >= 'A' && c <= 'F') return c - 'A' + 10;
return 0;
}
#if 0
static const char *
bintohex(char *dst, const unsigned char *arg, size_t sz)
{
char *result = dst;
static char hexletters[16] = "0123456789abcdef";
for(int i=0; i<sz; i++) {
*dst++ = hexletters[arg[i] >> 4];
*dst++ = hexletters[arg[i] & 0xf];
}
*dst = 0;
return result;
}
#endif
static void hextobin(unsigned char *buf, size_t sz, const char *arg)
{
for(int i=0; i<sz; i++) {
if(*arg == 0) break;
unsigned hibit = hexcharvalue(*arg);
arg++;
if(*arg == 0) break;
unsigned lowbit = hexcharvalue(*arg);
arg++;
buf[i] = (hibit << 4) | lowbit;
}
}
static void
set_mkey(const char *arg) {
hextobin(mkey, sizeof(mkey), arg);
}
static void
perror_fatal(const char *s) __attribute__((noreturn));
static void
perror_fatal(const char *s) {
perror(s); abort();
}
static void
fatal(const char *s) __attribute__((noreturn));
static void
fatal(const char *s) {
fprintf(stderr, "%s\n", s); abort();
}
static void
fatalf(const char *fmt, ...) __attribute__((noreturn));
static void
fatalf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
abort();
}
void trimnl(char *buf) {
char *end = buf + strlen(buf) - 1;
while(end >= buf && *end == '\n') { *end-- = 0; }
}
static void
putbe32(unsigned char *buf, uint32_t arg)
{
*buf++ = (arg >> 24) & 0xff;
*buf++ = (arg >> 16) & 0xff;
*buf++ = (arg >> 8) & 0xff;
*buf = (arg ) & 0xff;
}
static void
putle64(unsigned char *buf, uint64_t arg)
{
*buf++ = (arg ) & 0xff;
*buf++ = (arg >> 8) & 0xff;
*buf++ = (arg >> 16) & 0xff;
*buf++ = (arg >> 24) & 0xff;
*buf++ = (arg >> 32) & 0xff;
*buf++ = (arg >> 40) & 0xff;
*buf++ = (arg >> 48) & 0xff;
*buf = (arg >> 56) & 0xff;
}
static void
xtr_ivgen(unsigned char *buf, uint64_t arg) {
putle64(buf, arg);
memset(buf+8, 0, 8);
}
static void
read_full(int fd, unsigned char *blk, int blocksize) {
do {
int res = read(fd, blk, blocksize);
if(res == 0) fatal("read_full read() -> 0");
if(res < 0 && errno == EAGAIN) continue;
if(res < 0) perror_fatal("read_full");
blk += res;
blocksize -= res;
} while(blocksize);
}
static void
write_full(int fd, const unsigned char *blk, int blocksize) {
do {
int res = write(fd, blk, blocksize);
if(res == 0) fatal("write_full write() -> 0");
if(res < 0 && errno == EAGAIN) continue;
if(res < 0) perror_fatal("write_full");
blk += res;
blocksize -= res;
} while(blocksize);
}
typedef struct {
SHA512_CTX ctx;
unsigned char k_opad[128];
} eli_crypto_ctx;
static void xorbuf(unsigned char *dest, const unsigned char *src,
unsigned char xor, size_t n) {
for(size_t i=0; i<n; i++) {
dest[i] = src[i] ^ xor;
}
}
static void
eli_crypto_ctx_init(eli_crypto_ctx *ctx,
const unsigned char *hkey, size_t hkeysz)
{
unsigned char key[128], k_ipad[128];
memset(key, 0, sizeof(key));
if(hkeysz <= 128) {
if(hkeysz != 0)
memcpy(key, hkey, hkeysz);
} else {
SHA512(hkey, hkeysz, key);
}
xorbuf(k_ipad, key, 0x36, 128);
xorbuf(ctx->k_opad, key, 0x5c, 128);
SHA512_Init(&ctx->ctx);
SHA512_Update(&ctx->ctx, k_ipad, 128);
}
static void
eli_crypto_ctx_update(eli_crypto_ctx *ctx,
const unsigned char *data, size_t datasz)
{
SHA512_Update(&ctx->ctx, data, datasz);
}
static void
eli_crypto_ctx_final(eli_crypto_ctx *ctx,
unsigned char *out, size_t outsz)
{
unsigned char digest[SHA512_MDLEN];
SHA512_Final(digest, &ctx->ctx);
SHA512_Init(&ctx->ctx);
SHA512_Update(&ctx->ctx, ctx->k_opad, 128);
SHA512_Update(&ctx->ctx, digest, SHA512_MDLEN);
SHA512_Final(digest, &ctx->ctx);
memcpy(out, digest, outsz ? outsz : SHA512_MDLEN);
}
static void
eli_crypto_hmac(const unsigned char *key, size_t keysz,
const unsigned char *data, size_t datasz,
unsigned char *out, size_t outsz)
{
eli_crypto_ctx ctx;
eli_crypto_ctx_init(&ctx, key, keysz);
eli_crypto_ctx_update(&ctx, data, datasz);
eli_crypto_ctx_final(&ctx, out, outsz);
}
static void
eli_key_fill(const unsigned char *mkey, uint64_t keyno,
unsigned char *key)
{
unsigned char buf[12];
memcpy(buf, "ekey", 4);
putle64(buf+4, keyno);
eli_crypto_hmac(mkey, ELI_MAXKEYLEN, buf, sizeof(buf), key, 0);
}
void test_eli_crypto_hmac() {
unsigned char result[SHA512_MDLEN], xresult[SHA512_MDLEN];
hextobin(xresult, sizeof(xresult),
"c2bb740c5c718cc30baccd240af3ef853f872d5a642e0b52449921bdf10723e6"
"b6e99cbea89bd2da76fb45b8f073f27cc8c9a68c698ac4d51244df74aa13cdc1");
eli_crypto_hmac((unsigned char*) "\1", 1, (unsigned char*) "bluemoon", 8, result, 0);
if(memcmp(result, xresult, sizeof(xresult))) {
fatal("test_eli_crypto_hmac()");
}
}
void eli_decrypt_range(int ifd, unsigned char *ob, uint64_t byteoffset, uint64_t count, int blocksize)
{
unsigned char bkey[ELI_MAXKEYLEN];
eli_key_fill(mkey, (byteoffset >> ELI_KEY_SHIFT) / blocksize, bkey);
unsigned char biv[IVSIZE];
xtr_ivgen(biv, byteoffset);
unsigned char ib[count];
read_full(ifd, ib, count);
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(ctx, EVP_aes_128_xts(), NULL, bkey, biv);
int out_len, final_out_len;
EVP_DecryptUpdate(ctx, ob, &out_len, ib, count);
EVP_DecryptFinal_ex(ctx, ob+out_len, &final_out_len);
EVP_CIPHER_CTX_free(ctx);
if(out_len + final_out_len != count) fatalf("eli_decrypt_range EVP final_out_len %d != %d+%d", count, out_len, final_out_len);
}
#ifdef __linux__
static uint64_t ntohll(uint64_t v) {
if(ntohl(1) == 1) return v;
uint32_t lo = v & 0xffffffff, hi = v >> 32;
return ((uint64_t)ntohl(lo)) << 32 | ntohl(hi);
}
uint64_t filesize(int fd) {
off_t result = lseek(fd, (off_t)0, SEEK_END);
if(result < (off_t)0) perror_fatal("lseek(SEEK_END)");
return (uint64_t)result;
}
#define BUFSIZE ((1024*1024)+sizeof(struct nbd_reply))
void eli_decrypt_range_ex(int ifd, unsigned char *buf, uint64_t offset,
size_t sz, int blocksize) {
if(offset % blocksize) {
fatal("Offset not multiple of blocksize");
}
if(sz % blocksize) {
fatal("Request length not multiple of blocksize");
}
while(sz) {
lseek(ifd, offset, 0);
eli_decrypt_range(ifd, buf, offset, blocksize, blocksize);
sz -= blocksize;
buf += blocksize;
offset += blocksize;
}
}
void serve_nbd(int sock, int ifd, int blocksize) {
struct nbd_request request;
struct nbd_reply reply;
unsigned char buf[BUFSIZE];
reply.magic = htonl(NBD_REPLY_MAGIC);
reply.error = 0;
while(1) {
read_full(sock, (unsigned char*)&request, sizeof(request));
if(request.magic != htonl(NBD_REQUEST_MAGIC))
fatal("bad request.magic");
request.from = ntohll(request.from);
request.type = ntohl(request.type);
size_t len = ntohl(request.len);
long command = request.type & NBD_CMD_MASK_COMMAND;
if(command == NBD_CMD_DISC) break;
if(command != NBD_CMD_READ)
fatalf("Un-handled request %d", command);
memcpy(reply.handle, request.handle, sizeof(reply.handle));
write_full(sock, (unsigned char*)&reply, sizeof(reply));
size_t currlen = len;
if(currlen > BUFSIZE - sizeof(struct nbd_reply))
len = BUFSIZE - sizeof(struct nbd_reply);
while(len > 0) {
eli_decrypt_range_ex(ifd, buf, request.from, currlen, blocksize);
write_full(sock, buf, currlen);
len -= currlen;
currlen = (len < BUFSIZE) ? len : BUFSIZE;
}
}
}
int setup_nbd(int ifd, int ofd, int blocksize) {
// valgrind 3.8.1 reports "unhandled ioctl with no size/direction hints"
// and similar for NBD ioctls. These can safely(?) be ignored.
if(ioctl(ofd, NBD_SET_SIZE, (unsigned long)blocksize) < 0)
perror_fatal("ioctl NBD_SET_SIZE");
uint64_t wholesize = filesize(ifd);
uint64_t n4kblocks = (wholesize - 512) / 4096ULL;
if(n4kblocks != (uint64_t)(unsigned long)n4kblocks) {
fatal("Device too large");
}
if(ioctl(ofd, NBD_SET_BLKSIZE, 4096UL) < 0)
perror_fatal("ioctl NBD_SET_BLKSIZE");
if(ioctl(ofd, NBD_SET_SIZE_BLOCKS, (unsigned long) n4kblocks) < 0)
perror_fatal("ioctl NBD_SET_SIZE_BLOCKS");
if(ioctl(ofd, NBD_SET_BLKSIZE, blocksize) < 0)
perror_fatal("ioctl NBD_SET_BLKSIZE");
if(ioctl(ofd, NBD_CLEAR_SOCK) < 0)
perror_fatal("ioctl NBD_CLEAR_BLOCK");
if(ioctl(ofd, NBD_SET_FLAGS, NBD_FLAG_HAS_FLAGS | NBD_FLAG_READ_ONLY) < 0)
perror_fatal("ioctl NBD_SET_FLAGS");
int read_only = 1;
if(ioctl(ofd, BLKROSET, (unsigned long)&read_only) < 0)
perror_fatal("ioctl BLKROSET");
int sv[2];
if(socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) < 0)
perror_fatal("socketpair");
if(ioctl(ofd, NBD_SET_SOCK, sv[0]) < 0)
perror_fatal("ioctl NBD_SET_SOCK");
int pid = fork();
if(pid < 0) perror_fatal("fork");
if(pid > 0) {
close(sv[1]);
// parent
// need to do like in nbd-client to cause partition table read?
if(ioctl(ofd, NBD_DO_IT) < 0) perror_fatal("ioctl NBD_DO_IT");
} else {
close(sv[0]);
close(ofd);
serve_nbd(sv[1], ifd, blocksize);
}
return 0;
}
int is_nbd(int ofd) {
// valgrind 3.8.1 reports "unhandled ioctl with no size/direction hints"
// and similar for NBD ioctls. These can safely(?) be ignored.
return ioctl(ofd, NBD_SET_SIZE, 4096UL) == 0;
}
#else
int setup_nbd(int ifd, int ofd) {
fatal("not on this platform");
}
int is_nbd(int ofd) {
return 0;
}
#endif
static int
readle16(unsigned char *buf) {
int result = *buf++;
result |= *buf << 8;
return result;
}
static uint32_t
readle32(unsigned char *buf) {
uint32_t result = *buf++;
result |= (uint32_t)*buf++ << 8;
result |= (uint32_t)*buf++ << 16;
result |= (uint32_t)*buf << 24;
return result;
}
static uint64_t
readle64(unsigned char *buf) {
uint64_t result = *buf++;
result |= (uint64_t)*buf++ << 8;
result |= (uint64_t)*buf++ << 16;
result |= (uint64_t)*buf++ << 24;
result |= (uint64_t)*buf++ << 32;
result |= (uint64_t)*buf++ << 40;
result |= (uint64_t)*buf++ << 48;
result |= (uint64_t)*buf << 56;
return result;
}
static void
eli_verify_metadata(unsigned char *buf, unsigned char *hash)
{
unsigned char ckhash[MD5_DIGEST_LENGTH];
MD5(buf, 511-16, ckhash);
if(memcmp(hash, ckhash, sizeof(ckhash)))
fatal("metadata hash check failure");
}
typedef struct {
char md_magic[16];
uint32_t md_version;
uint32_t md_flags;
uint16_t md_ealgo;
uint16_t md_keylen;
uint16_t md_aalgo;
uint64_t md_provsize;
uint32_t md_sectorsize;
uint8_t md_keys;
uint32_t md_iterations;
uint8_t md_salt[ELI_SALTLEN];
uint8_t md_mkeys[ELI_MAXMKEYS * ELI_MKEYLEN];
uint8_t md_hash[MD5_DIGEST_LENGTH];
} eli_metadata;
static void
eli_read_metadata(int fd, eli_metadata *md) {
unsigned char buf[511], *ptr=buf;
lseek(fd, -512, SEEK_END);
read_full(fd, buf, 511);
memcpy(md->md_magic, ptr, sizeof(md->md_magic));
ptr += sizeof(md->md_magic);
md->md_version = readle32(ptr); ptr += 4;
md->md_flags = readle32(ptr); ptr += 4;
md->md_ealgo = readle16(ptr); ptr += 2;
md->md_keylen = readle16(ptr); ptr += 2;
md->md_aalgo = readle16(ptr); ptr += 2;
md->md_provsize = readle64(ptr); ptr += 8;
md->md_sectorsize = readle32(ptr); ptr += 4;
md->md_keys = *ptr; ptr += 1;
md->md_iterations = readle32(ptr); ptr += 4;
memcpy(md->md_salt, ptr, sizeof(md->md_salt));
ptr += sizeof(md->md_salt);
memcpy(md->md_mkeys, ptr, sizeof(md->md_mkeys));
ptr += sizeof(md->md_mkeys);
memcpy(md->md_hash, ptr, sizeof(md->md_hash));
eli_verify_metadata(buf, md->md_hash);
}
static void
eli_crypto_decrypt(int ealgo, unsigned char *enckey, size_t keylen,
unsigned char *src, size_t len, unsigned char *dest)
{
if(ealgo != CRYPTO_AES_XTS) fatal("unsupported ealgo");
if(keylen != 16) fatal("unsupported key length");
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, enckey, 0);
EVP_CIPHER_CTX_set_padding(ctx, 0);
int out_len, final_out_len;
EVP_DecryptUpdate(ctx, dest, &out_len, src, len);
EVP_DecryptFinal_ex(ctx, dest+out_len, &final_out_len);
EVP_CIPHER_CTX_free(ctx);
if(out_len + final_out_len != len) fatalf("eli_crypto_decrypt EVP final_out_len %d != %d+%d", len, out_len, final_out_len);
}
static void pkcs_xorbuf(unsigned char *dest, const unsigned char *src) {
for(int i=0; i<SHA512_MDLEN; i++) { dest[i] ^= src[i]; }
}
static void
eli_pkcs5v2_genkey(const unsigned char *salt, size_t nsalt, const char *buf, int iterations,
unsigned char *dest) {
size_t nbuf = strlen(buf);
unsigned char saltcount[nsalt+4];
memcpy(saltcount, salt, nsalt);
putbe32(saltcount+nsalt, 1);
unsigned char md[SHA512_MDLEN], keyp[SHA512_MDLEN];
eli_crypto_hmac((unsigned char*)buf, nbuf,
saltcount, nsalt+4, md, sizeof(md));
memcpy(keyp, md, sizeof(keyp));
for(int i=1; i<iterations; i++) {
eli_crypto_hmac((unsigned char*)buf, nbuf,
md, SHA512_MDLEN, md, sizeof(md));
pkcs_xorbuf(keyp, md);
}
memcpy(dest, keyp, sizeof(keyp));
}
static void
test_eli_pkcs5v2()
{
unsigned char test_hkey[] = {1};
char *test_passphrase = "bluemoon";
int test_iterations = 8;
unsigned char xresult[SHA512_MDLEN];
hextobin(xresult, sizeof(xresult),
"629e270fe754fb70ce2b2e6bc0de923a0f66ec0d41c03dab6b9049c7446e9d12"
"b154f53cd6bc3f32c37436c7c40293e21ea299dfc6617b8e0315b634b0f8474c");
unsigned char result[SHA512_MDLEN];
eli_pkcs5v2_genkey(test_hkey, sizeof(test_hkey), test_passphrase, test_iterations, result);
if(memcmp(result, xresult, sizeof(xresult))) {
fatal("test_eli_crypto_hmac()");
}
}
#define MAX(a,b) ((a) < (b) ? (b) : (a))
static int
eli_mkey_verify(unsigned char *tmpmkey, unsigned char *key) {
unsigned char hmkey[SHA512_MDLEN];
unsigned char chmac[SHA512_MDLEN];
unsigned char *odhmac = tmpmkey + ELI_DATAIVKEYLEN;
eli_crypto_hmac(key, ELI_USERKEYLEN, (unsigned char*)"\0", 1, hmkey, sizeof(hmkey));
eli_crypto_hmac(hmkey, sizeof(hmkey), tmpmkey, ELI_DATAIVKEYLEN, chmac, sizeof(chmac));
// valgrind 3.8.1 reports "Conditional jump or move depends on
// uninitialized value(s)". Instrumenting indicates that there are
// uninitialized bytes in the local variable 'chmac' but as the software
// works I am inclined to believe this is a spurious diagnotsic.
int result = !memcmp(chmac, odhmac, sizeof(chmac));
if(!result) fprintf(stderr, "Note: Failed key verification (this is not fatal on its own)\n");
return result;
}
static void
set_mkey_from_passfile(const char *arg, eli_metadata *md) {
FILE *s = fopen(arg, "r");
char buf[4096];
if(!s) perror_fatal("fopen passfile");
fgets(buf, sizeof(buf), s);
trimnl(buf);
fclose(s);
unsigned char hbuf[SHA512_MDLEN];
eli_pkcs5v2_genkey(md->md_salt, sizeof(md->md_salt),
buf, md->md_iterations, hbuf);
unsigned char key[SHA512_MDLEN];
eli_crypto_hmac(0, 0, hbuf, sizeof(hbuf), key, sizeof(key));
unsigned char enckey[SHA512_MDLEN];
eli_crypto_hmac(key, sizeof(key), (unsigned char*)"\1", 1, enckey, sizeof(enckey));
for(int nkey=0; nkey<ELI_MAXMKEYS; nkey++) {
int moff = nkey * ELI_MKEYLEN;
int bit = (1<<nkey);
if(!(md->md_keys & bit)) continue;
unsigned char tmpmkey[ELI_MKEYLEN];
eli_crypto_decrypt(md->md_ealgo, enckey, md->md_keylen/8,
md->md_mkeys + moff, ELI_MKEYLEN, tmpmkey);
if(!eli_mkey_verify(tmpmkey, key)) continue;
memcpy(mkey, tmpmkey, sizeof(mkey));
return;
}
fatal("Failed to decryt master key");
}
static void
set_mkey_from_keyfile(const char *arg, eli_metadata *md) {
FILE *s = fopen(arg, "r");
unsigned char buf[ELI_USERKEYLEN];
if(!s) perror_fatal("fopen keyfile");
if (!fread(buf, sizeof(buf), 1, s)) perror_fatal("fread keyfile");
fclose(s);
unsigned char key[SHA512_MDLEN];
eli_crypto_hmac(0, 0, buf, sizeof(buf), key, sizeof(key));
unsigned char enckey[SHA512_MDLEN];
eli_crypto_hmac(key, sizeof(key), (unsigned char*)"\1", 1, enckey, sizeof(enckey));
for(int nkey=0; nkey<ELI_MAXMKEYS; nkey++) {
int moff = nkey * ELI_MKEYLEN;
int bit = (1<<nkey);
if(!(md->md_keys & bit)) continue;
unsigned char tmpmkey[ELI_MKEYLEN];
eli_crypto_decrypt(md->md_ealgo, enckey, md->md_keylen/8,
md->md_mkeys + moff, ELI_MKEYLEN, tmpmkey);
if(!eli_mkey_verify(tmpmkey, key)) continue;
memcpy(mkey, tmpmkey, sizeof(mkey));
return;
}
fatal("Failed to decryt master key");
}
int main(int argc, char **argv) {
unsigned blocksize = 4096;
unsigned long long offset = 0;
unsigned long long nblocks = 1;
char *passphrase_file = NULL;
char *key_file = NULL;
test_eli_crypto_hmac();
test_eli_pkcs5v2();
int opt;
while((opt = getopt(argc, argv, "o:n:b:m:j:k:")) != -1) {
switch(opt) {
case 'o': offset = strtoull(optarg, NULL, 0); break;
case 'n': nblocks = strtoull(optarg, NULL, 0); break;
case 'b': blocksize = atoi(optarg); break;
case 'm': set_mkey(optarg); break;
case 'j': passphrase_file = optarg; break;
case 'k': key_file = optarg; break;
}
}
argc -= optind-1; argv += optind-1;
int ifd=0, ofd=1;
if(argc > 1 && strcmp(argv[1], "-")) {
ifd = open(argv[1], O_RDONLY);
if(ifd < 0) perror_fatal("open(input)");
}
if(argc > 2 && strcmp(argv[2], "-")) {
ofd = open(argv[2], O_WRONLY | O_CREAT, 0666);
if(ofd < 0) perror_fatal("open(outut)");
}
if(isatty(ifd))
fatal("refusing to read from a terminal (use cat | if you insist)");
eli_metadata md;
eli_read_metadata(ifd, &md);
if(!blocksize)
fatal("Blocksize must not be zero");
if(md.md_version != 6)
fatalf("Unsupported version %d", md.md_version);
if(passphrase_file)
set_mkey_from_passfile(passphrase_file, &md);
if(key_file)
set_mkey_from_keyfile(key_file, &md);
if(is_nbd(ofd)) {
return setup_nbd(ifd, ofd, blocksize);
}
if(isatty(ofd))
fatal("refusing to write to a terminal (use | cat if you insist)");
if(lseek(ifd, offset*blocksize, SEEK_SET) < 0)
perror_fatal("lseek(input)");
for(unsigned long long i=0; i<nblocks; i++)
{
unsigned char ob[blocksize];
uint64_t blockoffset = offset + i;
uint64_t byteoffset = blockoffset * blocksize;
eli_decrypt_range(ifd, ob, byteoffset, blocksize, blocksize);
// valgrind 3.8.1 reports "Syscall param write(buf) points to
// uninitialised byte(s)". Zeroing ob[] before eli_decrypt_range
// does not make the diagnostic go away. Since decrypted blocks
// are actually correct, I am inclined to believe this is a spurious
// diagnostic.
write_full(ofd, ob, blocksize);
}
return 0;
}