Implemented updating an encrypted file in ntfsdecrypt

Existing encrypted files can be updated provided the encryption method
and key can be extracted from the LOGGED_UTILITY_STREAM attribute.
edge.strict_endians
Jean-Pierre André 2014-03-11 11:10:33 +01:00
parent 700015c289
commit fb88692394
2 changed files with 176 additions and 5 deletions

View File

@ -6108,7 +6108,8 @@ static int ntfs_non_resident_attr_shrink(ntfs_attr *na, const s64 newsize)
/* If the attribute now has zero size, make it resident. */
if (!newsize) {
if (ntfs_attr_make_resident(na, ctx)) {
if (!(na->data_flags & ATTR_IS_ENCRYPTED)
&& ntfs_attr_make_resident(na, ctx)) {
/* If couldn't make resident, just continue. */
if (errno != EPERM)
ntfs_log_error("Failed to make attribute "
@ -6436,7 +6437,7 @@ static int ntfs_attr_truncate_i(ntfs_attr *na, const s64 newsize,
* Encrypted attributes are not supported. We return access denied,
* which is what Windows NT4 does, too.
*/
if (na->data_flags & ATTR_IS_ENCRYPTED) {
if ((na->data_flags & ATTR_IS_ENCRYPTED) && !na->ni->vol->efs_raw) {
errno = EACCES;
ntfs_log_trace("Cannot truncate encrypted attribute\n");
goto out;

View File

@ -65,6 +65,7 @@
#include "dir.h"
#include "layout.h"
/* #include "version.h" */
#include "misc.h"
typedef gcry_sexp_t ntfs_rsa_private_key;
@ -129,6 +130,7 @@ struct options {
int force; /* Override common sense */
int quiet; /* Less output */
int verbose; /* Extra output */
int encrypt; /* Encrypt */
};
static const char *EXEC_NAME = "ntfsdecrypt";
@ -168,6 +170,7 @@ static void usage(void)
ntfs_log_info("\nUsage: %s [options] -k name.pfx device [file]\n\n"
" -i, --inode num Display this inode\n\n"
" -k --keyfile name.pfx Use file name as the user's private key file.\n"
" -e --encrypt Update an encrypted file\n"
" -f --force Use less caution\n"
" -h --help Print this help\n"
" -q --quiet Less output\n"
@ -188,8 +191,9 @@ static void usage(void)
*/
static int parse_options(int argc, char **argv)
{
static const char *sopt = "-fh?i:k:qVv";
static const char *sopt = "-fh?ei:k:qVv";
static const struct option lopt[] = {
{"encrypt", no_argument, NULL, 'e'},
{"force", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"inode", required_argument, NULL, 'i'},
@ -222,6 +226,9 @@ static int parse_options(int argc, char **argv)
err++;
}
break;
case 'e':
opts.encrypt++;
break;
case 'f':
opts.force++;
break;
@ -893,6 +900,25 @@ static void ntfs_desx_decrypt(ntfs_fek *fek, u8 *outbuf, const u8 *inbuf)
*(u64*)outbuf ^= ctx->in_whitening;
}
/**
* ntfs_desx_encrypt
*/
static void ntfs_desx_encrypt(ntfs_fek *fek, u8 *outbuf, const u8 *inbuf)
{
gcry_error_t err;
ntfs_desx_ctx *ctx = &fek->desx_ctx;
err = gcry_cipher_reset(fek->gcry_cipher_hd);
if (err != GPG_ERR_NO_ERROR)
ntfs_log_error("Failed to reset des cipher (error 0x%x).\n",
err);
*(u64*)outbuf = *(const u64*)inbuf ^ ctx->in_whitening;
err = gcry_cipher_decrypt(fek->gcry_cipher_hd, outbuf, 8, NULL, 0);
if (err != GPG_ERR_NO_ERROR)
ntfs_log_error("Des decryption failed (error 0x%x).\n", err);
*(u64*)outbuf ^= ctx->out_whitening;
}
//#define DO_CRYPTO_TESTS 1
#ifdef DO_CRYPTO_TESTS
@ -1283,6 +1309,48 @@ static int ntfs_fek_decrypt_sector(ntfs_fek *fek, u8 *data, const u64 offset)
return 512;
}
/**
* ntfs_fek_encrypt_sector
*/
static int ntfs_fek_encrypt_sector(ntfs_fek *fek, u8 *data, const u64 offset)
{
gcry_error_t err;
err = gcry_cipher_reset(fek->gcry_cipher_hd);
if (err != GPG_ERR_NO_ERROR) {
ntfs_log_error("Failed to reset cipher: %s\n",
gcry_strerror(err));
return -1;
}
/*
* Note: You may wonder why we are not calling gcry_cipher_setiv() here
* instead of doing it by hand after the decryption. The answer is
* that gcry_cipher_setiv() wants an iv of length 8 bytes but we give
* it a length of 16 for AES256 so it does not like it.
*/
/* Apply the IV. */
if (fek->alg_id == CALG_AES_256) {
((le64*)data)[0] ^= cpu_to_le64(0x5816657be9161312ULL + offset);
((le64*)data)[1] ^= cpu_to_le64(0x1989adbe44918961ULL + offset);
} else {
/* All other algos (Des, 3Des, DesX) use the same IV. */
((le64*)data)[0] ^= cpu_to_le64(0x169119629891ad13ULL + offset);
}
if (fek->alg_id == CALG_DESX) {
int k;
for (k=0; k<512; k+=8) {
ntfs_desx_encrypt(fek, &data[k], &data[k]);
}
} else
err = gcry_cipher_encrypt(fek->gcry_cipher_hd, data, 512, NULL, 0);
if (err != GPG_ERR_NO_ERROR) {
ntfs_log_error("Encryption failed: %s\n", gcry_strerror(err));
return -1;
}
return 512;
}
/**
* ntfs_cat_decrypt - Decrypt the contents of an encrypted file to stdout.
* @inode: An encrypted file's inode structure, as obtained by
@ -1352,6 +1420,104 @@ static int ntfs_cat_decrypt(ntfs_inode *inode, ntfs_fek *fek)
return 0;
}
/**
* ntfs_feed_encrypt - Encrypt the contents of stdin to an encrypted file
* @inode: An encrypted file's inode structure, as obtained by
* ntfs_inode_open().
* @fek: A file encryption key. As obtained by ntfs_inode_fek_get().
*/
static int ntfs_feed_encrypt(ntfs_inode *inode, ntfs_fek *fek)
{
const int bufsize = 512;
unsigned char *buffer;
ntfs_attr *attr;
s64 bytes_read, written, offset, total;
unsigned char *b;
long val;
int count;
int i;
buffer = (unsigned char*)malloc(bufsize);
if (!buffer)
return 1;
attr = ntfs_attr_open(inode, AT_DATA, NULL, 0);
if (!attr) {
ntfs_log_error("Cannot feed into a directory.\n");
goto rejected;
}
total = 0;
if (!(attr->data_flags & ATTR_IS_ENCRYPTED)) {
ntfs_log_error("The data stream was not encrypted\n");
goto rejected;
}
inode->vol->efs_raw = TRUE;
if (ntfs_attr_truncate(attr, 0)) {
ntfs_log_error("Failed to truncate the data stream\n");
goto rejected;
}
offset = 0;
do {
bytes_read = fread(buffer, 1, bufsize, stdin);
if (bytes_read <= 0) {
if (bytes_read < 0)
ntfs_log_perror("ERROR: Couldn't read data");
} else {
if (bytes_read < bufsize) {
/* Fill with random data */
srandom((unsigned int)(sle64_to_cpu(
inode->last_data_change_time)
/100000000));
count = bufsize - bytes_read;
b = &buffer[bytes_read];
do {
val = random();
switch (count) {
default :
*b++ = val;
val >>= 8;
case 3 :
*b++ = val;
val >>= 8;
case 2 :
*b++ = val;
val >>= 8;
case 1 :
*b++ = val;
val >>= 8;
}
count -= 4;
} while (count > 0);
}
if ((i = ntfs_fek_encrypt_sector(fek, buffer, offset))
< bufsize) {
ntfs_log_perror("ERROR: Couldn't encrypt all data!");
ntfs_log_error("%u/%lld/%lld/%lld\n", i,
(long long)bytes_read, (long long)offset,
(long long)total);
break;
}
written = ntfs_attr_pwrite(attr, offset, bufsize, buffer);
if (written != bufsize) {
ntfs_log_perror("ERROR: Couldn't output all data!");
break;
}
offset += bufsize;
total += bytes_read;
}
} while (bytes_read == bufsize);
ntfs_attr_truncate(attr, total);
inode->last_data_change_time = ntfs_current_time();
NAttrSetEncrypted(attr);
ntfs_attr_close(attr);
free(buffer);
return 0;
rejected :
free(buffer);
return (-1);
}
/**
* main - Begin here
*
@ -1412,7 +1578,8 @@ int main(int argc, char *argv[])
return 1;
}
/* Mount the ntfs volume. */
vol = utils_mount_volume(opts.device, NTFS_MNT_RDONLY |
vol = utils_mount_volume(opts.device,
(opts.encrypt ? 0 : NTFS_MNT_RDONLY) |
(opts.force ? NTFS_MNT_RECOVER : 0));
if (!vol) {
ntfs_log_error("Failed to mount ntfs volume. Aborting.\n");
@ -1437,7 +1604,10 @@ int main(int argc, char *argv[])
sizeof(thumbprint), df_type);
ntfs_rsa_private_key_release(rsa_key);
if (fek) {
res = ntfs_cat_decrypt(inode, fek);
if (opts.encrypt)
res = ntfs_feed_encrypt(inode, fek);
else
res = ntfs_cat_decrypt(inode, fek);
ntfs_fek_release(fek);
} else {
ntfs_log_error("Failed to obtain file encryption key. "