From aea237a632cdf7bccf1a341f72210e2462c3cc3b Mon Sep 17 00:00:00 2001 From: Thomas Fussell Date: Sun, 23 Oct 2016 22:40:05 -0400 Subject: [PATCH] implement ooxml decryption (standard and agile) --- source/detail/xlsx_crypto.cpp | 821 ++++++++++++------ source/workbook/tests/test_consume_xlsx.hpp | 13 +- ...pted.xlsx => 14_encrypted_excel_2016.xlsx} | Bin tests/data/15_encryped_libre_office.xlsx | Bin 0 -> 8704 bytes tests/data/16_encrypted_excel_2007.xlsx | Bin 0 -> 12800 bytes 5 files changed, 549 insertions(+), 285 deletions(-) rename tests/data/{14_encrypted.xlsx => 14_encrypted_excel_2016.xlsx} (100%) create mode 100644 tests/data/15_encryped_libre_office.xlsx create mode 100644 tests/data/16_encrypted_excel_2007.xlsx diff --git a/source/detail/xlsx_crypto.cpp b/source/detail/xlsx_crypto.cpp index 20fe6d38..0e4a06bb 100644 --- a/source/detail/xlsx_crypto.cpp +++ b/source/detail/xlsx_crypto.cpp @@ -2,56 +2,405 @@ #include #include -#include #include +#include +#include +#include #include +#include #include namespace xlnt { namespace detail { +static const std::size_t segment_length = 4096; + enum class cipher_algorithm { - AES, - RC2, - RC4, - DES, - DESX, - TripleDES, - TripleDES_112 + aes, + rc2, + rc4, + des, + desx, + triple_des, + triple_des_112 }; enum class cipher_chaining { - ECB, - CBC, - CFB + ecb, // electronic code book + cbc, // cipher block chaining + cfb // cipher feedback chaining }; enum class hash_algorithm { - SHA1, - SHA256, - SHA384, - SHA512, - MD5, - MD4, - MD2, - RIPEMD128, - RIPEMD160, - WHIRLPOOL + sha1, + sha256, + sha384, + sha512, + md5, + md4, + md2, + ripemd128, + ripemd160, + whirlpool }; -enum class encryption_algorithm +std::vector rijndael_ecb_decrypt(std::vector key, + const std::vector &encrypted) { - aes_128, - aes_192, - aes_256, - sha_1, - sha_512 + static const CK_MECHANISM_TYPE mechanism = CKM_AES_ECB; + static const CK_ATTRIBUTE_TYPE direction = CKA_DECRYPT; + + // IV (null) + auto nss_iv_param = PK11_ParamFromIV(mechanism, nullptr); + + // key + SECItem nss_key_item{ siBuffer, key.data(), static_cast(key.size()) }; + auto nss_key = PK11_ImportSymKey(PK11_GetBestSlot(mechanism, nullptr), + mechanism, PK11_OriginUnwrap, direction, &nss_key_item, nullptr); + + // context + auto nss_context = PK11_CreateContextBySymKey(mechanism, direction, nss_key, nss_iv_param); + + // decrypt + std::vector decrypted(encrypted.size(), 0); + int output_length; + PK11_CipherOp(nss_context, decrypted.data(), &output_length, + static_cast(encrypted.size()), encrypted.data(), + static_cast(encrypted.size())); + + // clean up + PK11_DestroyContext(nss_context, PR_TRUE); + PK11_FreeSymKey(nss_key); + SECITEM_FreeItem(nss_iv_param, PR_TRUE); + + return decrypted; +} + +std::vector rijndael_cbc_decrypt(std::vector key, + std::vector iv, const std::vector &encrypted) +{ + static const CK_MECHANISM_TYPE mechanism = CKM_AES_CBC; + static const CK_ATTRIBUTE_TYPE direction = CKA_DECRYPT; + + // IV + SECItem nss_iv_item{ siBuffer, iv.data(), + static_cast(iv.size()) }; + auto nss_iv_param = PK11_ParamFromIV(mechanism, &nss_iv_item); + + // key + SECItem nss_key_item{ siBuffer, key.data(), + static_cast(key.size()) }; + auto nss_key = PK11_ImportSymKey(PK11_GetBestSlot(mechanism, nullptr), + mechanism, PK11_OriginUnwrap, direction, &nss_key_item, nullptr); + + // context + auto nss_context = PK11_CreateContextBySymKey(mechanism, direction, nss_key, nss_iv_param); + + // decrypt + std::vector decrypted(encrypted.size(), 0); + int output_length; + PK11_CipherOp(nss_context, decrypted.data(), &output_length, + static_cast(encrypted.size()), encrypted.data(), + static_cast(encrypted.size())); + + // clean up + PK11_DestroyContext(nss_context, PR_TRUE); + PK11_FreeSymKey(nss_key); + SECITEM_FreeItem(nss_iv_param, PR_TRUE); + + return decrypted; }; +// Adapted from https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64 +// This function is public domain +std::vector decode_base64(const std::string &encoded) +{ + if (encoded.length() % 4) + { + throw xlnt::exception("invalid base64"); + } + + std::size_t padding = 0; + + if (!encoded.empty()) + { + if (encoded[encoded.length() - 1] == '=') padding++; + if (encoded[encoded.length() - 2] == '=') padding++; + } + + std::vector decoded(((encoded.length() / 4) * 3) - padding, 0); + auto decoded_iter = decoded.begin(); + + std::uint32_t temp = 0; + + for (auto encoded_iter = encoded.begin(); encoded_iter != encoded.end();) + { + for (std::size_t quantumPosition = 0; quantumPosition < 4; quantumPosition++) + { + auto current_char = *encoded_iter; + temp <<= 6; + + // convert character into index from 0 to 63 + if (current_char >= 'A' && current_char <= 'Z') + { + temp |= current_char - 'A'; + } + else if (current_char >= 'a' && current_char <= 'z') + { + temp |= current_char - 71; + } + else if (current_char >= '0' && current_char <= '9') + { + temp |= current_char + 4; + } + else if (current_char == '+') + { + temp |= 62; + } + else if (current_char == '/') + { + temp |= 63; + } + else if (current_char == '=') + { + switch (encoded.end() - encoded_iter) + { + case 1: // one pad character + *(decoded_iter++) = (temp >> 16) & 0x000000ff; + *(decoded_iter++) = (temp >> 8) & 0x000000ff; + return decoded; + case 2: // two pad characters + *(decoded_iter++) = (temp >> 10) & 0x000000ff; + return decoded; + default: + throw std::runtime_error("Invalid Padding in Base 64!"); + } + } + else + { + throw std::runtime_error("Non-Valid Character in Base 64!"); + } + + ++encoded_iter; + } + + // split lower 24 bits into 3 bytes + *(decoded_iter++) = (temp >> 16) & 0x000000FF; + *(decoded_iter++) = (temp >> 8) & 0x000000FF; + *(decoded_iter++) = (temp) & 0x000000FF; + } + + return decoded; +}; + +std::vector get_file(POLE::Storage &storage, const std::string &name) +{ + POLE::Stream stream(&storage, name.c_str()); + if (stream.fail()) return {}; + std::vector bytes(stream.size(), 0); + stream.read(bytes.data(), static_cast(bytes.size())); + return bytes; +} + +template +std::vector hash(hash_algorithm algorithm, InIter begin, InIter end) +{ + HASH_HashType hash_type = HASH_HashType::HASH_AlgNULL; + std::size_t out_length = 0; + + if (algorithm == hash_algorithm::sha1) + { + hash_type = HASH_HashType::HASH_AlgSHA1; + out_length = SHA1_LENGTH; + } + else if (algorithm == hash_algorithm::sha512) + { + hash_type = HASH_HashType::HASH_AlgSHA512; + out_length = SHA512_LENGTH; + } + else if (algorithm == hash_algorithm::sha256) + { + hash_type = HASH_HashType::HASH_AlgSHA256; + out_length = SHA256_LENGTH; + } + else if (algorithm == hash_algorithm::sha384) + { + hash_type = HASH_HashType::HASH_AlgSHA384; + out_length = SHA384_LENGTH; + } + + auto context = HASH_Create(hash_type); + HASH_Begin(context); + std::vector input(begin, end); + HASH_Update(context, input.data(), static_cast(input.size())); + unsigned int write_length; + std::vector result(out_length, 0); + HASH_End(context, result.data(), &write_length, static_cast(out_length)); + HASH_Destroy(context); + + return result; +} + +template +auto read_int(std::size_t &index, const std::vector &raw_data) +{ + auto result = *reinterpret_cast(&raw_data[index]); + index += sizeof(T); + return result; +}; + +struct standard_encryption_info +{ + const std::size_t spin_count = 50000; + std::size_t block_size; + std::size_t key_bits; + std::size_t key_bytes; + std::size_t hash_size; + cipher_algorithm cipher; + cipher_chaining chaining; + const hash_algorithm hash = hash_algorithm::sha1; + std::vector salt_value; + std::vector verifier_hash_input; + std::vector verifier_hash_value; + std::vector encrypted_key_value; +}; + +std::vector decrypt_xlsx_standard(const std::vector &encryption_info, + const std::string &password, const std::vector &encrypted_package) +{ + std::size_t offset = 0; + + standard_encryption_info info; + + auto header_length = read_int(offset, encryption_info); + auto index_at_start = offset; + auto skip_flags = read_int(offset, encryption_info); + auto size_extra = read_int(offset, encryption_info); + auto alg_id = read_int(offset, encryption_info); + + if (alg_id == 0 || alg_id == 0x0000660E || alg_id == 0x0000660F || alg_id == 0x00006610) + { + info.cipher = cipher_algorithm::aes; + } + else + { + throw xlnt::exception("invalid cipher algorithm"); + } + + auto alg_id_hash = read_int(offset, encryption_info); + if (alg_id_hash != 0x00008004 && alg_id_hash == 0) + { + throw xlnt::exception("invalid hash algorithm"); + } + + info.key_bits = read_int(offset, encryption_info); + info.key_bytes = info.key_bits / 8; + + auto provider_type = read_int(offset, encryption_info); + if (provider_type != 0 && provider_type != 0x00000018) + { + throw xlnt::exception("invalid provider type"); + } + + read_int(offset, encryption_info); // reserved 1 + if (read_int(offset, encryption_info) != 0) // reserved 2 + { + throw xlnt::exception("invalid header"); + } + + const auto csp_name_length = header_length - (offset - index_at_start); + std::vector csp_name_wide( + reinterpret_cast(&*(encryption_info.begin() + offset)), + reinterpret_cast(&*(encryption_info.begin() + offset + csp_name_length))); + std::string csp_name(csp_name_wide.begin(), csp_name_wide.end() - 1); // without trailing null + if (csp_name != "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)" + && csp_name != "Microsoft Enhanced RSA and AES Cryptographic Provider") + { + throw xlnt::exception("invalid cryptographic provider"); + } + offset += csp_name_length; + + const auto salt_size = read_int(offset, encryption_info); + std::vector salt(encryption_info.begin() + offset, + encryption_info.begin() + offset + salt_size); + offset += salt_size; + + static const auto verifier_size = std::size_t(16); + std::vector verifier_hash_input(encryption_info.begin() + offset, + encryption_info.begin() + offset + verifier_size); + offset += verifier_size; + + const auto verifier_hash_size = read_int(offset, encryption_info); + std::vector verifier_hash_value(encryption_info.begin() + offset, + encryption_info.begin() + offset + 32); + offset += verifier_hash_size; + + // begin key generation algorithm + + // H_0 = H(salt + password) + auto salt_plus_password = salt; + std::vector password_wide(password.begin(), password.end()); + std::for_each(password_wide.begin(), password_wide.end(), + [&salt_plus_password](std::uint16_t c) + { + salt_plus_password.insert(salt_plus_password.end(), + reinterpret_cast(&c), + reinterpret_cast(&c) + sizeof(std::uint16_t)); + }); + std::vector h_0 = hash(info.hash, + salt_plus_password.begin(), salt_plus_password.end()); + + // H_n = H(iterator + H_n-1) + std::vector iterator_plus_h_n(4, 0); + iterator_plus_h_n.insert(iterator_plus_h_n.end(), h_0.begin(), h_0.end()); + std::uint32_t &iterator = *reinterpret_cast(iterator_plus_h_n.data()); + std::vector h_n; + for (iterator = 0; iterator < info.spin_count; ++iterator) + { + h_n = hash(info.hash, iterator_plus_h_n.begin(), iterator_plus_h_n.end()); + std::copy(h_n.begin(), h_n.end(), iterator_plus_h_n.begin() + 4); + } + + // H_final = H(H_n + block) + auto h_n_plus_block = h_n; + const std::uint32_t block_number = 0; + h_n_plus_block.insert(h_n_plus_block.end(), + reinterpret_cast(&block_number), + reinterpret_cast(&block_number) + sizeof(std::uint32_t)); + auto h_final = hash(info.hash, h_n_plus_block.begin(), h_n_plus_block.end()); + + // X1 = H(h_final ^ 0x36) + std::vector buffer(64, 0x36); + for (std::size_t i = 0; i < h_final.size(); ++i) + { + buffer[i] = static_cast(0x36 ^ h_final[i]); + } + auto X1 = hash(info.hash, buffer.begin(), buffer.end()); + + // X2 = H(h_final ^ 0x5C) + buffer.assign(64, 0x5c); + for (std::size_t i = 0; i < h_final.size(); ++i) + { + buffer[i] = static_cast(0x5c ^ h_final[i]); + } + auto X2 = hash(info.hash, buffer.begin(), buffer.end()); + + auto X3 = X1; + X3.insert(X3.end(), X2.begin(), X2.end()); + + auto key_derived = std::vector(X3.begin(), X3.begin() + info.key_bytes); + + //todo: verify here + + std::vector encrypted_data(encrypted_package.begin() + 8, encrypted_package.end()); + return rijndael_ecb_decrypt(key_derived, encrypted_data); +} + + struct agile_encryption_info { // key data @@ -82,7 +431,7 @@ struct agile_encryption_info std::size_t hash_size; std::string cipher_algorithm; std::string cipher_chaining; - std::string hash_algorithm; + hash_algorithm hash_algorithm; std::vector salt_value; std::vector verifier_hash_input; std::vector verifier_hash_value; @@ -90,161 +439,16 @@ struct agile_encryption_info } key_encryptor; }; -struct standard_encryption_info -{ - std::vector salt; -}; - -std::vector get_file(POLE::Storage &storage, const std::string &name) -{ - POLE::Stream stream(&storage, name.c_str()); - if (stream.fail()) return {}; - std::vector bytes; - - for (std::size_t i = 0; i < stream.size(); ++i) - { - bytes.push_back(stream.getch()); - } - - return bytes; -} - -template -void hash(const std::string &algorithm, InIter begin, InIter end, OutIter out) -{ - Botan::Pipe pipe(new Botan::Hash_Filter(algorithm)); - - pipe.start_msg(); - std::for_each(begin, end, [&pipe](std::uint8_t b) { pipe.write(b); }); - pipe.end_msg(); - - for (auto i : pipe.read_all()) - { - *(out++) = i; - } -} - -template -auto read_int(std::size_t &index, const std::vector &raw_data) -{ - auto result = *reinterpret_cast(&raw_data[index]); - index += sizeof(T); - return result; -}; - -Botan::SymmetricKey generate_standard_encryption_key(const std::vector &raw_data, - std::size_t offset, const std::string &password) -{ - auto header_length = read_int(offset, raw_data); - auto index_at_start = offset; - auto skip_flags = read_int(offset, raw_data); - auto size_extra = read_int(offset, raw_data); - auto alg_id = read_int(offset, raw_data); - auto alg_hash_id = read_int(offset, raw_data); - auto key_bits = read_int(offset, raw_data); - auto provider_type = read_int(offset, raw_data); - /*auto reserved1 = */read_int(offset, raw_data); - /*auto reserved2 = */read_int(offset, raw_data); - - const auto csp_name_length = header_length - (offset - index_at_start); - std::vector csp_name_wide( - reinterpret_cast(&*(raw_data.begin() + offset)), - reinterpret_cast(&*(raw_data.begin() + offset + csp_name_length))); - std::string csp_name(csp_name_wide.begin(), csp_name_wide.end()); - offset += csp_name_length; - - const auto salt_size = read_int(offset, raw_data); - std::vector salt(raw_data.begin() + offset, raw_data.begin() + offset + salt_size); - offset += salt_size; - - static const auto verifier_size = std::size_t(16); - std::vector verifier_hash_input(raw_data.begin() + offset, raw_data.begin() + offset + verifier_size); - offset += verifier_size; - - const auto verifier_hash_size = read_int(offset, raw_data); - std::vector verifier_hash_value(raw_data.begin() + offset, raw_data.begin() + offset + 32); - offset += verifier_hash_size; - - std::string hash_algorithm = "SHA-1"; - - const auto key_size = key_bits / 8; - const auto hash_out_size = hash_algorithm == "SHA-512" ? 64 : 20; - - auto salted_password = salt; - - for (auto c : password) - { - salted_password.push_back(c); - salted_password.push_back(0); - } - - std::vector hash_result(hash_out_size, 0); - hash(hash_algorithm, salted_password.begin(), salted_password.end(), hash_result.begin()); - - std::vector iterator_with_hash(4 + hash_out_size, 0); - std::copy(hash_result.begin(), hash_result.end(), iterator_with_hash.begin() + 4); - - std::uint32_t &iterator = *reinterpret_cast(iterator_with_hash.data()); - static const std::size_t spin_count = 50000; - - for (iterator = 0; iterator < spin_count; ++iterator) - { - hash(hash_algorithm, iterator_with_hash.begin(), iterator_with_hash.end(), hash_result.begin()); - } - - auto hash_with_block_key = hash_result; - std::vector block_key(4, 0); - hash_with_block_key.insert(hash_with_block_key.end(), block_key.begin(), block_key.end()); - hash(hash_algorithm, hash_with_block_key.begin(), hash_with_block_key.end(), hash_result.begin()); - - std::vector key = hash_result; - key.resize(key_size); - - for (std::size_t i = 0; i < key_size; ++i) - { - key[i] = static_cast(i < hash_out_size ? 0x36 ^ key[i] : 0x36); - } - - hash(hash_algorithm, key.begin(), key.end(), key.begin()); - - if (verifier_hash_value.size() <= key_size) - { - std::vector first_part(key.begin(), key.begin() + key_size); - - for (int i = 0; i < key.size(); ++i) - { - key[i] = static_cast(i < 20 ? 0x5C ^ key[i] : 0x5C); - } - - hash(hash_algorithm, key.begin(), key.end(), key.begin() + key_size); - std::copy(first_part.begin(), first_part.end(), key.begin()); - } - - key.resize(key_size, 0); - - //todo: verify here - - return Botan::SymmetricKey(key); -} - -Botan::SymmetricKey generate_agile_encryption_key(const std::vector &raw_data, - std::size_t offset, const std::string &password) +std::vector decrypt_xlsx_agile(const std::vector &encryption_info, + const std::string &password, const std::vector &encrypted_package) { static const auto xmlns = std::string("http://schemas.microsoft.com/office/2006/encryption"); static const auto xmlns_p = std::string("http://schemas.microsoft.com/office/2006/keyEncryptor/password"); static const auto xmlns_c = std::string("http://schemas.microsoft.com/office/2006/keyEncryptor/certificate"); - auto from_base64 = [](const std::string &s) - { - Botan::Pipe base64_pipe(new Botan::Base64_Decoder()); - base64_pipe.process_msg(s); - auto decoded = base64_pipe.read_all(); - return std::vector(decoded.begin(), decoded.end()); - }; - agile_encryption_info result; - xml::parser parser(raw_data.data() + offset, raw_data.size() - offset, "EncryptionInfo"); + xml::parser parser(encryption_info.data(), encryption_info.size(), "EncryptionInfo"); parser.next_expect(xml::parser::event_type::start_element, xmlns, "encryption"); @@ -256,12 +460,12 @@ Botan::SymmetricKey generate_agile_encryption_key(const std::vector("hashSize"); result.key_encryptor.cipher_algorithm = parser.attribute("cipherAlgorithm"); result.key_encryptor.cipher_chaining = parser.attribute("cipherChaining"); - result.key_encryptor.hash_algorithm = parser.attribute("hashAlgorithm"); - result.key_encryptor.salt_value = from_base64(parser.attribute("saltValue")); - result.key_encryptor.verifier_hash_input = from_base64(parser.attribute("encryptedVerifierHashInput")); - result.key_encryptor.verifier_hash_value = from_base64(parser.attribute("encryptedVerifierHashValue")); - result.key_encryptor.encrypted_key_value = from_base64(parser.attribute("encryptedKeyValue")); + + auto hash_algorithm_string = parser.attribute("hashAlgorithm"); + if (hash_algorithm_string == "SHA512") + { + result.key_encryptor.hash_algorithm = hash_algorithm::sha512; + } + else if (hash_algorithm_string == "SHA1") + { + result.key_encryptor.hash_algorithm = hash_algorithm::sha1; + } + else if (hash_algorithm_string == "SHA256") + { + result.key_encryptor.hash_algorithm = hash_algorithm::sha256; + } + else if (hash_algorithm_string == "SHA384") + { + result.key_encryptor.hash_algorithm = hash_algorithm::sha384; + } + + result.key_encryptor.salt_value = decode_base64(parser.attribute("saltValue")); + result.key_encryptor.verifier_hash_input = decode_base64(parser.attribute("encryptedVerifierHashInput")); + result.key_encryptor.verifier_hash_value = decode_base64(parser.attribute("encryptedVerifierHashValue")); + result.key_encryptor.encrypted_key_value = decode_base64(parser.attribute("encryptedKeyValue")); } else { - throw "other encryption key types not supported"; + throw xlnt::unsupported("other encryption key types not supported"); } parser.next_expect(xml::parser::event_type::end_element); @@ -307,146 +529,179 @@ Botan::SymmetricKey generate_agile_encryption_key(const std::vector password_wide(password.begin(), password.end()); + std::for_each(password_wide.begin(), password_wide.end(), + [&salt_plus_password](std::uint16_t c) { - salted_password.push_back(c); - salted_password.push_back(0); - } - - std::vector hash_result(hash_out_size, 0); - hash(result.key_encryptor.hash_algorithm, salted_password.begin(), salted_password.end(), hash_result.begin()); - - std::vector iterator_with_hash(4 + hash_out_size, 0); - std::copy(hash_result.begin(), hash_result.end(), iterator_with_hash.begin() + 4); - - std::uint32_t &iterator = *reinterpret_cast(iterator_with_hash.data()); + salt_plus_password.insert(salt_plus_password.end(), + reinterpret_cast(&c), + reinterpret_cast(&c) + sizeof(std::uint16_t)); + }); + std::vector h_0 = hash(result.key_encryptor.hash_algorithm, + salt_plus_password.begin(), salt_plus_password.end()); + // H_n = H(iterator + H_n-1) + std::vector iterator_plus_h_n(4, 0); + iterator_plus_h_n.insert(iterator_plus_h_n.end(), h_0.begin(), h_0.end()); + std::uint32_t &iterator = *reinterpret_cast(iterator_plus_h_n.data()); + std::vector h_n; for (iterator = 0; iterator < result.key_encryptor.spin_count; ++iterator) { - hash(result.key_encryptor.hash_algorithm, iterator_with_hash.begin(), iterator_with_hash.end(), hash_result.begin()); + h_n = hash(result.key_encryptor.hash_algorithm, + iterator_plus_h_n.begin(), iterator_plus_h_n.end()); + std::copy(h_n.begin(), h_n.end(), iterator_plus_h_n.begin() + 4); } + + static const std::size_t block_size = 8; - auto hash_with_block_key = hash_result; - std::vector block_key(4, 0); - hash_with_block_key.insert(hash_with_block_key.end(), block_key.begin(), block_key.end()); - hash(result.key_encryptor.hash_algorithm, hash_with_block_key.begin(), hash_with_block_key.end(), hash_result.begin()); - - std::vector key = hash_result; - key.resize(key_size); - - for (std::size_t i = 0; i < key_size; ++i) + auto calculate_block = [&result]( + const std::vector &raw_key, + const std::array &block, + const std::vector &encrypted) { - key[i] = static_cast(i < hash_out_size ? 0x36 ^ key[i] : 0x36); - } + auto combined = raw_key; + combined.insert(combined.end(), block.begin(), block.end()); + auto key = hash(result.key_encryptor.hash_algorithm, combined.begin(), combined.end()); + key.resize(result.key_encryptor.key_bits / 8); + return rijndael_cbc_decrypt(key, result.key_encryptor.salt_value, encrypted); + }; - hash(result.key_encryptor.hash_algorithm, key.begin(), key.end(), key.begin()); + const std::array input_block_key + = { 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 }; + auto hash_input = calculate_block(h_n, input_block_key, + result.key_encryptor.verifier_hash_input); + auto calculated_verifier = hash(result.key_encryptor.hash_algorithm, + hash_input.begin(), hash_input.end()); - if (result.key_encryptor.verifier_hash_value.size() <= key_size) + const std::array verifier_block_key + = { 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e }; + auto expected_verifier = calculate_block(h_n, verifier_block_key, + result.key_encryptor.verifier_hash_value); + + if (calculated_verifier.size() != expected_verifier.size() + || std::mismatch(calculated_verifier.begin(), calculated_verifier.end(), + expected_verifier.begin(), expected_verifier.end()) + != std::make_pair(calculated_verifier.end(), expected_verifier.end())) { - std::vector first_part(key.begin(), key.begin() + key_size); - - for (int i = 0; i < key.size(); ++i) - { - key[i] = static_cast(i < 20 ? 0x5C ^ key[i] : 0x5C); - } - - hash(result.key_encryptor.hash_algorithm, key.begin(), key.end(), key.begin() + key_size); - std::copy(first_part.begin(), first_part.end(), key.begin()); + throw xlnt::exception("bad password"); } - key.resize(key_size, 0); + const std::array key_value_block_key + = { 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 }; + auto key = calculate_block(h_n, key_value_block_key, + result.key_encryptor.encrypted_key_value); - //todo: verify here + auto salt_size = result.key_data.salt_size; + auto salt_with_block_key = result.key_data.salt_value; + salt_with_block_key.resize(salt_size + sizeof(std::uint32_t), 0); - return Botan::SymmetricKey(key); + auto &segment = *reinterpret_cast(salt_with_block_key.data() + salt_size); + auto total_size = *reinterpret_cast(encrypted_package.data()); + + std::vector encrypted_segment(segment_length, 0); + std::vector decrypted_package; + decrypted_package.reserve(encrypted_package.size() - 8); + + for (std::size_t i = 8; i < encrypted_package.size(); i += segment_length) + { + auto iv = hash(result.key_encryptor.hash_algorithm, + salt_with_block_key.begin(), salt_with_block_key.end()); + iv.resize(32); + + auto decrypted_segment = rijndael_cbc_decrypt(key, iv, std::vector( + encrypted_package.begin() + i, encrypted_package.begin() + i + segment_length)); + decrypted_package.insert(decrypted_package.end(), + decrypted_segment.begin(), decrypted_segment.end()); + + ++segment; + } + + decrypted_package.resize(total_size); + + return decrypted_package; } -Botan::SymmetricKey generate_encryption_key(const std::vector &raw_data, const std::string &password) +std::vector decrypt_xlsx(const std::vector &bytes, const std::string &password) { + // nss has checks for re-initialization, but there might be some overhead + static bool nss_initialized = false; + + if (!nss_initialized) + { + NSS_NoDB_Init(nullptr); + nss_initialized = true; + } + + if (bytes.empty()) + { + throw xlnt::exception("empty file"); + } + + std::vector as_chars(bytes.begin(), bytes.end()); + POLE::Storage storage(as_chars.data(), static_cast(bytes.size())); + + if (!storage.open()) + { + throw xlnt::exception("not an ole compound file"); + } + + auto encrypted_package = get_file(storage, "EncryptedPackage"); + auto encryption_info = get_file(storage, "EncryptionInfo"); + std::size_t index = 0; - auto version_major = read_int(index, raw_data); - auto version_minor = read_int(index, raw_data); - auto encryption_flags = read_int(index, raw_data); + auto version_major = read_int(index, encryption_info); + auto version_minor = read_int(index, encryption_info); + auto encryption_flags = read_int(index, encryption_info); + + // get rid of header + encryption_info.erase(encryption_info.begin(), encryption_info.begin() + index); // version 4.4 is agile if (version_major == 4 && version_minor == 4) { if (encryption_flags != 0x40) { - throw "bad header"; + throw xlnt::exception("bad header"); } - return generate_agile_encryption_key(raw_data, index, password); + return decrypt_xlsx_agile(encryption_info, password, encrypted_package); } - else + + // not agile, only try to decrypt versions 3.2 and 4.2 + if (version_minor != 2 + || (version_major != 2 && version_major != 3 && version_major != 4)) { - // not agile, only try to decrypt versions 3.2 and 4.2 - if (version_minor != 2 - || (version_major != 2 && version_major != 3 && version_major != 4)) - { - throw "unsupported encryption version"; - } - - if (encryption_flags & 0b00000011) // Reserved1 and Reserved2, MUST be 0 - { - throw "bad header"; - } - - if ((encryption_flags & 0b00000100) != 0 // fCryptoAPI - || (encryption_flags & 0b00010000) == 0) // fExternal - { - throw "extensible encryption is not supported"; - } - - if ((encryption_flags & 0b00100000) == 0) // fAES - { - throw "not an OOXML document"; - } - - return generate_standard_encryption_key(raw_data, index, password); + throw xlnt::exception("unsupported encryption version"); } -} -std::vector decrypt_xlsx(const std::vector &bytes, const std::string &password) -{ - std::vector as_chars(bytes.begin(), bytes.end()); - POLE::Storage storage(as_chars.data(), static_cast(bytes.size())); - - if (!storage.open()) + if ((encryption_flags & 0b00000011) != 0) // Reserved1 and Reserved2, MUST be 0 { - throw "error"; + throw xlnt::exception("bad header"); } - auto key = generate_encryption_key(get_file(storage, "EncryptionInfo"), password); - auto encrypted_package = get_file(storage, "EncryptedPackage"); - auto size = *reinterpret_cast(encrypted_package.data()); + if ((encryption_flags & 0b00000100) == 0 // fCryptoAPI + || (encryption_flags & 0b00010000) != 0) // fExternal + { + throw xlnt::exception("extensible encryption is not supported"); + } - Botan::InitializationVector iv; - auto cipher_name = std::string("AES-128/ECB/NoPadding"); - auto cipher = Botan::get_cipher(cipher_name, key, iv, Botan::DECRYPTION); + if ((encryption_flags & 0b00100000) == 0) // fAES + { + throw xlnt::exception("not an OOXML document"); + } - Botan::Pipe pipe(cipher); - pipe.process_msg(encrypted_package.data() + 8, - 16 * static_cast(std::ceil(size / 16))); - Botan::secure_vector c1 = pipe.read_all(0); - - return std::vector(c1.begin(), c1.begin() + size); + return decrypt_xlsx_standard(encryption_info, password, encrypted_package); } void xlsx_consumer::read(const std::vector &source, const std::string &password) { - destination_.clear(); - auto decrypted = decrypt_xlsx(source, password); - std::ofstream out("a.zip", std::ostream::binary); - for (auto b : decrypted) out << b; - out.close(); - source_.load(decrypted); + source_.load(decrypt_xlsx(source, password)); populate_workbook(); } diff --git a/source/workbook/tests/test_consume_xlsx.hpp b/source/workbook/tests/test_consume_xlsx.hpp index 9534c7c2..4e0c3314 100644 --- a/source/workbook/tests/test_consume_xlsx.hpp +++ b/source/workbook/tests/test_consume_xlsx.hpp @@ -10,11 +10,20 @@ class test_consume_xlsx : public CxxTest::TestSuite { public: - void test_consume_password_protected() + void test_decrypt_agile() { #ifdef CRYPTO_ENABLED xlnt::workbook wb; - wb.load(path_helper::get_data_directory("14_encrypted.xlsx"), "secret"); + // key is { 125, 177, 16, 188, 228, 63, 60, 108, 222, 145, 79, 49, 49, 13, 157, 98, 217, 33, 52, 3, 222, 124, 200, 242, 179, 57, 61, 97, 225, 195, 141, 242 }; + wb.load(path_helper::get_data_directory("14_encrypted_excel_2016.xlsx"), "secret"); +#endif + } + + void test_decrypt_standard() + { +#ifdef CRYPTO_ENABLED + xlnt::workbook wb; + wb.load(path_helper::get_data_directory("16_encrypted_excel_2007.xlsx"), "password"); #endif } }; diff --git a/tests/data/14_encrypted.xlsx b/tests/data/14_encrypted_excel_2016.xlsx similarity index 100% rename from tests/data/14_encrypted.xlsx rename to tests/data/14_encrypted_excel_2016.xlsx diff --git a/tests/data/15_encryped_libre_office.xlsx b/tests/data/15_encryped_libre_office.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..865cfdf6b2d5decf99707073f6e5bc7e96734513 GIT binary patch literal 8704 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 literal 0 HcmV?d00001 diff --git a/tests/data/16_encrypted_excel_2007.xlsx b/tests/data/16_encrypted_excel_2007.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..15c3b0995d1d79a1f0a0017e15e94c5b98697e41 GIT binary patch literal 12800 zcmeHrby!`=w&%e;xVu}h;O-tIxVt+9hXi+bcPCg#upq(R1HmN_JP8^+@b*bJ^YwK1 zO!vL>{+M^aUWeaaWxH0@vZ~GG1p0cxYl2Up-;odq3Uqgm0D}EZI|NV$<0qkkK#)KI zT)(@&zyGBX1Okfx(ElI}`~cPp{i_YZwubq&47MpeP(}cN2mlEHG5{0+j{u+o09*is z4gdoHCIBn|*Z^<<-~s^8@elp~tpOE~Bk*$tk%1&Z4ghxnc>$LD`-B5{%)<`Be)4}K z|EdyGII#%`C}5BX_yG`Ds_9RuPJncCkQqoBWDK;}0#8ez{#Qf+WT6N7VE$L*!ToRm zn#sT=5Rl&_070qd%j3yn>(m7Rf98$$vXW1K6Vr zAkiAIg2Qh+{$?2s1PSpV|L^V}JoVFG=YPoGU~_=u6%2450P6@G|KR#>I z0x|%9l^31>V^n|UD{$Oc{%-s)(e%g2244CdJ;?t%^Qi$^Fb1SO2Xy2Jle#pBI{I`AouRg@_Z{%O_IU(}vBK~Nn ze|i7^>-+)x@}JSez90B+$Nbg)|G2*2z6}Dr^%wnvZ4bUH{uw>Uec-wRlw?8tF3Xs=DfinP{ zY0UsG3gk~!fQ$e15AZF~5y0B1M3mF6>l3Wy1phrwOCa*mjz9)tqzx z_FWkhN6)dUxwLv5z8j11^_&@=n~)HcJEPXMYgu8nQx-DdPF>Y>r^2!*eS;zjsjm2*b^}?-qupc{bA$fJkNq?4-s~sVePexeT<>z5NU2pP zxpzZeRur(H%1e(v9oZnwcluVDuev?OMTPv{>?w>CUVvFAN=EezHars+`2TE)G0hE?DM`2yy{XtlD<(+{JvA+vZAo? zb^#_b>h#Fd~=&J@c{|CDjrn=~W1kFK#*kGjWambu1HyGK2+aBzTCd|9E)ZS2h; zT9_(ix@B4Hg6nxwAvv7bG4|_?Nbxz{o6IDN_+b*EkT2u!!dCNy&_Io0YVVCmJA(Vz ziKwqNDykjjISu@*TWl|(#wGrVum4%`^?Q3|^o;stmNyX_zdK&_hx3y5Agl0+=Ztas zu6uCD*g5bGB4)U(*SO`wq^N?i-9@j@@a3lR`yKu|h{W0N-WGVWVJkTJS$tF{uZ7w= z8*#NgU{D<@b<(A>py~*#D=;$$v|k3zIldl{E3mw3kT<7*xW`Cvg?nN`a%`d)mOH+l z8-@F{!RkkY%+v)h{nNa2U21+BEVt5lU43{@M2L}!Eip|MV$mZp!9-gu8}iy_L31U_ zE}#3?P!&0xm>zg>`7v5E4a3?zPg9F~SVOYdPX}_aMgmZ9F)^60J6(~v(E8crGksK^ z$54ANQ+PXONguz%L5U-_{fHy{<8b4y?*7oU!*=9|Yvs1oA?TgT-jWzKXR9~LG_4n_ zY1wuC7@vyRL@CN}tqi_dyiu<^0tTu_VrO_i+Q;u&MDm=3(lfu;=YM(~M#?Qsunc%A)pQbQk*AfMzdzy*w}}&LjMrxT)GcLbVQ3y- z)rv2MG^uHPedj%FB1>EL-bg9D@tdBNsa<)+C+NP*iWP3Al{NiBpY`FHnwy0VAgUc^6;^(!AeeR-3-aMoCr8Z2;&n0KW?9tis)ulHCGsc9Stg1Y zV*Xer;d+e+C7lHAuqT%4b=cm)YNzP!R1=`7OgcG)4a?FWBwKN%zq~P8@~ZJs9Bfgu z{@PAUY!G?+m+C-*SVeUSjIV+RnxZktBX4R1xZuVBxHh=`Fc03M10Ky!KJs6yiv; zE2*;|k#n&S$?~cjf5r)+k&=m97&mjWn@h~yV<>EV?<=n(i56L?{HW)wP$8?^6Zc{# znUTf#Ca}CaT|RSYqD=d$9Mn#pto7(Iju;(v6uM=nX9Jg69MM;eGG%}44HrG=Bi
    X9U|Y&wtm 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 M|9t=d_k92V7puJ8CjbBd literal 0 HcmV?d00001