mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
commit in-progress work for reading/writing password protected workbooks, #69 (not working yet)
This commit is contained in:
parent
eee47c267c
commit
a5d50b8ec6
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -17,9 +17,13 @@
|
|||
path = third-party/pugixml
|
||||
url = https://github.com/zeux/pugixml
|
||||
branch = master
|
||||
|
||||
[submodule "third-party/botan"]
|
||||
path = third-party/botan
|
||||
url = https://github.com/randombit/botan
|
||||
branch = master
|
||||
|
||||
[submodule "third-party/pole"]
|
||||
path = third-party/pole
|
||||
url = https://github.com/catlan/pole
|
||||
branch = master
|
||||
|
|
|
@ -1,58 +1,31 @@
|
|||
cmake_minimum_required(VERSION 2.8.7)
|
||||
|
||||
if(NOT DEFINED CMAKE_SUPPRESS_DEVELOPER_WARNINGS)
|
||||
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE INTERNAL "No dev warnings")
|
||||
endif()
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
|
||||
project(xlnt)
|
||||
|
||||
option(SHARED "Set to OFF to not build shared libraries" ON)
|
||||
option(STATIC "Set to ON to build static libraries" OFF)
|
||||
option(DEBUG "Set to ON to for debug configuration" OFF)
|
||||
option(EXAMPLES "Build examples" OFF)
|
||||
option(TESTS "Build tests" OFF)
|
||||
option(BENCHMARKS "Build performance benchmarks" OFF)
|
||||
option(COVERAGE "Generate coverage data for use in Coveralls" OFF)
|
||||
|
||||
option(WITH_EXAMPLES "Build examples" OFF)
|
||||
option(WITH_TESTS "Build tests" OFF)
|
||||
option(WITH_BENCHMARKS "Build performance benchmarks" OFF)
|
||||
option(WITH_CRYPTO "Set to ON to be able to read and write password-protected workbooks; requires botan and pole libraries" OFF)
|
||||
|
||||
if(APPLE)
|
||||
option(FRAMEWORK "Set to ON to package dylib and headers into a .framework, OSX only" OFF)
|
||||
execute_process(COMMAND "sw_vers -productVersion | awk -F'.' '{print $1\".\"$2}'"
|
||||
OUTPUT_VARIABLE OSX_VERSION)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET ${OSX_VERSION})
|
||||
endif()
|
||||
|
||||
if(COVERAGE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
|
||||
endif()
|
||||
|
||||
if(${CMAKE_GENERATOR} STREQUAL "Unix Makefiles")
|
||||
if(DEBUG)
|
||||
set(CMAKE_BUILD_TYPE "Debug")
|
||||
else()
|
||||
set(CMAKE_BUILD_TYPE "Release")
|
||||
endif()
|
||||
endif()
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(APPLE)
|
||||
execute_process(COMMAND "sw_vers -productVersion | awk -F'.' '{print $1\".\"$2}'"
|
||||
OUTPUT_VARIABLE OSX_VERSION
|
||||
)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET ${OSX_VERSION})
|
||||
endif(APPLE)
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS 3.1)
|
||||
if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.5)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
|
||||
endif()
|
||||
elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
|
||||
endif()
|
||||
else()
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
endif()
|
||||
|
||||
if(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
|
||||
if(MSVC)
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
endif()
|
||||
|
||||
|
@ -65,9 +38,9 @@ foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES})
|
|||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/lib)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/bin)
|
||||
endforeach(OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES)
|
||||
endforeach()
|
||||
|
||||
if(TESTS)
|
||||
if(WITH_TESTS)
|
||||
include(cmake/xlnt.test.cmake)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ The resulting library, libxlnt.dylib or libxlnt.so or xlnt.dll, would be found i
|
|||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -D TESTS=1 -D SHARED=0 -D STATIC=1 -G Xcode ..
|
||||
cmake -D WITH_TESTS=1 -D SHARED=0 -D STATIC=1 -G Xcode ..
|
||||
cmake --build .
|
||||
bin/xlnt.test
|
||||
```
|
||||
|
@ -73,6 +73,9 @@ xlnt uses the following libraries, which are included in the source tree (all bu
|
|||
- [libstudxml v1.1.0](http://www.codesynthesis.com/projects/libstudxml/) (MIT license)
|
||||
- [utfcpp v2.3.4](http://utfcpp.sourceforge.net/) (Boost Software License, Version 1.0)
|
||||
- [cxxtest v4.4](http://cxxtest.com/) (LGPLv3 license [only used for testing, separate from main library assembly])
|
||||
- [pugixml v1.7](http://cxxtest.com/) (LGPLv3 license [only used for testing, separate from main library assembly])
|
||||
- [botan v1.11](https://botan.randombit.net/) (BSD license [only used for reading/writing password-protected workbooks, WITH_CRYPTO=1])
|
||||
- [pole v0.5](https://github.com/catlan/pole) (BSD license [only used for reading/writing password-protected workbooks, WITH_CRYPTO=1])
|
||||
|
||||
Initialize the submodules with this command:
|
||||
```bash
|
||||
|
@ -81,7 +84,7 @@ git submodule update --init
|
|||
|
||||
## Documentation
|
||||
|
||||
More extensive documentation with examples can be found on [Read The Docs](http://xlnt.readthedocs.org/en/latest/).
|
||||
More detailed documentation with examples can be found on [Read The Docs](http://xlnt.readthedocs.org/en/latest/) (warning: this is somewhat out of date at the moment).
|
||||
|
||||
## License
|
||||
xlnt is currently released to the public for free under the terms of the MIT License:
|
||||
|
|
|
@ -24,6 +24,13 @@ if(NOT BIN_DEST_DIR)
|
|||
set(BIN_DEST_DIR ${CMAKE_INSTALL_PREFIX}/bin)
|
||||
endif()
|
||||
|
||||
if(WITH_CRYPTO)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DCRYPTO_ENABLED -DBOTAN_DLL= -D_ITERATOR_DEBUG_LEVEL=0")
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj -DNOMINMAX")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include_directories(include)
|
||||
include_directories(include/xlnt)
|
||||
include_directories(source)
|
||||
|
@ -31,6 +38,8 @@ include_directories(source/detail)
|
|||
include_directories(third-party/miniz)
|
||||
include_directories(third-party/libstudxml)
|
||||
include_directories(third-party/utfcpp/source)
|
||||
include_directories(third-party/botan)
|
||||
include_directories(third-party/pole)
|
||||
|
||||
FILE(GLOB ROOT_HEADERS include/xlnt/*.hpp)
|
||||
FILE(GLOB CELL_HEADERS include/xlnt/cell/*.hpp)
|
||||
|
@ -61,17 +70,29 @@ FILE(GLOB WORKBOOK_SOURCES source/workbook/*.cpp)
|
|||
FILE(GLOB WORKSHEET_SOURCES source/worksheet/*.cpp)
|
||||
FILE(GLOB DETAIL_SOURCES source/detail/*.cpp)
|
||||
|
||||
SET(SOURCES ${CELL_SOURCES} ${CHARTS_SOURCES} ${CHARTSHEET_SOURCES} ${DRAWING_SOURCES} ${FORMULA_SOURCES} ${PACKAGING_SOURCES} ${SERIALIZATION_SOURCES} ${STYLES_SOURCES} ${UTILS_SOURCES} ${WORKBOOK_SOURCES} ${WORKSHEET_SOURCES} ${DETAIL_SOURCES})
|
||||
set(SOURCES ${CELL_SOURCES} ${CHARTS_SOURCES} ${CHARTSHEET_SOURCES} ${DRAWING_SOURCES} ${FORMULA_SOURCES} ${PACKAGING_SOURCES} ${SERIALIZATION_SOURCES} ${STYLES_SOURCES} ${UTILS_SOURCES} ${WORKBOOK_SOURCES} ${WORKSHEET_SOURCES} ${DETAIL_SOURCES})
|
||||
|
||||
SET(MINIZ ../third-party/miniz/miniz.c ../third-party/miniz/miniz.h)
|
||||
SET(LIBSTUDXML ../third-party/libstudxml/xml/parser.cxx ../third-party/libstudxml/xml/qname.cxx ../third-party/libstudxml/xml/serializer.cxx ../third-party/libstudxml/xml/value-traits.cxx ../third-party/libstudxml/xml/details/expat/xmlparse.c ../third-party/libstudxml/xml/details/expat/xmlrole.c ../third-party/libstudxml/xml/details/expat/xmltok_impl.c ../third-party/libstudxml/xml/details/expat/xmltok_ns.c ../third-party/libstudxml/xml/details/expat/xmltok.c ../third-party/libstudxml/xml/details/genx/char-props.c ../third-party/libstudxml/xml/details/genx/genx.c)
|
||||
set(MINIZ ../third-party/miniz/miniz.c ../third-party/miniz/miniz.h)
|
||||
set(LIBSTUDXML ../third-party/libstudxml/xml/parser.cxx ../third-party/libstudxml/xml/qname.cxx ../third-party/libstudxml/xml/serializer.cxx ../third-party/libstudxml/xml/value-traits.cxx ../third-party/libstudxml/xml/details/expat/xmlparse.c ../third-party/libstudxml/xml/details/expat/xmlrole.c ../third-party/libstudxml/xml/details/expat/xmltok_impl.c ../third-party/libstudxml/xml/details/expat/xmltok_ns.c ../third-party/libstudxml/xml/details/expat/xmltok.c ../third-party/libstudxml/xml/details/genx/char-props.c ../third-party/libstudxml/xml/details/genx/genx.c)
|
||||
set(SOURCES ${SOURCES} ${MINIZ} ${LIBSTUDXML})
|
||||
|
||||
if(WITH_CRYPTO)
|
||||
set(BOTAN ../third-party/botan/botan_all.cpp)
|
||||
set(POLE ../third-party/pole/pole.cpp)
|
||||
set(SOURCES ${SOURCES} ${BOTAN} ${POLE})
|
||||
endif()
|
||||
|
||||
if(SHARED)
|
||||
add_library(xlnt.shared SHARED ${HEADERS} ${SOURCES} ${MINIZ} ${LIBSTUDXML})
|
||||
add_library(xlnt.shared SHARED ${HEADERS} ${SOURCES} ${MINIZ} ${LIBSTUDXML} ${BOTAN} ${POLE})
|
||||
target_compile_definitions(xlnt.shared PRIVATE XLNT_SHARED=1 LIBSTUDXML_STATIC_LIB=1)
|
||||
if(MSVC)
|
||||
target_compile_definitions(xlnt.shared PRIVATE XLNT_EXPORT=1 _CRT_SECURE_NO_WARNINGS=1)
|
||||
set_target_properties(xlnt.shared PROPERTIES COMPILE_FLAGS "/wd\"4251\" /wd\"4275\"")
|
||||
if(WITH_CRYPTO)
|
||||
set_target_properties(xlnt.shared PROPERTIES COMPILE_FLAGS "/wd\"4250\" /wd\"4251\" /wd\"4275\"")
|
||||
else()
|
||||
set_target_properties(xlnt.shared PROPERTIES COMPILE_FLAGS "/wd\"4251\" /wd\"4275\"")
|
||||
endif()
|
||||
endif()
|
||||
install(TARGETS xlnt.shared
|
||||
LIBRARY DESTINATION ${LIB_DEST_DIR}
|
||||
|
@ -101,7 +122,7 @@ if(SHARED)
|
|||
endif()
|
||||
|
||||
if(STATIC)
|
||||
add_library(xlnt.static STATIC ${HEADERS} ${SOURCES} ${MINIZ} ${LIBSTUDXML})
|
||||
add_library(xlnt.static STATIC ${HEADERS} ${SOURCES} ${MINIZ} ${LIBSTUDXML} ${BOTAN} ${POLE})
|
||||
target_compile_definitions(xlnt.static PUBLIC XLNT_STATIC=1)
|
||||
target_compile_definitions(xlnt.static PRIVATE LIBSTUDXML_STATIC_LIB=1)
|
||||
if(MSVC)
|
||||
|
|
|
@ -155,4 +155,13 @@ public:
|
|||
unhandled_switch_case();
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Exception for attempting to use a feature which is not supported
|
||||
/// </summary>
|
||||
class XLNT_CLASS unsupported : public exception
|
||||
{
|
||||
public:
|
||||
unsupported(const std::string &message);
|
||||
};
|
||||
|
||||
} // namespace xlnt
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <xlnt/xlnt_config.hpp>
|
||||
#include <xlnt/utils/exceptions.hpp>
|
||||
|
||||
namespace xlnt {
|
||||
|
||||
template<typename T>
|
||||
class optional
|
||||
class XLNT_CLASS optional
|
||||
{
|
||||
public:
|
||||
optional() : has_value_(false), value_(T())
|
||||
|
|
|
@ -360,6 +360,18 @@ public:
|
|||
void load(const xlnt::path &filename);
|
||||
void load(std::istream &stream);
|
||||
|
||||
#ifdef CRYPTO_ENABLED
|
||||
void save(const std::string &filename, const std::string &password);
|
||||
void save(const xlnt::path &filename, const std::string &password);
|
||||
void save(std::istream &stream, const std::string &password);
|
||||
void save(const std::vector<std::uint8_t> &data, const std::string &password);
|
||||
|
||||
void load(const std::string &filename, const std::string &password);
|
||||
void load(const xlnt::path &filename, const std::string &password);
|
||||
void load(std::istream &stream, const std::string &password);
|
||||
void load(const std::vector<std::uint8_t> &data, const std::string &password);
|
||||
#endif
|
||||
|
||||
bool has_view() const;
|
||||
workbook_view get_view() const;
|
||||
void set_view(const workbook_view &view);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include <cctype>
|
||||
|
||||
#include <detail/xlsx_consumer.hpp>
|
||||
|
||||
#include <detail/constants.hpp>
|
||||
#include <detail/custom_value_traits.hpp>
|
||||
#include <detail/workbook_impl.hpp>
|
||||
|
|
|
@ -54,6 +54,14 @@ public:
|
|||
|
||||
void read(const std::vector<std::uint8_t> &source);
|
||||
|
||||
#ifdef CRYPTO_ENABLED
|
||||
void read(const path &source, const std::string &password);
|
||||
|
||||
void read(std::istream &source, const std::string &password);
|
||||
|
||||
void read(const std::vector<std::uint8_t> &source, const std::string &password);
|
||||
#endif
|
||||
|
||||
private:
|
||||
/// <summary>
|
||||
/// Read all the files needed from the XLSX archive and initialize all of
|
||||
|
|
469
source/detail/xlsx_crypto.cpp
Normal file
469
source/detail/xlsx_crypto.cpp
Normal file
|
@ -0,0 +1,469 @@
|
|||
#ifdef CRYPTO_ENABLED
|
||||
|
||||
#include <array>
|
||||
#include <pole.h>
|
||||
#include <botan_all.h>
|
||||
#include <include_libstudxml.hpp>
|
||||
|
||||
#include <detail/xlsx_consumer.hpp>
|
||||
#include <xlnt/workbook/workbook.hpp>
|
||||
|
||||
namespace xlnt {
|
||||
namespace detail {
|
||||
|
||||
enum class cipher_algorithm
|
||||
{
|
||||
AES,
|
||||
RC2,
|
||||
RC4,
|
||||
DES,
|
||||
DESX,
|
||||
TripleDES,
|
||||
TripleDES_112
|
||||
};
|
||||
|
||||
enum class cipher_chaining
|
||||
{
|
||||
ECB,
|
||||
CBC,
|
||||
CFB
|
||||
};
|
||||
|
||||
enum class hash_algorithm
|
||||
{
|
||||
SHA1,
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512,
|
||||
MD5,
|
||||
MD4,
|
||||
MD2,
|
||||
RIPEMD128,
|
||||
RIPEMD160,
|
||||
WHIRLPOOL
|
||||
};
|
||||
|
||||
enum class encryption_algorithm
|
||||
{
|
||||
aes_128,
|
||||
aes_192,
|
||||
aes_256,
|
||||
sha_1,
|
||||
sha_512
|
||||
};
|
||||
|
||||
struct agile_encryption_info
|
||||
{
|
||||
// key data
|
||||
struct
|
||||
{
|
||||
std::size_t salt_size;
|
||||
std::size_t block_size;
|
||||
std::size_t key_bits;
|
||||
std::size_t hash_size;
|
||||
std::string cipher_algorithm;
|
||||
std::string cipher_chaining;
|
||||
std::string hash_algorithm;
|
||||
std::vector<std::uint8_t> salt_value;
|
||||
} key_data;
|
||||
|
||||
struct
|
||||
{
|
||||
std::vector<std::uint8_t> hmac_key;
|
||||
std::vector<std::uint8_t> hmac_value;
|
||||
} data_integrity;
|
||||
|
||||
struct
|
||||
{
|
||||
std::size_t spin_count;
|
||||
std::size_t salt_size;
|
||||
std::size_t block_size;
|
||||
std::size_t key_bits;
|
||||
std::size_t hash_size;
|
||||
std::string cipher_algorithm;
|
||||
std::string cipher_chaining;
|
||||
std::string hash_algorithm;
|
||||
std::vector<std::uint8_t> salt_value;
|
||||
std::vector<std::uint8_t> verifier_hash_input;
|
||||
std::vector<std::uint8_t> verifier_hash_value;
|
||||
std::vector<std::uint8_t> encrypted_key_value;
|
||||
} key_encryptor;
|
||||
};
|
||||
|
||||
struct standard_encryption_info
|
||||
{
|
||||
std::vector<std::uint8_t> salt;
|
||||
};
|
||||
|
||||
std::vector<std::uint8_t> get_file(POLE::Storage &storage, const std::string &name)
|
||||
{
|
||||
POLE::Stream stream(&storage, name.c_str());
|
||||
if (stream.fail()) return {};
|
||||
std::vector<Botan::byte> bytes;
|
||||
|
||||
for (std::size_t i = 0; i < stream.size(); ++i)
|
||||
{
|
||||
bytes.push_back(stream.getch());
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
template<typename InIter, typename OutIter>
|
||||
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<typename T>
|
||||
auto read_int(std::size_t &index, const std::vector<std::uint8_t> &raw_data)
|
||||
{
|
||||
auto result = *reinterpret_cast<const T *>(&raw_data[index]);
|
||||
index += sizeof(T);
|
||||
return result;
|
||||
};
|
||||
|
||||
Botan::SymmetricKey generate_standard_encryption_key(const std::vector<std::uint8_t> &raw_data,
|
||||
std::size_t offset, const std::string &password)
|
||||
{
|
||||
auto header_length = read_int<std::uint32_t>(offset, raw_data);
|
||||
auto index_at_start = offset;
|
||||
auto skip_flags = read_int<std::uint32_t>(offset, raw_data);
|
||||
auto size_extra = read_int<std::uint32_t>(offset, raw_data);
|
||||
auto alg_id = read_int<std::uint32_t>(offset, raw_data);
|
||||
auto alg_hash_id = read_int<std::uint32_t>(offset, raw_data);
|
||||
auto key_bits = read_int<std::uint32_t>(offset, raw_data);
|
||||
auto provider_type = read_int<std::uint32_t>(offset, raw_data);
|
||||
/*auto reserved1 = */read_int<std::uint32_t>(offset, raw_data);
|
||||
/*auto reserved2 = */read_int<std::uint32_t>(offset, raw_data);
|
||||
|
||||
const auto csp_name_length = header_length - (offset - index_at_start);
|
||||
std::vector<std::uint16_t> csp_name_wide(
|
||||
reinterpret_cast<const std::uint16_t *>(&*(raw_data.begin() + offset)),
|
||||
reinterpret_cast<const std::uint16_t *>(&*(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<std::uint32_t>(offset, raw_data);
|
||||
std::vector<std::uint8_t> 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<std::uint8_t> verifier_hash_input(raw_data.begin() + offset, raw_data.begin() + offset + verifier_size);
|
||||
offset += verifier_size;
|
||||
|
||||
const auto verifier_hash_size = read_int<std::uint32_t>(offset, raw_data);
|
||||
std::vector<std::uint8_t> 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<std::uint8_t> hash_result(hash_out_size, 0);
|
||||
hash(hash_algorithm, salted_password.begin(), salted_password.end(), hash_result.begin());
|
||||
|
||||
std::vector<std::uint8_t> 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<std::uint32_t *>(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<std::uint8_t> 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<std::uint8_t> key = hash_result;
|
||||
key.resize(key_size);
|
||||
|
||||
for (std::size_t i = 0; i < key_size; ++i)
|
||||
{
|
||||
key[i] = static_cast<std::uint8_t>(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<std::uint8_t> first_part(key.begin(), key.begin() + key_size);
|
||||
|
||||
for (int i = 0; i < key.size(); ++i)
|
||||
{
|
||||
key[i] = static_cast<std::uint8_t>(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<std::uint8_t> &raw_data,
|
||||
std::size_t offset, const std::string &password)
|
||||
{
|
||||
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<std::uint8_t>(decoded.begin(), decoded.end());
|
||||
};
|
||||
|
||||
agile_encryption_info result;
|
||||
|
||||
xml::parser parser(raw_data.data() + offset, raw_data.size() - offset, "EncryptionInfo");
|
||||
|
||||
parser.next_expect(xml::parser::event_type::start_element, xmlns, "encryption");
|
||||
|
||||
parser.next_expect(xml::parser::event_type::start_element, xmlns, "keyData");
|
||||
result.key_data.salt_size = parser.attribute<std::size_t>("saltSize");
|
||||
result.key_data.block_size = parser.attribute<std::size_t>("blockSize");
|
||||
result.key_data.key_bits = parser.attribute<std::size_t>("keyBits");
|
||||
result.key_data.hash_size = parser.attribute<std::size_t>("hashSize");
|
||||
result.key_data.cipher_algorithm = parser.attribute("cipherAlgorithm");
|
||||
result.key_data.cipher_chaining = parser.attribute("cipherChaining");
|
||||
result.key_data.hash_algorithm = parser.attribute("hashAlgorithm");
|
||||
result.key_data.salt_value = from_base64(parser.attribute("saltValue"));
|
||||
parser.next_expect(xml::parser::event_type::end_element, xmlns, "keyData");
|
||||
|
||||
parser.next_expect(xml::parser::event_type::start_element, xmlns, "dataIntegrity");
|
||||
result.data_integrity.hmac_key = from_base64(parser.attribute("encryptedHmacKey"));
|
||||
result.data_integrity.hmac_value = from_base64(parser.attribute("encryptedHmacValue"));
|
||||
parser.next_expect(xml::parser::event_type::end_element, xmlns, "dataIntegrity");
|
||||
|
||||
parser.next_expect(xml::parser::event_type::start_element, xmlns, "keyEncryptors");
|
||||
parser.next_expect(xml::parser::event_type::start_element, xmlns, "keyEncryptor");
|
||||
parser.attribute("uri");
|
||||
bool any_password_key = false;
|
||||
|
||||
while (parser.peek() != xml::parser::event_type::end_element)
|
||||
{
|
||||
parser.next_expect(xml::parser::event_type::start_element);
|
||||
|
||||
if (parser.namespace_() == xmlns_p && parser.name() == "encryptedKey")
|
||||
{
|
||||
any_password_key = true;
|
||||
result.key_encryptor.spin_count = parser.attribute<std::size_t>("spinCount");
|
||||
result.key_encryptor.salt_size = parser.attribute<std::size_t>("saltSize");
|
||||
result.key_encryptor.block_size = parser.attribute<std::size_t>("blockSize");
|
||||
result.key_encryptor.key_bits = parser.attribute<std::size_t>("keyBits");
|
||||
result.key_encryptor.hash_size = parser.attribute<std::size_t>("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"));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "other encryption key types not supported";
|
||||
}
|
||||
|
||||
parser.next_expect(xml::parser::event_type::end_element);
|
||||
}
|
||||
|
||||
if (!any_password_key)
|
||||
{
|
||||
throw "no password key in keyEncryptors";
|
||||
}
|
||||
|
||||
parser.next_expect(xml::parser::event_type::end_element, xmlns, "keyEncryptor");
|
||||
parser.next_expect(xml::parser::event_type::end_element, xmlns, "keyEncryptors");
|
||||
|
||||
parser.next_expect(xml::parser::event_type::end_element, xmlns, "encryption");
|
||||
|
||||
const auto key_size = result.key_encryptor.key_bits / 8;
|
||||
const auto hash_out_size = result.key_encryptor.hash_algorithm == "SHA512" ? 64 : 20;
|
||||
|
||||
auto salted_password = result.key_encryptor.salt_value;
|
||||
|
||||
for (auto c : password)
|
||||
{
|
||||
salted_password.push_back(c);
|
||||
salted_password.push_back(0);
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> hash_result(hash_out_size, 0);
|
||||
hash(result.key_encryptor.hash_algorithm, salted_password.begin(), salted_password.end(), hash_result.begin());
|
||||
|
||||
std::vector<std::uint8_t> 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<std::uint32_t *>(iterator_with_hash.data());
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
auto hash_with_block_key = hash_result;
|
||||
std::vector<std::uint8_t> 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<std::uint8_t> key = hash_result;
|
||||
key.resize(key_size);
|
||||
|
||||
for (std::size_t i = 0; i < key_size; ++i)
|
||||
{
|
||||
key[i] = static_cast<std::uint8_t>(i < hash_out_size ? 0x36 ^ key[i] : 0x36);
|
||||
}
|
||||
|
||||
hash(result.key_encryptor.hash_algorithm, key.begin(), key.end(), key.begin());
|
||||
|
||||
if (result.key_encryptor.verifier_hash_value.size() <= key_size)
|
||||
{
|
||||
std::vector<std::uint8_t> first_part(key.begin(), key.begin() + key_size);
|
||||
|
||||
for (int i = 0; i < key.size(); ++i)
|
||||
{
|
||||
key[i] = static_cast<std::uint8_t>(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());
|
||||
}
|
||||
|
||||
key.resize(key_size, 0);
|
||||
|
||||
//todo: verify here
|
||||
|
||||
return Botan::SymmetricKey(key);
|
||||
}
|
||||
|
||||
Botan::SymmetricKey generate_encryption_key(const std::vector<std::uint8_t> &raw_data, const std::string &password)
|
||||
{
|
||||
std::size_t index = 0;
|
||||
|
||||
auto version_major = read_int<std::uint16_t>(index, raw_data);
|
||||
auto version_minor = read_int<std::uint16_t>(index, raw_data);
|
||||
auto encryption_flags = read_int<std::uint32_t>(index, raw_data);
|
||||
|
||||
// version 4.4 is agile
|
||||
if (version_major == 4 && version_minor == 4)
|
||||
{
|
||||
if (encryption_flags != 0x40)
|
||||
{
|
||||
throw "bad header";
|
||||
}
|
||||
|
||||
return generate_agile_encryption_key(raw_data, index, password);
|
||||
}
|
||||
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))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> decrypt_xlsx(const std::vector<std::uint8_t> &bytes, const std::string &password)
|
||||
{
|
||||
std::vector<char> as_chars(bytes.begin(), bytes.end());
|
||||
POLE::Storage storage(as_chars.data(), static_cast<unsigned long>(bytes.size()));
|
||||
|
||||
if (!storage.open())
|
||||
{
|
||||
throw "error";
|
||||
}
|
||||
|
||||
auto key = generate_encryption_key(get_file(storage, "EncryptionInfo"), password);
|
||||
auto encrypted_package = get_file(storage, "EncryptedPackage");
|
||||
auto size = *reinterpret_cast<std::uint64_t *>(encrypted_package.data());
|
||||
|
||||
Botan::InitializationVector iv;
|
||||
auto cipher_name = std::string("AES-128/ECB/NoPadding");
|
||||
auto cipher = Botan::get_cipher(cipher_name, key, iv, Botan::DECRYPTION);
|
||||
|
||||
Botan::Pipe pipe(cipher);
|
||||
pipe.process_msg(encrypted_package.data() + 8,
|
||||
16 * static_cast<std::size_t>(std::ceil(size / 16)));
|
||||
Botan::secure_vector<Botan::byte> c1 = pipe.read_all(0);
|
||||
|
||||
return std::vector<std::uint8_t>(c1.begin(), c1.begin() + size);
|
||||
}
|
||||
|
||||
void xlsx_consumer::read(const std::vector<std::uint8_t> &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);
|
||||
populate_workbook();
|
||||
}
|
||||
|
||||
void xlsx_consumer::read(std::istream &source, const std::string &password)
|
||||
{
|
||||
std::vector<std::uint8_t> data((std::istreambuf_iterator<char>(source)),
|
||||
std::istreambuf_iterator<char>());
|
||||
return read(data, password);
|
||||
}
|
||||
|
||||
void xlsx_consumer::read(const path &source, const std::string &password)
|
||||
{
|
||||
std::ifstream file_stream(source.string(), std::iostream::binary);
|
||||
return read(file_stream, password);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace xlnt
|
||||
|
||||
#endif
|
|
@ -402,7 +402,7 @@ void xlsx_producer::write_workbook(const relationship &rel)
|
|||
for (const auto ws : source_)
|
||||
{
|
||||
auto sheet_rel_id = source_.d_->sheet_title_rel_id_map_[ws.get_title()];
|
||||
auto sheet_rel = source_.d_->manifest_.get_relationship(rel.get_source().get_path(), sheet_rel_id);
|
||||
auto sheet_rel = source_.d_->manifest_.get_relationship(rel.get_target().get_path(), sheet_rel_id);
|
||||
|
||||
serializer().start_element(xmlns, "sheet");
|
||||
serializer().attribute("name", ws.get_title());
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cxxtest/TestSuite.h>
|
||||
|
||||
#include <xlnt/xlnt.hpp>
|
||||
|
|
|
@ -153,7 +153,7 @@ zip_file::zip_file(std::istream &stream) : zip_file()
|
|||
load(stream);
|
||||
}
|
||||
|
||||
zip_file::zip_file(const std::vector<unsigned char> &bytes) : zip_file()
|
||||
zip_file::zip_file(const std::vector<std::uint8_t> &bytes) : zip_file()
|
||||
{
|
||||
load(bytes);
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ void zip_file::load(const path &filename)
|
|||
start_read();
|
||||
}
|
||||
|
||||
void zip_file::load(const std::vector<unsigned char> &bytes)
|
||||
void zip_file::load(const std::vector<std::uint8_t> &bytes)
|
||||
{
|
||||
if (bytes.empty())
|
||||
{
|
||||
|
@ -245,7 +245,7 @@ void zip_file::save(std::ostream &stream)
|
|||
stream.write(buffer_.data(), static_cast<long>(buffer_.size()));
|
||||
}
|
||||
|
||||
void zip_file::save(std::vector<unsigned char> &bytes)
|
||||
void zip_file::save(std::vector<std::uint8_t> &bytes)
|
||||
{
|
||||
if (archive_->m_zip_mode == MZ_ZIP_MODE_WRITING)
|
||||
{
|
||||
|
|
|
@ -93,4 +93,9 @@ no_visible_worksheets::no_visible_worksheets()
|
|||
{
|
||||
}
|
||||
|
||||
unsupported::unsupported(const std::string &message)
|
||||
: exception(message)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace xlnt
|
||||
|
|
|
@ -133,7 +133,6 @@ char path::system_separator()
|
|||
|
||||
path::path()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
path::path(const std::string &path_string) : internal_(path_string)
|
||||
|
|
|
@ -5,10 +5,16 @@
|
|||
#include <cxxtest/TestSuite.h>
|
||||
|
||||
#include <helpers/path_helper.hpp>
|
||||
#include <xlnt/cell/text.hpp>
|
||||
#include <xlnt/cell/text_run.hpp>
|
||||
#include <xlnt/packaging/manifest.hpp>
|
||||
#include <xlnt/workbook/workbook.hpp>
|
||||
|
||||
class test_consume_xlsx : public CxxTest::TestSuite
|
||||
{
|
||||
public:
|
||||
void test_consume_password_protected()
|
||||
{
|
||||
#ifdef CRYPTO_ENABLED
|
||||
xlnt::workbook wb;
|
||||
wb.load(path_helper::get_data_directory("14_encrypted.xlsx"), "password");
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
|
|
@ -612,7 +612,7 @@ void workbook::load(std::istream &stream)
|
|||
consumer.read(stream);
|
||||
}
|
||||
|
||||
void workbook::load(const std::vector<unsigned char> &data)
|
||||
void workbook::load(const std::vector<std::uint8_t> &data)
|
||||
{
|
||||
clear();
|
||||
detail::xlsx_consumer consumer(*this);
|
||||
|
@ -631,7 +631,34 @@ void workbook::load(const path &filename)
|
|||
consumer.read(filename);
|
||||
}
|
||||
|
||||
void workbook::save(std::vector<unsigned char> &data) const
|
||||
#ifdef CRYPTO_ENABLED
|
||||
void workbook::load(const std::string &filename, const std::string &password)
|
||||
{
|
||||
return load(path(filename), password);
|
||||
}
|
||||
|
||||
void workbook::load(const path &filename, const std::string &password)
|
||||
{
|
||||
std::ifstream file_stream(filename.string(), std::iostream::binary);
|
||||
return load(file_stream, password);
|
||||
}
|
||||
|
||||
void workbook::load(const std::vector<std::uint8_t> &data, const std::string &password)
|
||||
{
|
||||
clear();
|
||||
detail::xlsx_consumer consumer(*this);
|
||||
consumer.read(data, password);
|
||||
}
|
||||
|
||||
void workbook::load(std::istream &stream, const std::string &password)
|
||||
{
|
||||
clear();
|
||||
detail::xlsx_consumer consumer(*this);
|
||||
consumer.read(stream, password);
|
||||
}
|
||||
#endif
|
||||
|
||||
void workbook::save(std::vector<std::uint8_t> &data) const
|
||||
{
|
||||
detail::xlsx_producer producer(*this);
|
||||
producer.write(data);
|
||||
|
|
BIN
tests/data/14_encrypted.xlsx
Normal file
BIN
tests/data/14_encrypted.xlsx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user