#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::provideRandomData(unsigned char* data, size_t len) { int code = gnutls_rnd(GNUTLS_RND_KEY, data, len); if (code < 0) { throw std::runtime_error( std::string("gnutls: random number generation error: ") + std::string(gnutls_strerror(code))); } } 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 const* in_data, size_t len, unsigned char* out_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") + std::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"); }