From 88bedb41fe82df312d62e364a5a216b62fc8807c Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Tue, 5 Nov 2019 14:32:28 -0500 Subject: Implement gnutls crypto provider (fixes #218) Thanks to Zdenek Dohnal for contributing the code used for the gnutls crypto provider. --- libqpdf/QPDFCryptoProvider.cc | 6 + libqpdf/QPDFCrypto_gnutls.cc | 277 ++++++++++++++++++++++++++++++++++++++ libqpdf/build.mk | 7 + libqpdf/qpdf/QPDFCrypto_gnutls.hh | 53 ++++++++ libqpdf/qpdf/qpdf-config.h.in | 3 + 5 files changed, 346 insertions(+) create mode 100644 libqpdf/QPDFCrypto_gnutls.cc create mode 100644 libqpdf/qpdf/QPDFCrypto_gnutls.hh (limited to 'libqpdf') diff --git a/libqpdf/QPDFCryptoProvider.cc b/libqpdf/QPDFCryptoProvider.cc index 58712f54..133a16f7 100644 --- a/libqpdf/QPDFCryptoProvider.cc +++ b/libqpdf/QPDFCryptoProvider.cc @@ -5,6 +5,9 @@ #ifdef USE_CRYPTO_NATIVE # include #endif +#ifdef USE_CRYPTO_GNUTLS +# include +#endif std::shared_ptr QPDFCryptoProvider::getImpl() @@ -42,6 +45,9 @@ QPDFCryptoProvider::QPDFCryptoProvider() : { #ifdef USE_CRYPTO_NATIVE registerImpl_internal("native"); +#endif +#ifdef USE_CRYPTO_GNUTLS + registerImpl_internal("gnutls"); #endif setDefaultProvider_internal(DEFAULT_CRYPTO); } diff --git a/libqpdf/QPDFCrypto_gnutls.cc b/libqpdf/QPDFCrypto_gnutls.cc new file mode 100644 index 00000000..d9383064 --- /dev/null +++ b/libqpdf/QPDFCrypto_gnutls.cc @@ -0,0 +1,277 @@ +#include +#include +#include +#include + +QPDFCrypto_gnutls::QPDFCrypto_gnutls() : + hash_ctx(nullptr), + cipher_ctx(nullptr), + sha2_bits(0), + encrypt(false), + cbc_mode(false), + aes_key_data(nullptr), + aes_key_len(0) +{ + memset(digest, 0, sizeof(digest)); +} + +QPDFCrypto_gnutls::~QPDFCrypto_gnutls() +{ + if (this->hash_ctx) + { + gnutls_hash_deinit(this->hash_ctx, digest); + } + if (cipher_ctx) + { + gnutls_cipher_deinit(this->cipher_ctx); + } + this->aes_key_data = nullptr; + this->aes_key_len = 0; +} + +void +QPDFCrypto_gnutls::MD5_init() +{ + MD5_finalize(); + int code = gnutls_hash_init(&this->hash_ctx, GNUTLS_DIG_MD5); + if (code < 0) + { + this->hash_ctx = nullptr; + throw std::runtime_error( + std::string("gnutls: MD5 error: ") + + std::string(gnutls_strerror(code))); + } +} + +void +QPDFCrypto_gnutls::MD5_update(unsigned char const* data, size_t len) +{ + gnutls_hash(this->hash_ctx, data, len); +} + +void +QPDFCrypto_gnutls::MD5_finalize() +{ + if (this->hash_ctx) + { + gnutls_hash_deinit(this->hash_ctx, this->digest); + this->hash_ctx = nullptr; + } +} + +void +QPDFCrypto_gnutls::MD5_digest(MD5_Digest d) +{ + memcpy(d, this->digest, sizeof(MD5_Digest)); +} + +void +QPDFCrypto_gnutls::RC4_init(unsigned char const* key_data, int key_len) +{ + RC4_finalize(); + if (key_len == -1) + { + key_len = QIntC::to_int( + strlen(reinterpret_cast(key_data))); + } + gnutls_datum_t key; + key.data = const_cast(key_data); + key.size = QIntC::to_uint(key_len); + + int code = gnutls_cipher_init( + &this->cipher_ctx, GNUTLS_CIPHER_ARCFOUR_128, &key, nullptr); + if (code < 0) + { + this->cipher_ctx = nullptr; + throw std::runtime_error( + std::string("gnutls: RC4 error: ") + + std::string(gnutls_strerror(code))); + } +} + +void +QPDFCrypto_gnutls::RC4_process(unsigned char* in_data, size_t len, + unsigned char* out_data) +{ + if (nullptr == out_data) + { + out_data = in_data; + } + gnutls_cipher_encrypt2(this->cipher_ctx, in_data, len, out_data, len); +} + +void +QPDFCrypto_gnutls::RC4_finalize() +{ + if (this->cipher_ctx) + { + gnutls_cipher_deinit(this->cipher_ctx); + this->cipher_ctx = nullptr; + } +} + +void +QPDFCrypto_gnutls::SHA2_init(int bits) +{ + SHA2_finalize(); + gnutls_digest_algorithm_t alg = GNUTLS_DIG_UNKNOWN; + switch (bits) + { + case 256: + alg = GNUTLS_DIG_SHA256; + break; + case 384: + alg = GNUTLS_DIG_SHA384; + break; + case 512: + alg = GNUTLS_DIG_SHA512; + break; + default: + badBits(); + break; + } + this->sha2_bits = bits; + int code = gnutls_hash_init(&this->hash_ctx, alg); + if (code < 0) + { + this->hash_ctx = nullptr; + throw std::runtime_error( + std::string("gnutls: SHA") + QUtil::int_to_string(bits) + + " error: " + std::string(gnutls_strerror(code))); + } +} + +void +QPDFCrypto_gnutls::SHA2_update(unsigned char const* data, size_t len) +{ + gnutls_hash(this->hash_ctx, data, len); +} + +void +QPDFCrypto_gnutls::SHA2_finalize() +{ + if (this->hash_ctx) + { + gnutls_hash_deinit(this->hash_ctx, this->digest); + this->hash_ctx = nullptr; + } +} + +std::string +QPDFCrypto_gnutls::SHA2_digest() +{ + std::string result; + switch (this->sha2_bits) + { + case 256: + result = std::string(reinterpret_cast(this->digest), 32); + break; + case 384: + result = std::string(reinterpret_cast(this->digest), 48); + break; + case 512: + result = std::string(reinterpret_cast(this->digest), 64); + break; + default: + badBits(); + break; + } + return result; +} + +void +QPDFCrypto_gnutls::rijndael_init( + bool encrypt, unsigned char const* key_data, size_t key_len, + bool cbc_mode, unsigned char* cbc_block) +{ + rijndael_finalize(); + this->encrypt = encrypt; + this->cbc_mode = cbc_mode; + if (! cbc_mode) + { + // Save the key so we can re-initialize. + this->aes_key_data = key_data; + this->aes_key_len = key_len; + } + + gnutls_cipher_algorithm_t alg = GNUTLS_CIPHER_UNKNOWN; + gnutls_datum_t cipher_key; + gnutls_datum_t iv; + + cipher_key.data = const_cast(key_data); + + switch(key_len) + { + case 16: + alg = GNUTLS_CIPHER_AES_128_CBC; + break; + case 32: + alg = GNUTLS_CIPHER_AES_256_CBC; + break; + case 24: + alg = GNUTLS_CIPHER_AES_192_CBC; + break; + default: + alg = GNUTLS_CIPHER_AES_128_CBC; + break; + } + + cipher_key.size = QIntC::to_uint(gnutls_cipher_get_key_size(alg)); + + iv.data = cbc_block; + iv.size = rijndael_buf_size; + + int code = gnutls_cipher_init(&this->cipher_ctx, alg, &cipher_key, &iv); + if (code < 0) + { + this->cipher_ctx = nullptr; + throw std::runtime_error( + std::string("gnutls: AES error: ") + + std::string(gnutls_strerror(code))); + } +} + +void +QPDFCrypto_gnutls::rijndael_process(unsigned char* in_data, + unsigned char* out_data) +{ + if (this->encrypt) + { + gnutls_cipher_encrypt2(this->cipher_ctx, + in_data, rijndael_buf_size, + out_data, rijndael_buf_size); + } + else + { + gnutls_cipher_decrypt2(this->cipher_ctx, + in_data, rijndael_buf_size, + out_data, rijndael_buf_size); + } + + // Gnutls doesn't support AES in ECB (non-CBC) mode, but the + // result is the same as if you just reset the cbc block to all + // zeroes each time. We jump through a few hoops here to make this + // work. + if (! this->cbc_mode) + { + static unsigned char zeroes[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + rijndael_init(this->encrypt, this->aes_key_data, this->aes_key_len, + false, zeroes); + } +} + +void +QPDFCrypto_gnutls::rijndael_finalize() +{ + if (this->cipher_ctx) + { + gnutls_cipher_deinit(this->cipher_ctx); + this->cipher_ctx = nullptr; + } +} + +void +QPDFCrypto_gnutls::badBits() +{ + throw std::logic_error("SHA2 (gnutls) has bits != 256, 384, or 512"); +} diff --git a/libqpdf/build.mk b/libqpdf/build.mk index 7e5f4aa3..ac904174 100644 --- a/libqpdf/build.mk +++ b/libqpdf/build.mk @@ -14,6 +14,9 @@ CRYPTO_NATIVE = \ libqpdf/sha2.c \ libqpdf/sha2big.c +CRYPTO_GNUTLS = \ + libqpdf/QPDFCrypto_gnutls.cc + SRCS_libqpdf = \ libqpdf/BitStream.cc \ libqpdf/BitWriter.cc \ @@ -94,6 +97,10 @@ ifeq ($(USE_CRYPTO_NATIVE), 1) SRCS_libqpdf += $(CRYPTO_NATIVE) endif +ifeq ($(USE_CRYPTO_GNUTLS), 1) +SRCS_libqpdf += $(CRYPTO_GNUTLS) +endif + # ----- CCSRCS_libqpdf = $(filter %.cc,$(SRCS_libqpdf)) diff --git a/libqpdf/qpdf/QPDFCrypto_gnutls.hh b/libqpdf/qpdf/QPDFCrypto_gnutls.hh new file mode 100644 index 00000000..e7e21182 --- /dev/null +++ b/libqpdf/qpdf/QPDFCrypto_gnutls.hh @@ -0,0 +1,53 @@ +#ifndef QPDFCRYPTO_GNUTLS_HH +#define QPDFCRYPTO_GNUTLS_HH + +#include +#include +#include +#include +#include + +class QPDFCrypto_gnutls: public QPDFCryptoImpl +{ + public: + QPDFCrypto_gnutls(); + + QPDF_DLL + virtual ~QPDFCrypto_gnutls(); + + virtual void MD5_init(); + virtual void MD5_update(unsigned char const* data, size_t len); + virtual void MD5_finalize(); + virtual void MD5_digest(MD5_Digest); + + virtual void RC4_init(unsigned char const* key_data, int key_len = -1); + virtual void RC4_process(unsigned char* in_data, size_t len, + unsigned char* out_data = 0); + virtual void RC4_finalize(); + + virtual void SHA2_init(int bits); + virtual void SHA2_update(unsigned char const* data, size_t len); + virtual void SHA2_finalize(); + virtual std::string SHA2_digest(); + + virtual void rijndael_init( + bool encrypt, unsigned char const* key_data, size_t key_len, + bool cbc_mode, unsigned char* cbc_block); + virtual void rijndael_process( + unsigned char* in_data, unsigned char* out_data); + virtual void rijndael_finalize(); + + private: + void badBits(); + + gnutls_hash_hd_t hash_ctx; + gnutls_cipher_hd_t cipher_ctx; + int sha2_bits; + bool encrypt; + bool cbc_mode; + char digest[64]; + unsigned char const* aes_key_data; + size_t aes_key_len; +}; + +#endif // QPDFCRYPTO_GNUTLS_HH diff --git a/libqpdf/qpdf/qpdf-config.h.in b/libqpdf/qpdf/qpdf-config.h.in index 20441efd..75d34c66 100644 --- a/libqpdf/qpdf/qpdf-config.h.in +++ b/libqpdf/qpdf/qpdf-config.h.in @@ -84,6 +84,9 @@ /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS +/* Whether to use the gnutls crypto provider */ +#undef USE_CRYPTO_GNUTLS + /* Whether to use the native crypto provider */ #undef USE_CRYPTO_NATIVE -- cgit v1.2.3-54-g00ecf