Skip to content

Commit 02eaa16

Browse files
committed
Streaming decryption for CDoc1
Signed-off-by: Raul Metsma <[email protected]>
1 parent 3b0d127 commit 02eaa16

File tree

6 files changed

+209
-117
lines changed

6 files changed

+209
-117
lines changed

cdoc/CDoc1Reader.cpp

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ result_t CDoc1Reader::decryptData(const std::vector<uint8_t>& fmk, std::string&
384384
return result;
385385
}
386386

387+
std::vector<unsigned char> b64;
387388
XMLReader reader(d->dsrc, false);
388389
int skipKeyInfo = 0;
389390
while (reader.read()) {
@@ -397,26 +398,30 @@ result_t CDoc1Reader::decryptData(const std::vector<uint8_t>& fmk, std::string&
397398
// EncryptedData/CipherData/CipherValue
398399
else if(reader.isElement("CipherValue"))
399400
{
400-
data = libcdoc::Crypto::decrypt(d->method, fmk, reader.readBase64());
401+
b64 = reader.readBase64();
401402
break;
402403
}
403404
}
404405

405-
if(data.empty()) {
406+
if(b64.empty()) {
407+
setLastError("Failed to decode base64 data");
408+
return libcdoc::IO_ERROR;
409+
}
410+
VectorSource src(b64);
411+
libcdoc::DecryptionSource dec(src, d->method, fmk);
412+
if(dec.isError()) {
406413
setLastError("Failed to decrypt data, verify if FMK is correct");
407-
return libcdoc::CRYPTO_ERROR;
414+
return CRYPTO_ERROR;
408415
}
416+
libcdoc::VectorConsumer out(data);
409417
setLastError({});
410418
if (d->mime == MIME_ZLIB) {
411-
libcdoc::VectorSource vsrc(data);
412-
libcdoc::ZSource zsrc(&vsrc);
413-
std::vector<uint8_t> tmp;
414-
libcdoc::VectorConsumer vcons(tmp);
415-
vcons.writeAll(zsrc);
416-
data = std::move(tmp);
419+
libcdoc::ZSource zsrc(&dec);
420+
out.writeAll(zsrc);
417421
mime = d->properties["OriginalMimeType"];
418422
}
419423
else
420424
mime = d->mime;
421-
return libcdoc::OK;
425+
out.writeAll(dec);
426+
return dec.close();
422427
}

cdoc/Crypto.cpp

Lines changed: 130 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -141,61 +141,43 @@ const EVP_CIPHER *Crypto::cipher(const std::string &algo)
141141
std::vector<uint8_t> Crypto::concatKDF(const std::string &hashAlg, uint32_t keyDataLen,
142142
const std::vector<uint8_t> &z, const std::vector<uint8_t> &otherInfo)
143143
{
144-
std::vector<uint8_t> key;
145-
uint32_t hashLen = SHA384_DIGEST_LENGTH;
146-
if(hashAlg == SHA256_MTH) hashLen = SHA256_DIGEST_LENGTH;
147-
else if(hashAlg == SHA384_MTH) hashLen = SHA384_DIGEST_LENGTH;
148-
else if(hashAlg == SHA512_MTH) hashLen = SHA512_DIGEST_LENGTH;
149-
else return key;
150-
151-
SHA256_CTX sha256;
152-
SHA512_CTX sha512;
153-
std::vector<uint8_t> hash(hashLen, 0);
154-
uint8_t intToFourBytes[4];
144+
std::vector<uint8_t> key;
145+
const EVP_MD *md {};
146+
if(hashAlg == SHA256_MTH) md = EVP_sha256();
147+
else if(hashAlg == SHA384_MTH) md = EVP_sha384();
148+
else if(hashAlg == SHA512_MTH) md = EVP_sha512();
149+
else {
150+
LOG_WARN("Usnupported hash algo {}", hashAlg);
151+
return key;
152+
}
155153

154+
uint32_t hashLen = EVP_MD_get_size(md);
156155
uint32_t reps = keyDataLen / hashLen;
157156
if (keyDataLen % hashLen > 0)
158157
reps++;
159158

160-
for(uint32_t i = 1; i <= reps; i++)
161-
{
162-
intToFourBytes[0] = uint8_t(i >> 24);
163-
intToFourBytes[1] = uint8_t(i >> 16);
164-
intToFourBytes[2] = uint8_t(i >> 8);
165-
intToFourBytes[3] = uint8_t(i >> 0);
166-
switch(hashLen)
167-
{
168-
case SHA256_DIGEST_LENGTH:
169-
if (SSL_FAILED(SHA256_Init(&sha256), "SHA256_Init") ||
170-
SSL_FAILED(SHA256_Update(&sha256, intToFourBytes, 4), "SHA256_Update") ||
171-
SSL_FAILED(SHA256_Update(&sha256, z.data(), z.size()), "SHA256_Update") ||
172-
SSL_FAILED(SHA256_Update(&sha256, otherInfo.data(), otherInfo.size()), "SHA256_Update") ||
173-
SSL_FAILED(SHA256_Final(hash.data(), &sha256), "SHA256_Final"))
174-
return {};
175-
break;
176-
case SHA384_DIGEST_LENGTH:
177-
if (SSL_FAILED(SHA384_Init(&sha512), "SHA384_Init") ||
178-
SSL_FAILED(SHA384_Update(&sha512, intToFourBytes, 4), "SHA384_Update") ||
179-
SSL_FAILED(SHA384_Update(&sha512, z.data(), z.size()), "SHA384_Update") ||
180-
SSL_FAILED(SHA384_Update(&sha512, otherInfo.data(), otherInfo.size()), "SHA384_Update") ||
181-
SSL_FAILED(SHA384_Final(hash.data(), &sha512), "SHA384_Final"))
182-
return {};
183-
break;
184-
case SHA512_DIGEST_LENGTH:
185-
if (SSL_FAILED(SHA512_Init(&sha512), "SHA512_Init") ||
186-
SSL_FAILED(SHA512_Update(&sha512, intToFourBytes, 4), "SHA512_Update") ||
187-
SSL_FAILED(SHA512_Update(&sha512, otherInfo.data(), otherInfo.size()), "SHA512_Update") ||
188-
SSL_FAILED(SHA512_Final(hash.data(), &sha512), "SHA512_Update"))
189-
return {};
190-
break;
191-
default:
192-
LOG_WARN("Usnupported hash length {}", hashLen);
193-
return key;
194-
}
195-
key.insert(key.cend(), hash.cbegin(), hash.cend());
196-
}
197-
key.resize(size_t(keyDataLen));
198-
return key;
159+
auto ctx = make_unique_ptr<EVP_MD_CTX_free>(EVP_MD_CTX_new());
160+
if(!ctx)
161+
{
162+
LOG_SSL_ERROR("EVP_MD_CTX_new");
163+
return key;
164+
}
165+
166+
std::vector<uint8_t> hash(hashLen, 0);
167+
for(uint32_t i = 1; i <= reps; i++)
168+
{
169+
uint8_t intToFourBytes[4] { uint8_t(i >> 24), uint8_t(i >> 16), uint8_t(i >> 8), uint8_t(i >> 0) };
170+
unsigned int size = hashLen;
171+
if (SSL_FAILED(EVP_DigestInit(ctx.get(), md), "EVP_DigestInit") ||
172+
SSL_FAILED(EVP_DigestUpdate(ctx.get(), intToFourBytes, 4), "EVP_DigestUpdate") ||
173+
SSL_FAILED(EVP_DigestUpdate(ctx.get(), z.data(), z.size()), "EVP_DigestUpdate") ||
174+
SSL_FAILED(EVP_DigestUpdate(ctx.get(), otherInfo.data(), otherInfo.size()), "EVP_DigestUpdate") ||
175+
SSL_FAILED(EVP_DigestFinal(ctx.get(), hash.data(), &size), "EVP_DigestFinal"))
176+
return {};
177+
key.insert(key.cend(), hash.cbegin(), hash.cend());
178+
}
179+
key.resize(size_t(keyDataLen));
180+
return key;
199181
}
200182

201183
std::vector<uint8_t> Crypto::concatKDF(const std::string &hashAlg, uint32_t keyDataLen, const std::vector<uint8_t> &z,
@@ -234,56 +216,6 @@ Crypto::encrypt(EVP_PKEY *pub, int padding, const std::vector<uint8_t> &data)
234216
return result;
235217
}
236218

237-
std::vector<uint8_t> Crypto::decrypt(const std::string &method, const std::vector<uint8_t> &key, const std::vector<uint8_t> &data)
238-
{
239-
const EVP_CIPHER *cipher = Crypto::cipher(method);
240-
size_t dataSize = data.size();
241-
std::vector<uint8_t> iv(data.cbegin(), data.cbegin() + EVP_CIPHER_iv_length(cipher));
242-
if(dataSize < iv.size())
243-
return {};
244-
dataSize -= iv.size();
245-
246-
LOG_TRACE_KEY("iv {}", iv);
247-
LOG_TRACE_KEY("transport {}", key);
248-
249-
auto ctx = make_unique_ptr<EVP_CIPHER_CTX_free>(EVP_CIPHER_CTX_new());
250-
if (!ctx)
251-
{
252-
LOG_SSL_ERROR("EVP_CIPHER_CTX_new");
253-
return {};
254-
}
255-
256-
if (SSL_FAILED(EVP_CipherInit(ctx.get(), cipher, key.data(), iv.data(), 0), "EVP_CipherInit"))
257-
{
258-
return {};
259-
}
260-
261-
if (EVP_CIPHER_mode(cipher) == EVP_CIPH_GCM_MODE)
262-
{
263-
std::vector<uint8_t> tag(data.cend() - 16, data.cend());
264-
if(dataSize < tag.size())
265-
return {};
266-
EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, int(tag.size()), tag.data());
267-
dataSize -= tag.size();
268-
LOG_DBG("GCM TAG {}", toHex(tag));
269-
}
270-
271-
int size = 0;
272-
std::vector<uint8_t> result(dataSize + size_t(EVP_CIPHER_CTX_block_size(ctx.get())), 0);
273-
if (SSL_FAILED(EVP_CipherUpdate(ctx.get(), result.data(), &size, &data[iv.size()], int(dataSize)), "EVP_CipherUpdate"))
274-
{
275-
return {};
276-
}
277-
278-
int size2 = 0;
279-
if (SSL_FAILED(EVP_CipherFinal(ctx.get(), result.data() + size, &size2), "EVP_CipherFinal"))
280-
{
281-
return {};
282-
}
283-
result.resize(size_t(size + size2));
284-
return result;
285-
}
286-
287219
std::vector<uint8_t> Crypto::decodeBase64(const uint8_t *data)
288220
{
289221
std::vector<uint8_t> result;
@@ -608,14 +540,109 @@ EncryptionConsumer::close()
608540
if(SSL_FAILED(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, int(tag.size()), tag.data()), "EVP_CIPHER_CTX_ctrl"))
609541
return CRYPTO_ERROR;
610542
LOG_DBG("tag: {}", toHex(tag));
611-
return dst.write(tag.data(), tag.size());
543+
if (dst.write(tag.data(), tag.size()) != tag.size())
544+
return IO_ERROR;
612545
}
613-
if(EVP_CIPHER_CTX_flags(ctx.get()) & EVP_CIPH_FLAG_AEAD_CIPHER)
546+
else if(EVP_CIPHER_CTX_flags(ctx.get()) & EVP_CIPH_FLAG_AEAD_CIPHER)
614547
{
615548
if(SSL_FAILED(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, int(tag.size()), tag.data()), "EVP_CIPHER_CTX_ctrl"))
616549
return CRYPTO_ERROR;
617550
LOG_DBG("tag: {}", toHex(tag));
618-
return dst.write(tag.data(), tag.size());
551+
if (dst.write(tag.data(), tag.size()) != tag.size())
552+
return IO_ERROR;
553+
}
554+
return OK;
555+
}
556+
557+
DecryptionSource::DecryptionSource(DataSource &src, const std::string &method, const std::vector<unsigned char> &key)
558+
: DecryptionSource(src, Crypto::cipher(method), key)
559+
{}
560+
561+
DecryptionSource::DecryptionSource(DataSource &src, const EVP_CIPHER *cipher, const std::vector<unsigned char> &key)
562+
: ctx{EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free}
563+
, src(src)
564+
{
565+
EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
566+
int ivLen = EVP_CIPHER_iv_length(cipher);
567+
std::vector<unsigned char> iv(ivLen, 0);
568+
if(auto rv = src.read(iv.data(), ivLen); size_t(rv) != iv.size())
569+
error = rv < 0 ? rv : IO_ERROR;
570+
else if(SSL_FAILED(EVP_CipherInit_ex(ctx.get(), cipher, nullptr, key.data(), iv.data(), 0), "EVP_CipherInit_ex"))
571+
error = CRYPTO_ERROR;
572+
else if(auto rv = src.read(tag.data(), tag.size()); size_t(rv) != tag.size())
573+
error = rv < 0 ? rv : IO_ERROR;
574+
}
575+
576+
result_t DecryptionSource::readAAD(const std::vector<uint8_t> &data)
577+
{
578+
if (error != OK)
579+
return error;
580+
int len = 0;
581+
if(SSL_FAILED(EVP_CipherUpdate(ctx.get(), nullptr, &len, data.data(), int(data.size())), "EVP_CipherUpdate"))
582+
return CRYPTO_ERROR;
583+
return OK;
584+
}
585+
586+
result_t DecryptionSource::read(unsigned char *dst, size_t size)
587+
{
588+
if (error != OK)
589+
return error;
590+
if (!dst || size == 0)
591+
return OK;
592+
if(size < tag.size())
593+
return INPUT_STREAM_ERROR;
594+
595+
auto r = src.read(dst + tag.size(), size - tag.size());
596+
if (r <= 0) {
597+
return r;
598+
}
599+
auto nread = static_cast<size_t>(r);
600+
601+
std::copy(tag.begin(), tag.end(), dst);
602+
603+
if (nread < size - tag.size()) {
604+
std::copy_n(std::next(dst, nread), tag.size(), tag.begin());
605+
size = nread;
606+
} else if (auto r = src.read(tag.data(), tag.size()); r < 0) {
607+
return r;
608+
} else if (auto tagSize = static_cast<size_t>(r); tagSize < tag.size()) {
609+
std::move_backward(tag.begin(), std::next(tag.begin(), tagSize), tag.end());
610+
size_t more = tag.size() - tagSize;
611+
std::copy_n(std::next(dst, size - more), more, tag.data());
612+
size -= more;
613+
}
614+
615+
if (int out = 0;
616+
SSL_FAILED(EVP_CipherUpdate(ctx.get(), dst, &out, dst, size), "EVP_CipherUpdate") ||
617+
size != out) {
618+
return error = CRYPTO_ERROR;
619+
}
620+
return size;
621+
}
622+
623+
result_t DecryptionSource::close()
624+
{
625+
if (error != OK)
626+
return error;
627+
628+
if (EVP_CIPHER_CTX_mode(ctx.get()) == EVP_CIPH_GCM_MODE) {
629+
LOG_DBG("tag: {}", toHex(tag));
630+
if (SSL_FAILED(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, int(tag.size()), (void*)tag.data()), "EVP_CIPHER_CTX_ctrl")) {
631+
return error = CRYPTO_ERROR;
632+
}
633+
}
634+
else if(EVP_CIPHER_CTX_flags(ctx.get()) & EVP_CIPH_FLAG_AEAD_CIPHER)
635+
{
636+
LOG_DBG("tag: {}", toHex(tag));
637+
if (SSL_FAILED(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG, int(tag.size()), (void*)tag.data()), "EVP_CIPHER_CTX_ctrl")) {
638+
return error = CRYPTO_ERROR;
639+
}
640+
}
641+
642+
int len = 0;
643+
std::vector<uint8_t> buffer(EVP_CIPHER_CTX_block_size(ctx.get()), 0);
644+
if (SSL_FAILED(EVP_CipherFinal_ex(ctx.get(), buffer.data(), &len), "EVP_CipherFinal_ex")) {
645+
return error = CRYPTO_ERROR;
619646
}
620647
return OK;
621648
}

cdoc/Crypto.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222

2323
#include "utils/memory.h"
2424

25+
#include <array>
26+
2527
using EVP_CIPHER_CTX = struct evp_cipher_ctx_st;
2628
using EVP_CIPHER = struct evp_cipher_st;
2729
using EVP_PKEY = struct evp_pkey_st;
@@ -81,7 +83,6 @@ class Crypto
8183
static std::vector<uint8_t> concatKDF(const std::string &hashAlg, uint32_t keyDataLen, const std::vector<uint8_t> &z, const std::vector<uint8_t> &otherInfo);
8284
static std::vector<uint8_t> concatKDF(const std::string &hashAlg, uint32_t keyDataLen, const std::vector<uint8_t> &z,
8385
const std::vector<uint8_t> &AlgorithmID, const std::vector<uint8_t> &PartyUInfo, const std::vector<uint8_t> &PartyVInfo);
84-
static std::vector<uint8_t> decrypt(const std::string &method, const std::vector<uint8_t> &key, const std::vector<uint8_t> &data);
8586
static std::vector<uint8_t> encrypt(EVP_PKEY *pub, int padding, const std::vector<uint8_t> &data);
8687
static std::vector<uint8_t> decodeBase64(const uint8_t *data);
8788
static std::vector<uint8_t> deriveSharedSecret(EVP_PKEY *pkey, EVP_PKEY *peerPKey);
@@ -129,7 +130,7 @@ struct EncryptionConsumer final : public DataConsumer {
129130
result_t write(const uint8_t *src, size_t size) final;
130131
result_t writeAAD(const std::vector<uint8_t> &data);
131132
result_t close() final;
132-
bool isError() final { return error != OK; }
133+
bool isError() final { return error != OK || dst.isError(); }
133134

134135
private:
135136
unique_free_t<EVP_CIPHER_CTX> ctx;
@@ -138,4 +139,22 @@ struct EncryptionConsumer final : public DataConsumer {
138139
std::vector<uint8_t> buf;
139140
};
140141

142+
struct DecryptionSource final : public DataSource {
143+
DecryptionSource(DataSource &src, const std::string &method, const std::vector<unsigned char> &key);
144+
DecryptionSource(DataSource &src, const EVP_CIPHER *cipher, const std::vector<unsigned char> &key);
145+
CDOC_DISABLE_MOVE_COPY(DecryptionSource)
146+
147+
result_t read(unsigned char* dst, size_t size) final;
148+
result_t readAAD(const std::vector<uint8_t>& data);
149+
result_t close();
150+
bool isError() final { return error != OK || src.isError(); }
151+
bool isEof() final { return src.isEof(); }
152+
153+
private:
154+
unique_free_t<EVP_CIPHER_CTX> ctx;
155+
DataSource &src;
156+
result_t error = OK;
157+
std::array<uint8_t, 16> tag {};
158+
};
159+
141160
}; // namespace libcdoc

cdoc/Io.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include <cdoc/CDoc.h>
2323

24+
#include <algorithm>
2425
#include <filesystem>
2526
#include <fstream>
2627

@@ -311,7 +312,7 @@ struct CDOC_EXPORT VectorSource : public DataSource {
311312

312313
result_t read(uint8_t *dst, size_t size) override {
313314
size = std::min<size_t>(size, _data.size() - _ptr);
314-
std::copy(_data.cbegin() + _ptr, _data.cbegin() + _ptr + size, dst);
315+
std::copy_n(_data.cbegin() + _ptr, size, dst);
315316
_ptr += size;
316317
return size;
317318
}

test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
add_executable(unittests libcdoc_boost.cpp ../cdoc/CDocCipher.cpp)
1+
add_executable(unittests libcdoc_boost.cpp ../cdoc/CDocCipher.cpp ../cdoc/Crypto.cpp)
22
target_compile_definitions(unittests PRIVATE DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data")
33
target_link_libraries(unittests OpenSSL::SSL cdoc Boost::unit_test_framework)
44

0 commit comments

Comments
 (0)