diff --git a/autoconf.mk.in b/autoconf.mk.in index 85a97aa8..41f51b44 100644 --- a/autoconf.mk.in +++ b/autoconf.mk.in @@ -31,6 +31,7 @@ OBJDUMP=@OBJDUMP@ GENDEPS=@GENDEPS@ LIBTOOL=@LIBTOOL@ USE_CRYPTO_NATIVE=@USE_CRYPTO_NATIVE@ +USE_CRYPTO_GNUTLS=@USE_CRYPTO_GNUTLS@ DOCBOOKX_DTD=@DOCBOOKX_DTD@ FOP=@FOP@ XSLTPROC=@XSLTPROC@ diff --git a/autofiles.sums b/autofiles.sums index 74ee4f26..9b3c766a 100644 --- a/autofiles.sums +++ b/autofiles.sums @@ -1,6 +1,6 @@ -2b5c5a808c353b8df9e28e8cfb1e7d37114a2cad37eaede5bfe4354acae804d0 configure.ac +97f3ed3cd8b491f0ceeb57baa40f4ed9c4be188692da1d13c93ef318c45cc4ae configure.ac d3f9ee6f6f0846888d9a10fd3dad2e4b1258be84205426cf04d7cef02d61dad7 aclocal.m4 -7fc840fce5d372e92aa676e0040213a0f239cc8c01b6d6ef53c82043ceda571a libqpdf/qpdf/qpdf-config.h.in +2e4cd495837be1b8454a4d8aef541b000988634be89d9c05a9cf5de67dffef5e libqpdf/qpdf/qpdf-config.h.in 5297971a0ef90bcd5563eb3f7127a032bb76d3ae2af7258bf13479caf8983a60 m4/ax_cxx_compile_stdcxx.m4 35bc5c645dc42d47f2daeea06f8f3e767c8a1aee6a35eb2b4854fd2ce66c3413 m4/ax_random_device.m4 37f8897d5f68d7d484e5457832a8f190ddb7507fa2a467cb7ee2be40a4364643 m4/libtool.m4 diff --git a/configure b/configure index be0bc3a4..5ea9989c 100755 --- a/configure +++ b/configure @@ -643,6 +643,9 @@ DOCBOOK_XHTML SHOW_FAILED_TEST_OUTPUT QPDF_SKIP_TEST_COMPARE_IMAGES DEFAULT_CRYPTO +USE_CRYPTO_GNUTLS +pc_gnutls_LIBS +pc_gnutls_CFLAGS USE_CRYPTO_NATIVE CXXWFLAGS WFLAGS @@ -780,6 +783,7 @@ enable_werror enable_int_warnings enable_implicit_crypto enable_crypto_native +enable_crypto_gnutls with_default_crypto enable_test_compare_images enable_show_failed_test_output @@ -811,7 +815,9 @@ PKG_CONFIG_LIBDIR pc_zlib_CFLAGS pc_zlib_LIBS pc_libjpeg_CFLAGS -pc_libjpeg_LIBS' +pc_libjpeg_LIBS +pc_gnutls_CFLAGS +pc_gnutls_LIBS' # Initialize some variables set by options. @@ -1466,6 +1472,8 @@ Optional Features: are not explicitly requested; true by default --enable-crypto-native whether to include support for native crypto provider + --enable-crypto-gnutls whether to include support for gnutls crypto + provider --enable-test-compare-images whether to compare images in test suite; disabled by default, enabling requires ghostscript and tiffcmp @@ -1535,6 +1543,10 @@ Some influential environment variables: C compiler flags for pc_libjpeg, overriding pkg-config pc_libjpeg_LIBS linker flags for pc_libjpeg, overriding pkg-config + pc_gnutls_CFLAGS + C compiler flags for pc_gnutls, overriding pkg-config + pc_gnutls_LIBS + linker flags for pc_gnutls, overriding pkg-config Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. @@ -17625,6 +17637,122 @@ $as_echo "#define USE_CRYPTO_NATIVE 1" >>confdefs.h fi + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pc_gnutls" >&5 +$as_echo_n "checking for pc_gnutls... " >&6; } + +if test -n "$pc_gnutls_CFLAGS"; then + pkg_cv_pc_gnutls_CFLAGS="$pc_gnutls_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"gnutls\""; } >&5 + ($PKG_CONFIG --exists --print-errors "gnutls") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_pc_gnutls_CFLAGS=`$PKG_CONFIG --cflags "gnutls" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$pc_gnutls_LIBS"; then + pkg_cv_pc_gnutls_LIBS="$pc_gnutls_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"gnutls\""; } >&5 + ($PKG_CONFIG --exists --print-errors "gnutls") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_pc_gnutls_LIBS=`$PKG_CONFIG --libs "gnutls" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + pc_gnutls_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gnutls" 2>&1` + else + pc_gnutls_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gnutls" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$pc_gnutls_PKG_ERRORS" >&5 + + GNUTLS_FOUND=0 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + GNUTLS_FOUND=0 +else + pc_gnutls_CFLAGS=$pkg_cv_pc_gnutls_CFLAGS + pc_gnutls_LIBS=$pkg_cv_pc_gnutls_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + GNUTLS_FOUND=1 +fi + +IMPLICIT_GNUTLS=0 +USE_CRYPTO_GNUTLS=0 + +# Check whether --enable-crypto-gnutls was given. +if test "${enable_crypto_gnutls+set}" = set; then : + enableval=$enable_crypto_gnutls; if test "$enableval" = "yes"; then + USE_CRYPTO_GNUTLS=1 + else + USE_CRYPTO_GNUTLS=0 + fi +else + IMPLICIT_GNUTLS=$IMPLICIT_CRYPTO +fi + + +if test "$IMPLICIT_GNUTLS" = "1"; then + USE_CRYPTO_GNUTLS=$GNUTLS_FOUND + if test "$USE_CRYPTO_GNUTLS" = "1"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: enabling gnutls crypto provider since gnutls is available" >&5 +$as_echo "$as_me: enabling gnutls crypto provider since gnutls is available" >&6;} + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not enabling gnutls crypto provider since gnutls was not found" >&5 +$as_echo "$as_me: not enabling gnutls crypto provider since gnutls was not found" >&6;} + fi +fi + +if test "$USE_CRYPTO_GNUTLS" = "1" -a "$GNUTLS_FOUND" = "0"; then + as_fn_error $? "unable to use requested gnutls crypto provider without gnutls" "$LINENO" 5 +fi + +if test "$USE_CRYPTO_GNUTLS" = "1"; then + CFLAGS="$CFLAGS $pc_gnutls_CFLAGS" + CXXFLAGS="$CXXFLAGS $pc_gnutls_CXXFLAGS" + LIBS="$LIBS $pc_gnutls_LIBS" + +$as_echo "#define USE_CRYPTO_GNUTLS 1" >>confdefs.h + + DEFAULT_CRYPTO=gnutls +elif test "$GNUTLS_FOUND" = "1"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: not linking with gnutls even though it is available" >&5 +$as_echo "$as_me: not linking with gnutls even though it is available" >&6;} +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking which crypto to use by default" >&5 $as_echo_n "checking which crypto to use by default... " >&6; } @@ -17650,6 +17778,11 @@ case "$DEFAULT_CRYPTO" in bad_crypto=1 fi ;; + "gnutls") + if test "$USE_CRYPTO_GNUTLS" != "1"; then + bad_crypto=1 + fi + ;; *) bad_crypto=1 ;; diff --git a/configure.ac b/configure.ac index d7223d06..1f6b151a 100644 --- a/configure.ac +++ b/configure.ac @@ -506,6 +506,49 @@ if test "$USE_CRYPTO_NATIVE" = "1"; then DEFAULT_CRYPTO=native fi +dnl If the gnutls provider is explicitly requested, require gnutls. If +dnl the gnutls provider is not explicitly disabled, enable it if +dnl gnutls is available. If the gnutls provider is explicitly +dnl disabled, do not link with gnutls even if present. + +PKG_CHECK_MODULES([pc_gnutls], [gnutls], [GNUTLS_FOUND=1], [GNUTLS_FOUND=0]) + +IMPLICIT_GNUTLS=0 +USE_CRYPTO_GNUTLS=0 +AC_SUBST(USE_CRYPTO_GNUTLS) +AC_ARG_ENABLE(crypto-gnutls, + AS_HELP_STRING([--enable-crypto-gnutls], + [whether to include support for gnutls crypto provider]), + [if test "$enableval" = "yes"; then + USE_CRYPTO_GNUTLS=1 + else + USE_CRYPTO_GNUTLS=0 + fi], + [IMPLICIT_GNUTLS=$IMPLICIT_CRYPTO]) + +if test "$IMPLICIT_GNUTLS" = "1"; then + USE_CRYPTO_GNUTLS=$GNUTLS_FOUND + if test "$USE_CRYPTO_GNUTLS" = "1"; then + AC_MSG_NOTICE(enabling gnutls crypto provider since gnutls is available) + else + AC_MSG_NOTICE(not enabling gnutls crypto provider since gnutls was not found) + fi +fi + +if test "$USE_CRYPTO_GNUTLS" = "1" -a "$GNUTLS_FOUND" = "0"; then + AC_MSG_ERROR(unable to use requested gnutls crypto provider without gnutls) +fi + +if test "$USE_CRYPTO_GNUTLS" = "1"; then + CFLAGS="$CFLAGS $pc_gnutls_CFLAGS" + CXXFLAGS="$CXXFLAGS $pc_gnutls_CXXFLAGS" + LIBS="$LIBS $pc_gnutls_LIBS" + AC_DEFINE([USE_CRYPTO_GNUTLS], 1, [Whether to use the gnutls crypto provider]) + DEFAULT_CRYPTO=gnutls +elif test "$GNUTLS_FOUND" = "1"; then + AC_MSG_NOTICE(not linking with gnutls even though it is available) +fi + dnl Allow the default crypto provider to be specified explicitly. AC_MSG_CHECKING(which crypto to use by default) @@ -527,6 +570,11 @@ case "$DEFAULT_CRYPTO" in bad_crypto=1 fi ;; + "gnutls") + if test "$USE_CRYPTO_GNUTLS" != "1"; then + bad_crypto=1 + fi + ;; *) bad_crypto=1 ;; 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