Implement gnutls crypto provider (fixes #218)

Thanks to Zdenek Dohnal <zdohnal@redhat.com> for contributing the code
used for the gnutls crypto provider.
This commit is contained in:
Jay Berkenbilt 2019-11-05 14:32:28 -05:00
parent cc14523440
commit 88bedb41fe
9 changed files with 531 additions and 3 deletions

View File

@ -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@

View File

@ -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

135
configure vendored
View File

@ -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
;;

View File

@ -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
;;

View File

@ -5,6 +5,9 @@
#ifdef USE_CRYPTO_NATIVE
# include <qpdf/QPDFCrypto_native.hh>
#endif
#ifdef USE_CRYPTO_GNUTLS
# include <qpdf/QPDFCrypto_gnutls.hh>
#endif
std::shared_ptr<QPDFCryptoImpl>
QPDFCryptoProvider::getImpl()
@ -42,6 +45,9 @@ QPDFCryptoProvider::QPDFCryptoProvider() :
{
#ifdef USE_CRYPTO_NATIVE
registerImpl_internal<QPDFCrypto_native>("native");
#endif
#ifdef USE_CRYPTO_GNUTLS
registerImpl_internal<QPDFCrypto_gnutls>("gnutls");
#endif
setDefaultProvider_internal(DEFAULT_CRYPTO);
}

View File

@ -0,0 +1,277 @@
#include <qpdf/QPDFCrypto_gnutls.hh>
#include <qpdf/QIntC.hh>
#include <qpdf/QUtil.hh>
#include <cstring>
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<char const*>(key_data)));
}
gnutls_datum_t key;
key.data = const_cast<unsigned char*>(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<char*>(this->digest), 32);
break;
case 384:
result = std::string(reinterpret_cast<char*>(this->digest), 48);
break;
case 512:
result = std::string(reinterpret_cast<char*>(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<unsigned char*>(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");
}

View File

@ -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))

View File

@ -0,0 +1,53 @@
#ifndef QPDFCRYPTO_GNUTLS_HH
#define QPDFCRYPTO_GNUTLS_HH
#include <qpdf/DLL.h>
#include <qpdf/QPDFCryptoImpl.hh>
#include <memory>
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
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

View File

@ -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