-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathodf_crypto.cpp
More file actions
216 lines (192 loc) · 7.65 KB
/
Copy pathodf_crypto.cpp
File metadata and controls
216 lines (192 loc) · 7.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
#include <odr/internal/odf/odf_crypto.hpp>
#include <odr/exceptions.hpp>
#include <odr/internal/abstract/file.hpp>
#include <odr/internal/abstract/filesystem.hpp>
#include <odr/internal/common/file.hpp>
#include <odr/internal/crypto/crypto_util.hpp>
#include <odr/internal/util/stream_util.hpp>
#include <odr/internal/zip/zip_archive.hpp>
#include <odr/internal/zip/zip_file.hpp>
#include <iostream>
#include <stdexcept>
namespace odr::internal {
bool odf::can_decrypt(const Manifest::Entry &entry) noexcept {
return (!entry.checksum.has_value() ||
(entry.checksum->type != ChecksumType::UNKNOWN)) &&
(entry.algorithm.type != AlgorithmType::UNKNOWN) &&
(entry.key_derivation.type != KeyDerivationType::UNKNOWN) &&
(entry.start_key_generation.type != ChecksumType::UNKNOWN);
}
std::string odf::hash(const std::string &input,
const ChecksumType checksum_type) {
switch (checksum_type) {
case ChecksumType::SHA256:
return crypto::util::sha256(input);
case ChecksumType::SHA1:
return crypto::util::sha1(input);
case ChecksumType::SHA256_1K:
return crypto::util::sha256(input.substr(0, 1024));
case ChecksumType::SHA1_1K:
return crypto::util::sha1(input.substr(0, 1024));
default:
throw std::invalid_argument("checksum type");
}
}
std::string odf::decrypt(const std::string &input,
const std::string &derived_key,
const std::string &initialisation_vector,
const AlgorithmType algorithm) {
switch (algorithm) {
case AlgorithmType::AES256_CBC:
return crypto::util::decrypt_aes_cbc(derived_key, initialisation_vector,
input);
case AlgorithmType::TRIPLE_DES_CBC:
return crypto::util::decrypt_triple_des(derived_key, initialisation_vector,
input);
case AlgorithmType::BLOWFISH_CFB:
return crypto::util::decrypt_blowfish(derived_key, initialisation_vector,
input);
case AlgorithmType::AES256_GCM:
return crypto::util::decrypt_aes_gcm(derived_key, initialisation_vector,
input);
default:
throw std::invalid_argument("algorithm");
}
}
std::string odf::start_key(const Manifest::Entry &entry,
const std::string &password) {
const std::string result = hash(password, entry.start_key_generation.type);
if (result.size() < entry.start_key_generation.size) {
throw std::invalid_argument("hash too small");
}
return result.substr(0, entry.start_key_generation.size);
}
std::string odf::derive_key(const Manifest::Entry &entry,
const std::string &start_key) {
switch (entry.key_derivation.type) {
case KeyDerivationType::PBKDF2:
return crypto::util::pbkdf2(entry.key_derivation.size, start_key,
entry.key_derivation.salt,
entry.key_derivation.iteration_count);
case KeyDerivationType::ARGON2ID:
return crypto::util::argon2id(
entry.key_derivation.size, start_key, entry.key_derivation.salt,
entry.key_derivation.iteration_count,
entry.key_derivation.argon2_memory, entry.key_derivation.argon2_lanes);
default:
throw std::invalid_argument("key derivation type");
}
}
std::string odf::derive_key_and_decrypt(const Manifest::Entry &entry,
const std::string &start_key,
const std::string &input) {
std::string derived_key = derive_key(entry, start_key);
return decrypt(input, derived_key, entry.algorithm.initialisation_vector,
entry.algorithm.type);
}
bool odf::validate_password(const Manifest::Entry &entry,
std::string decrypted) noexcept {
if (!entry.checksum.has_value()) {
return false;
}
try {
const std::size_t padding = crypto::util::padding(decrypted);
decrypted = decrypted.substr(0, decrypted.size() - padding);
const std::string checksum = hash(decrypted, entry.checksum->type);
return checksum == entry.checksum->value;
} catch (...) {
// TODO why catch all?
return false;
}
}
namespace odf {
namespace {
class DecryptedFilesystem final : public abstract::ReadableFilesystem {
public:
DecryptedFilesystem(std::shared_ptr<abstract::ReadableFilesystem> parent,
Manifest manifest, std::string start_key)
: m_parent(std::move(parent)), m_manifest(std::move(manifest)),
m_start_key(std::move(start_key)) {}
[[nodiscard]] bool exists(const common::Path &path) const final {
return m_parent->exists(path);
}
[[nodiscard]] bool is_file(const common::Path &path) const final {
return m_parent->is_file(path);
}
[[nodiscard]] bool is_directory(const common::Path &path) const final {
return m_parent->is_directory(path);
}
[[nodiscard]] std::unique_ptr<abstract::FileWalker>
file_walker(const common::Path &path) const final {
return m_parent->file_walker(path);
}
[[nodiscard]] std::shared_ptr<abstract::File>
open(const common::Path &path) const final {
const auto it = m_manifest.entries.find(path);
if (it == std::end(m_manifest.entries)) {
return m_parent->open(path);
}
if (!can_decrypt(it->second)) {
throw UnsupportedCryptoAlgorithm();
}
// TODO stream
auto source = m_parent->open(path)->stream();
const std::string input = util::stream::read(*source);
std::string result = crypto::util::inflate(
derive_key_and_decrypt(it->second, m_start_key, input));
return std::make_shared<common::MemoryFile>(result);
}
private:
const std::shared_ptr<abstract::ReadableFilesystem> m_parent;
const Manifest m_manifest;
const std::string m_start_key;
};
} // namespace
} // namespace odf
std::shared_ptr<abstract::ReadableFilesystem>
odf::decrypt(const std::shared_ptr<abstract::ReadableFilesystem> &filesystem,
const Manifest &manifest, const std::string &password) {
if (!manifest.encrypted) {
throw NotEncryptedError();
}
if (auto it = manifest.entries.find(common::Path("/encrypted-package"));
it != std::end(manifest.entries)) {
try {
const std::string start_key = odf::start_key(it->second, password);
const std::string input =
util::stream::read(*filesystem->open(it->first)->stream());
std::string decrypt = crypto::util::inflate(
derive_key_and_decrypt(it->second, start_key, input));
auto memory_file =
std::make_shared<common::MemoryFile>(std::move(decrypt));
return zip::ZipFile(memory_file).archive()->as_filesystem();
} catch (...) {
throw WrongPasswordError();
}
}
try {
const auto &smallest_file_path = manifest.smallest_file_path;
const auto &smallest_file_entry = manifest.smallest_file_entry();
if (!can_decrypt(smallest_file_entry)) {
throw UnsupportedCryptoAlgorithm();
}
const std::string start_key = odf::start_key(smallest_file_entry, password);
// TODO stream decrypt
const std::string input =
util::stream::read(*filesystem->open(smallest_file_path)->stream());
const std::string decrypt =
derive_key_and_decrypt(smallest_file_entry, start_key, input);
if (!validate_password(smallest_file_entry, decrypt)) {
throw WrongPasswordError();
}
return std::make_shared<DecryptedFilesystem>(filesystem, manifest,
start_key);
} catch (const UnsupportedCryptoAlgorithm &) {
throw;
} catch (const WrongPasswordError &) {
throw;
} catch (...) {
throw DecryptionFailed();
}
}
} // namespace odr::internal