From 2d645bae536fdb4fdd4a348ecbb1effcf23cc7fc Mon Sep 17 00:00:00 2001 From: Yura Pakhuchiy Date: Thu, 27 Sep 2007 16:36:53 +0300 Subject: [PATCH] Apply latest CVS on top. I will rewrite history properly later. --- ChangeLog | 9 ++ libntfs/bootsect.c | 24 ++-- ntfsprogs/ntfsdecrypt.c | 267 +++++++++++++++++++++++++++++++--------- 3 files changed, 231 insertions(+), 69 deletions(-) diff --git a/ChangeLog b/ChangeLog index c302d4a0..d7b01198 100644 --- a/ChangeLog +++ b/ChangeLog @@ -133,6 +133,15 @@ xx/03/2007 - 2.0.0 - ntfsmount sports full r/w and ntfsresize supports Vista. - ntfsmount: st_block calculation was wrong. (Yuval) - mkntfs: Use Vista $Boot rather than XP one. (Anton) - ntfsmount: use utimens() instead of deprecated utime(). (Yura) + - libntfs: Ignore bootsector checksum inconsistencies as too many 3rd + party utilities update the boot sector without correcting the + checksum. Ported change from kernel driver. (Anton) + - ntfsdecrypt: Major improvement! We now validate the .pfx file to be + the right type of RSA key and we also determine the thumbprint and + compare the one in the keys to the one in the .pfx file thus we only + try to decrypt the FEK once we have the correct key. Several other + bug fixes leading to crashes. Also make compilation work on OS X + when gnutls is otherwise not found. (Anton, Yura) 21/06/2006 - 1.13.1 - Various fixes. diff --git a/libntfs/bootsect.c b/libntfs/bootsect.c index 90a30f01..4278ac1f 100644 --- a/libntfs/bootsect.c +++ b/libntfs/bootsect.c @@ -63,22 +63,26 @@ BOOL ntfs_boot_sector_is_ntfs(NTFS_BOOT_SECTOR *b, ntfs_log_debug("\nBeginning bootsector check...\n"); - /* Calculate the checksum. Note, this is just a simple addition of - all u32 values in the bootsector starting at the beginning and - finishing at the offset of the checksum itself (i.e. not including - the checksum...). */ - if ((void*)b < (void*)&b->checksum) { + /* + * Check that checksum == sum of u32 values from b to the checksum + * field. If checksum is zero, no checking is done. We will work when + * the checksum test fails, since some utilities update the boot sector + * ignoring the checksum which leaves the checksum out-of-date. We + * report a warning if this is the case. + */ + if ((void*)b < (void*)&b->checksum && b->checksum) { u32 *u = (u32 *)b; u32 *bi = (u32 *)(&b->checksum); ntfs_log_debug("Calculating bootsector checksum... "); - for (i = 0; u < bi; ++u) i += le32_to_cpup(u); - - if (le32_to_cpu(b->checksum) && le32_to_cpu(b->checksum) != i) - goto not_ntfs; - ntfs_log_debug("OK\n"); + if (le32_to_cpu(b->checksum) && le32_to_cpu(b->checksum) != i) { + ntfs_log_debug("FAILED\n"); + ntfs_log_debug("The NTFS bootsector contains an " + "incorrect checksum."); + } else + ntfs_log_debug("OK\n"); } /* Check OEMidentifier is "NTFS " */ diff --git a/ntfsprogs/ntfsdecrypt.c b/ntfsprogs/ntfsdecrypt.c index cd0e72b9..81c596fe 100644 --- a/ntfsprogs/ntfsdecrypt.c +++ b/ntfsprogs/ntfsdecrypt.c @@ -2,7 +2,7 @@ * ntfsdecrypt - Decrypt ntfs encrypted files. Part of the Linux-NTFS project. * * Copyright (c) 2005 Yuval Fledel - * Copyright (c) 2005 Anton Altaparmakov + * Copyright (c) 2005-2007 Anton Altaparmakov * * This utility will decrypt files and print the decrypted data on the standard * output. @@ -66,6 +66,19 @@ typedef gcry_sexp_t ntfs_rsa_private_key; +#define NTFS_SHA1_THUMBPRINT_SIZE 0x14 + +#define NTFS_CRED_TYPE_CERT_THUMBPRINT const_cpu_to_le32(3) + +#define NTFS_EFS_CERT_PURPOSE_OID_DDF "1.3.6.1.4.1.311.10.3.4" +#define NTFS_EFS_CERT_PURPOSE_OID_DRF "1.3.6.1.4.1.311.10.3.4.1" + +typedef enum { + DF_TYPE_UNKNOWN, + DF_TYPE_DDF, + DF_TYPE_DRF, +} NTFS_DF_TYPES; + /** * enum NTFS_CRYPTO_ALGORITHMS - List of crypto algorithms used by EFS (32 bit) * @@ -439,19 +452,34 @@ static ntfs_rsa_private_key ntfs_rsa_private_key_import_from_gnutls( return (ntfs_rsa_private_key)rsa_key; } +/** + * ntfs_rsa_private_key_release + */ +static void ntfs_rsa_private_key_release(ntfs_rsa_private_key rsa_key) +{ + gcry_sexp_release((gcry_sexp_t)rsa_key); +} + /** * ntfs_pkcs12_extract_rsa_key */ static ntfs_rsa_private_key ntfs_pkcs12_extract_rsa_key(u8 *pfx, int pfx_size, - char *password) + char *password, char *thumbprint, int thumbprint_size, + NTFS_DF_TYPES *df_type) { int err, bag_index, flags; gnutls_datum_t dpfx, dkey; - gnutls_pkcs12_t pkcs12; - gnutls_pkcs12_bag_t bag; - gnutls_x509_privkey_t pkey; + gnutls_pkcs12_t pkcs12 = NULL; + gnutls_pkcs12_bag_t bag = NULL; + gnutls_x509_privkey_t pkey = NULL; + gnutls_x509_crt_t crt = NULL; ntfs_rsa_private_key rsa_key = NULL; + char purpose_oid[100]; + size_t purpose_oid_size = sizeof(purpose_oid); + size_t tp_size = thumbprint_size; + BOOL have_thumbprint = FALSE; + *df_type = DF_TYPE_UNKNOWN; /* Create a pkcs12 structure. */ err = gnutls_pkcs12_init(&pkcs12); if (err) { @@ -467,7 +495,7 @@ static ntfs_rsa_private_key ntfs_pkcs12_extract_rsa_key(u8 *pfx, int pfx_size, ntfs_log_error("Failed to convert the PFX file from DER to " "native PKCS#12 format: %s\n", gnutls_strerror(err)); - goto out; + goto err; } /* * Verify that the password is correct and that the key file has not @@ -484,9 +512,9 @@ retry_verify: password = NULL; goto retry_verify; } - ntfs_log_error("Failed to verify the MAC (%s). Is the " + ntfs_log_error("Failed to verify the MAC: %s Is the " "password correct?\n", gnutls_strerror(err)); - goto out; + goto err; } for (bag_index = 0; ; bag_index++) { err = gnutls_pkcs12_bag_init(&bag); @@ -494,29 +522,31 @@ retry_verify: ntfs_log_error("Failed to initialize PKCS#12 Bag " "structure: %s\n", gnutls_strerror(err)); - goto out; + goto err; } err = gnutls_pkcs12_get_bag(pkcs12, bag_index, bag); if (err) { - if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + if (err == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + err = 0; break; + } ntfs_log_error("Failed to obtain Bag from PKCS#12 " "structure: %s\n", gnutls_strerror(err)); - goto bag_out; + goto err; } check_again: err = gnutls_pkcs12_bag_get_count(bag); if (err < 0) { ntfs_log_error("Failed to obtain Bag count: %s\n", gnutls_strerror(err)); - goto bag_out; + goto err; } err = gnutls_pkcs12_bag_get_type(bag, 0); if (err < 0) { ntfs_log_error("Failed to determine Bag type: %s\n", gnutls_strerror(err)); - goto bag_out; + goto err; } flags = 0; switch (err) { @@ -527,14 +557,14 @@ check_again: if (err < 0) { ntfs_log_error("Failed to obtain Bag data: " "%s\n", gnutls_strerror(err)); - goto bag_out; + goto err; } err = gnutls_x509_privkey_init(&pkey); if (err) { ntfs_log_error("Failed to initialized " "private key structure: %s\n", gnutls_strerror(err)); - goto bag_out; + goto err; } /* Decrypt the private key into GNU TLS format. */ err = gnutls_x509_privkey_import_pkcs8(pkey, &dkey, @@ -544,7 +574,7 @@ check_again: "key from DER to GNU TLS " "format: %s\n", gnutls_strerror(err)); - goto key_out; + goto err; } #if 0 /* @@ -572,38 +602,110 @@ check_again: #endif /* Convert the private key to our internal format. */ rsa_key = ntfs_rsa_private_key_import_from_gnutls(pkey); - goto key_out; + if (!rsa_key) + goto err; + break; case GNUTLS_BAG_ENCRYPTED: err = gnutls_pkcs12_bag_decrypt(bag, password); if (err) { ntfs_log_error("Failed to decrypt Bag: %s\n", gnutls_strerror(err)); - goto bag_out; + goto err; } goto check_again; + case GNUTLS_BAG_CERTIFICATE: + err = gnutls_pkcs12_bag_get_data(bag, 0, &dkey); + if (err < 0) { + ntfs_log_error("Failed to obtain Bag data: " + "%s\n", gnutls_strerror(err)); + goto err; + } + err = gnutls_x509_crt_init(&crt); + if (err) { + ntfs_log_error("Failed to initialize " + "certificate structure: %s\n", + gnutls_strerror(err)); + goto err; + } + err = gnutls_x509_crt_import(crt, &dkey, + GNUTLS_X509_FMT_DER); + if (err) { + ntfs_log_error("Failed to convert certificate " + "from DER to GNU TLS format: " + "%s\n", gnutls_strerror(err)); + goto err; + } + err = gnutls_x509_crt_get_key_purpose_oid(crt, 0, + purpose_oid, &purpose_oid_size, NULL); + if (err) { + ntfs_log_error("Failed to get key purpose " + "OID: %s\n", + gnutls_strerror(err)); + goto err; + } + purpose_oid[purpose_oid_size - 1] = '\0'; + if (!strncmp(purpose_oid, + NTFS_EFS_CERT_PURPOSE_OID_DRF, + strlen( + NTFS_EFS_CERT_PURPOSE_OID_DRF))) + *df_type = DF_TYPE_DRF; + else if (!strncmp(purpose_oid, + NTFS_EFS_CERT_PURPOSE_OID_DDF, + strlen( + NTFS_EFS_CERT_PURPOSE_OID_DDF))) + *df_type = DF_TYPE_DDF; + else { + ntfs_log_error("Certificate has unknown " + "purpose OID %s.\n", + purpose_oid); + err = EINVAL; + goto err; + } + /* Return the thumbprint to the caller. */ + err = gnutls_x509_crt_get_fingerprint(crt, + GNUTLS_DIG_SHA1, thumbprint, &tp_size); + if (err) { + ntfs_log_error("Failed to get thumbprint: " + "%s\n", gnutls_strerror(err)); + goto err; + } + if (tp_size != NTFS_SHA1_THUMBPRINT_SIZE) { + ntfs_log_error("Invalid thumbprint size %zd. " + "Should be %d.\n", tp_size, + thumbprint_size); + err = EINVAL; + goto err; + } + have_thumbprint = TRUE; + gnutls_x509_crt_deinit(crt); + crt = NULL; + break; default: /* We do not care about other types. */ break; } gnutls_pkcs12_bag_deinit(bag); } -key_out: - gnutls_x509_privkey_deinit(pkey); -bag_out: - gnutls_pkcs12_bag_deinit(bag); -out: - gnutls_pkcs12_deinit(pkcs12); +err: + if (rsa_key && (err || *df_type == DF_TYPE_UNKNOWN || + !have_thumbprint)) { + if (!err) + ntfs_log_error("Key type or thumbprint not found, " + "aborting.\n"); + ntfs_rsa_private_key_release(rsa_key); + rsa_key = NULL; + } + if (crt) + gnutls_x509_crt_deinit(crt); + if (pkey) + gnutls_x509_privkey_deinit(pkey); + if (bag) + gnutls_pkcs12_bag_deinit(bag); + if (pkcs12) + gnutls_pkcs12_deinit(pkcs12); return rsa_key; } -/** - * ntfs_rsa_private_key_release - */ -static void ntfs_rsa_private_key_release(ntfs_rsa_private_key rsa_key) -{ - gcry_sexp_release((gcry_sexp_t)rsa_key); -} - /** * ntfs_buffer_reverse - * @@ -964,16 +1066,20 @@ static inline BOOL ntfs_des_test(void) /** * ntfs_fek_import_from_raw */ -static ntfs_fek *ntfs_fek_import_from_raw(u8 *fek_buf, - unsigned fek_size __attribute__((unused))) +static ntfs_fek *ntfs_fek_import_from_raw(u8 *fek_buf, unsigned fek_size) { ntfs_fek *fek; u32 key_size, wanted_key_size, gcry_algo; gcry_error_t err; - // TODO: Sanity checking of sizes and offsets. key_size = le32_to_cpup(fek_buf); - //ntfs_log_debug("key_size 0x%x\n", key_size); + ntfs_log_debug("key_size 0x%x\n", key_size); + if (key_size + 16 > fek_size) { + ntfs_log_debug("Invalid FEK. It was probably decrypted with " + "the incorrect RSA key."); + errno = EINVAL; + return NULL; + } fek = malloc(((((sizeof(*fek) + 7) & ~7) + key_size + 7) & ~7) + sizeof(gcry_cipher_hd_t)); if (!fek) { @@ -1032,7 +1138,8 @@ static ntfs_fek *ntfs_fek_import_from_raw(u8 *fek_buf, } if (key_size != wanted_key_size) { ntfs_log_error("%s key of %u bytes but needed size is %u " - "bytes, assuming corrupt key. Aborting.\n", + "bytes, assuming corrupt or incorrect key. " + "Aborting.\n", gcry_cipher_algo_name(gcry_algo), (unsigned)key_size, (unsigned)wanted_key_size); err = EIO; @@ -1076,7 +1183,8 @@ static void ntfs_fek_release(ntfs_fek *fek) * ntfs_df_array_fek_get */ static ntfs_fek *ntfs_df_array_fek_get(EFS_DF_ARRAY_HEADER *df_array, - ntfs_rsa_private_key rsa_key) + ntfs_rsa_private_key rsa_key, char *thumbprint, + int thumbprint_size) { EFS_DF_HEADER *df_header; EFS_DF_CREDENTIAL_HEADER *df_cred; @@ -1086,14 +1194,41 @@ static ntfs_fek *ntfs_df_array_fek_get(EFS_DF_ARRAY_HEADER *df_array, u32 df_count, fek_size; unsigned i; - df_header = (EFS_DF_HEADER*)(df_array + 1); df_count = le32_to_cpu(df_array->df_count); - for (i = 0; i < df_count; i++) { + if (!df_count) + ntfs_log_error("There are no elements in the DF array.\n"); + df_header = (EFS_DF_HEADER*)(df_array + 1); + for (i = 0; i < df_count; i++, df_header = (EFS_DF_HEADER*)( + (u8*)df_header + le32_to_cpu(df_header->df_length))) { df_cred = (EFS_DF_CREDENTIAL_HEADER*)((u8*)df_header + le32_to_cpu(df_header->cred_header_offset)); + if (df_cred->type != NTFS_CRED_TYPE_CERT_THUMBPRINT) { + ntfs_log_debug("Credential type is not certificate " + "thumbprint, skipping DF entry.\n"); + continue; + } df_cert = (EFS_DF_CERT_THUMBPRINT_HEADER*)((u8*)df_cred + le32_to_cpu( df_cred->cert_thumbprint_header_offset)); + if (le32_to_cpu(df_cert->thumbprint_size) != thumbprint_size) { + ntfs_log_error("Thumbprint size %d is not valid " + "(should be %d), skipping this DF " + "entry.\n", + le32_to_cpu(df_cert->thumbprint_size), + thumbprint_size); + continue; + } + if (memcmp((u8*)df_cert + + le32_to_cpu(df_cert->thumbprint_offset), + thumbprint, thumbprint_size)) { + ntfs_log_debug("Thumbprints do not match, skipping " + "this DF entry.\n"); + continue; + } + /* + * The thumbprints match so this is probably the DF entry + * matching the RSA key. Try to decrypt the FEK with it. + */ fek_size = le32_to_cpu(df_header->fek_size); fek_buf = (u8*)df_header + le32_to_cpu(df_header->fek_offset); /* Decrypt the FEK. Note: This is done in place. */ @@ -1108,8 +1243,6 @@ static ntfs_fek *ntfs_df_array_fek_get(EFS_DF_ARRAY_HEADER *df_array, } else ntfs_log_error("Failed to decrypt the file " "encryption key.\n"); - df_header = (EFS_DF_HEADER*)((u8*)df_header + - le32_to_cpu(df_header->df_length)); } return NULL; } @@ -1118,10 +1251,11 @@ static ntfs_fek *ntfs_df_array_fek_get(EFS_DF_ARRAY_HEADER *df_array, * ntfs_inode_fek_get - */ static ntfs_fek *ntfs_inode_fek_get(ntfs_inode *inode, - ntfs_rsa_private_key rsa_key) + ntfs_rsa_private_key rsa_key, char *thumbprint, + int thumbprint_size, NTFS_DF_TYPES df_type) { EFS_ATTR_HEADER *efs; - EFS_DF_ARRAY_HEADER *df_array; + EFS_DF_ARRAY_HEADER *df_array = NULL; ntfs_fek *fek = NULL; /* Obtain the $EFS contents. */ @@ -1130,17 +1264,29 @@ static ntfs_fek *ntfs_inode_fek_get(ntfs_inode *inode, ntfs_log_perror("Failed to read $EFS attribute"); return NULL; } - /* Iterate through the DDFs & DRFs until we obtain a key. */ - if (efs->offset_to_ddf_array) { - df_array = (EFS_DF_ARRAY_HEADER*)((u8*)efs + - le32_to_cpu(efs->offset_to_ddf_array)); - fek = ntfs_df_array_fek_get(df_array, rsa_key); - } - if (!fek && efs->offset_to_drf_array) { - df_array = (EFS_DF_ARRAY_HEADER*)((u8*)efs + - le32_to_cpu(efs->offset_to_drf_array)); - fek = ntfs_df_array_fek_get(df_array, rsa_key); - } + /* + * Depending on whether the key is a normal key or a data recovery key, + * iterate through the DDF or DRF array, respectively. + */ + if (df_type == DF_TYPE_DDF) { + if (efs->offset_to_ddf_array) + df_array = (EFS_DF_ARRAY_HEADER*)((u8*)efs + + le32_to_cpu(efs->offset_to_ddf_array)); + else + ntfs_log_error("There are no entries in the DDF " + "array.\n"); + } else if (df_type == DF_TYPE_DRF) { + if (efs->offset_to_drf_array) + df_array = (EFS_DF_ARRAY_HEADER*)((u8*)efs + + le32_to_cpu(efs->offset_to_drf_array)); + else + ntfs_log_error("There are no entries in the DRF " + "array.\n"); + } else + ntfs_log_error("Invalid DF type.\n"); + if (df_array) + fek = ntfs_df_array_fek_get(df_array, rsa_key, thumbprint, + thumbprint_size); free(efs); return fek; } @@ -1267,6 +1413,8 @@ int main(int argc, char *argv[]) ntfs_fek *fek; unsigned pfx_size; int res; + NTFS_DF_TYPES df_type; + char thumbprint[NTFS_SHA1_THUMBPRINT_SIZE]; ntfs_log_set_handler(ntfs_log_handler_stderr); @@ -1295,14 +1443,14 @@ int main(int argc, char *argv[]) return 1; } /* Obtain the user's private RSA key from the key file. */ - rsa_key = ntfs_pkcs12_extract_rsa_key(pfx_buf, pfx_size, password); + rsa_key = ntfs_pkcs12_extract_rsa_key(pfx_buf, pfx_size, password, + thumbprint, sizeof(thumbprint), &df_type); /* Destroy the password. */ memset(password, 0, strlen(password)); /* No longer need the pfx file contents. */ free(pfx_buf); if (!rsa_key) { - ntfs_log_error("Failed to extract the private RSA key. Did " - "you perhaps mistype the password?\n"); + ntfs_log_error("Failed to extract the private RSA key.\n"); ntfs_crypto_deinit(); return 1; } @@ -1328,7 +1476,8 @@ int main(int argc, char *argv[]) return 1; } /* Obtain the file encryption key of the encrypted file. */ - fek = ntfs_inode_fek_get(inode, rsa_key); + fek = ntfs_inode_fek_get(inode, rsa_key, thumbprint, + sizeof(thumbprint), df_type); ntfs_rsa_private_key_release(rsa_key); if (fek) { res = ntfs_cat_decrypt(inode, fek);