Blob Blame History Raw
diff --git a/configure.ac b/configure.ac
index a9f0316..6ff59ef 100644
--- a/configure.ac
+++ b/configure.ac
@@ -348,6 +348,36 @@ AC_ARG_ENABLE(show-failed-test-output,
     fi],
    [SHOW_FAILED_TEST_OUTPUT=0])
 
+AC_PATH_TOOL(PKGCONFIG, pkg-config)
+
+AC_MSG_CHECKING(if cryptography will be supplied by GNU TLS)
+AC_ARG_ENABLE(gnutls,
+   AS_HELP_STRING([--enable-gnutls],
+                  [enable cryptography by GNUTLS]),
+   [if test "$enableval" = "yes"; then
+      if test "x$PKGCONFIG" != x ; then
+        AC_TRY_COMPILE([
+        #include <gnutls/crypto.h>], , , AC_MSG_ERROR([GNU TLS development headers are needed.]))
+        GNUTLSFLAGS=`pkg-config --cflags gnutls`
+        GNUTLSLIBS=`pkg-config --libs gnutls`
+        LIBS="$LIBS $GNUTLSLIBS"
+        CPPFLAGS="$CPPFLAGS $GNUTLSFLAGS"
+        AC_DEFINE([HAVE_GNUTLS], [1], [If cryptography will be supplied by GNU TLS])
+        HAVE_GNUTLS=1
+      else
+        HAVE_GNUTLS=0
+        AC_MSG_ERROR([pkg-config is needed to configure compilation with GNU TLS.])
+      fi
+    else
+      HAVE_GNUTLS=0
+    fi],
+   [HAVE_GNUTLS=0])
+if test "$HAVE_GNUTLS" = "1" ; then
+   AC_MSG_RESULT(yes)
+else
+   AC_MSG_RESULT(no)
+fi
+
 AC_ARG_WITH(docbook-xsl,
    AS_HELP_STRING([--with-docbook-xsl=DIR],
 		  [location of docbook 4.x xml stylesheets]),
diff --git a/libqpdf/MD5-gnutls.cc b/libqpdf/MD5-gnutls.cc
new file mode 100644
index 0000000..5611b3e
--- /dev/null
+++ b/libqpdf/MD5-gnutls.cc
@@ -0,0 +1,61 @@
+#include <gnutls/crypto.h>
+#include <qpdf/MD5.hh>
+#include <qpdf/QUtil.hh>
+
+
+// MD5 initialization. Begins an MD5 operation, writing a new context.
+void MD5::init()
+{
+    int ret;
+
+    ret = gnutls_hash_init(&ctx, GNUTLS_DIG_MD5);
+    if (ret < 0) {
+	QUtil::throw_system_error(
+	    std::string("GNU TLS: MD5 error:") + std::string(gnutls_strerror(ret)));
+	ctx = nullptr;
+    }
+
+    finalized = false;
+    memset(digest_val, 0, sizeof(digest_val));
+}
+
+// MD5 block update operation. Continues an MD5 message-digest
+// operation, processing another message block, and updating the
+// context.
+
+void MD5::update(unsigned char *input,
+		 unsigned int inputLen)
+{
+    if (ctx != nullptr && input != nullptr)
+	gnutls_hash(ctx, input, inputLen);
+}
+
+// MD5 finalization. Ends an MD5 message-digest operation, writing the
+// the message digest and zeroizing the context.
+void MD5::final()
+{
+    if (finalized)
+	return;
+
+    if (ctx != nullptr)
+    {
+	gnutls_hash_deinit(ctx, digest_val);
+	ctx = nullptr;
+    }
+
+    finalized = true;
+}
+
+// MD5 basic transformation. Transforms state based on block.
+void MD5::transform(UINT4 state[4], unsigned char block[64])
+{}
+
+// Encodes input (UINT4) into output (unsigned char). Assumes len is a
+// multiple of 4.
+void MD5::encode(unsigned char *output, UINT4 *input, unsigned int len)
+{}
+
+// Decodes input (unsigned char) into output (UINT4). Assumes len is a
+// multiple of 4.
+void MD5::decode(UINT4 *output, unsigned char *input, unsigned int len)
+{}
diff --git a/libqpdf/MD5.cc b/libqpdf/MD5.cc
index 0504e2d..19dce79 100644
--- a/libqpdf/MD5.cc
+++ b/libqpdf/MD5.cc
@@ -36,6 +36,10 @@
 #include <string.h>
 #include <errno.h>
 
+#ifdef HAVE_GNUTLS
+# include "MD5-gnutls.cc"
+#else
+
 int const S11 = 7;
 int const S12 = 12;
 int const S13 = 17;
@@ -294,6 +298,8 @@ void MD5::decode(UINT4 *output, unsigned char *input, unsigned int len)
             (static_cast<UINT4>(input[j+3]) << 24);
 }
 
+#endif /* HAVE_GNUTLS */
+
 // Public functions
 
 MD5::MD5()
diff --git a/libqpdf/Pl_AES_PDF-gnutls.cc b/libqpdf/Pl_AES_PDF-gnutls.cc
new file mode 100644
index 0000000..7704e6d
--- /dev/null
+++ b/libqpdf/Pl_AES_PDF-gnutls.cc
@@ -0,0 +1,246 @@
+#include <qpdf/Pl_AES_PDF.hh>
+#include <gnutls/crypto.h>
+#include <qpdf/rijndael-gnutls.h>
+
+bool Pl_AES_PDF::use_static_iv = false;
+
+Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next,
+		       bool encrypt, unsigned char const* key,
+                       unsigned int key_bytes) :
+    Pipeline(identifier, next),
+    encrypt(encrypt),
+    cbc_mode(true),
+    first(true),
+    offset(0),
+    nrounds(0),
+    use_zero_iv(false),
+    use_specified_iv(false),
+    disable_padding(false)
+{
+    unsigned int keybits = 8 * key_bytes;
+    this->keylen = key_bytes;
+    this->ctx = nullptr;
+    assert(key_bytes == KEYLENGTH(keybits));
+    this->key = new unsigned char[key_bytes];
+    this->rk = new uint32_t[RKLENGTH(keybits)];
+    unsigned int rk_bytes = RKLENGTH(keybits) * sizeof(uint32_t);
+    std::memcpy(this->key, key, key_bytes);
+    std::memset(this->rk, 0, rk_bytes);
+    std::memset(this->inbuf, 0, this->buf_size);
+    std::memset(this->outbuf, 0, this->buf_size);
+    std::memset(this->cbc_block, 0, this->buf_size);
+}
+
+Pl_AES_PDF::~Pl_AES_PDF()
+{
+    delete [] this->key;
+    delete [] this->rk;
+}
+
+void
+Pl_AES_PDF::useZeroIV()
+{
+    this->use_zero_iv = true;
+}
+
+void
+Pl_AES_PDF::disablePadding()
+{
+    this->disable_padding = true;
+}
+
+void
+Pl_AES_PDF::setIV(unsigned char const* iv, size_t bytes)
+{
+    if (bytes != this->buf_size)
+    {
+        throw std::logic_error(
+            "Pl_AES_PDF: specified initialization vector"
+            " size in bytes must be " + QUtil::int_to_string(bytes));
+    }
+    this->use_specified_iv = true;
+    memcpy(this->specified_iv, iv, bytes);
+}
+
+void
+Pl_AES_PDF::disableCBC()
+{
+    this->cbc_mode = false;
+}
+
+void
+Pl_AES_PDF::useStaticIV()
+{
+    use_static_iv = true;
+}
+
+void
+Pl_AES_PDF::write(unsigned char* data, size_t len)
+{
+    size_t bytes_left = len;
+    unsigned char* p = data;
+
+    while (bytes_left > 0)
+    {
+	if (this->offset == this->buf_size)
+	{
+	    flush(false);
+	}
+
+	size_t available = this->buf_size - this->offset;
+	size_t bytes = (bytes_left < available ? bytes_left : available);
+	bytes_left -= bytes;
+	std::memcpy(this->inbuf + this->offset, p, bytes);
+	this->offset += bytes;
+	p += bytes;
+    }
+}
+
+void
+Pl_AES_PDF::finish()
+{
+    if (this->encrypt)
+    {
+	if (this->offset == this->buf_size)
+	{
+	    flush(false);
+	}
+        if (! this->disable_padding)
+        {
+            // Pad as described in section 3.5.1 of version 1.7 of the PDF
+            // specification, including providing an entire block of padding
+            // if the input was a multiple of 16 bytes.
+            unsigned char pad =
+                static_cast<unsigned char>(this->buf_size - this->offset);
+            memset(this->inbuf + this->offset, pad, pad);
+            this->offset = this->buf_size;
+            flush(false);
+        }
+    }
+    else
+    {
+	if (this->offset != this->buf_size)
+	{
+	    // This is never supposed to happen as the output is
+	    // always supposed to be padded.  However, we have
+	    // encountered files for which the output is not a
+	    // multiple of the block size.  In this case, pad with
+	    // zeroes and hope for the best.
+	    assert(this->buf_size > this->offset);
+	    std::memset(this->inbuf + this->offset, 0,
+			this->buf_size - this->offset);
+	    this->offset = this->buf_size;
+	}
+	flush(! this->disable_padding);
+    }
+
+    if (this->cbc_mode)
+	rijndaelFinish(this->ctx);
+
+    getNext()->finish();
+}
+
+void
+Pl_AES_PDF::initializeVector()
+{
+    if (use_zero_iv)
+    {
+	for (unsigned int i = 0; i < this->buf_size; ++i)
+	{
+	    this->cbc_block[i] = 0;
+	}
+    }
+    else if (use_specified_iv)
+    {
+        std::memcpy(this->cbc_block, this->specified_iv, this->buf_size);
+    }
+    else if (use_static_iv)
+    {
+	for (unsigned int i = 0; i < this->buf_size; ++i)
+	{
+	    this->cbc_block[i] = 14 * (1 + i);
+	}
+    }
+    else
+    {
+        QUtil::initializeWithRandomBytes(this->cbc_block, this->buf_size);
+    }
+}
+
+void
+Pl_AES_PDF::flush(bool strip_padding)
+{
+    assert(this->offset == this->buf_size);
+
+    if (first)
+    {
+	first = false;
+	if (this->cbc_mode)
+	{
+	    if (encrypt)
+	    {
+		// Set cbc_block to the initialization vector, and if
+		// not zero, write it to the output stream.
+		initializeVector();
+                if (! (this->use_zero_iv || this->use_specified_iv))
+                {
+                    getNext()->write(this->cbc_block, this->buf_size);
+                }
+	    }
+	    else if (this->use_zero_iv || this->use_specified_iv)
+            {
+                // Initialize vector with zeroes; zero vector was not
+                // written to the beginning of the input file.
+                initializeVector();
+            }
+            else
+	    {
+		// Take the first block of input as the initialization
+		// vector.  There's nothing to write at this time.
+		memcpy(this->cbc_block, this->inbuf, this->buf_size);
+		this->offset = 0;
+		rijndaelInit(&(this->ctx), this->key, this->keylen, this->cbc_block, this->buf_size);
+		return;
+	    }
+	    rijndaelInit(&(this->ctx), this->key, this->keylen, this->cbc_block, this->buf_size);
+	}
+    }
+
+    if (this->encrypt)
+    {
+	if (this->cbc_mode)
+	    rijndaelCBCEncrypt(this->ctx, this->inbuf, this->outbuf, this->buf_size);
+	else
+	    rijndaelECBEncrypt(this->key, this->keylen, this->cbc_block, this->inbuf, this->outbuf, this->buf_size);
+    }
+    else
+    {
+	if (this->cbc_mode)
+	    rijndaelCBCDecrypt(this->ctx, this->inbuf, this->outbuf, this->buf_size);
+	else
+	    rijndaelECBDecrypt(this->key, this->keylen, this->cbc_block, this->inbuf, this->outbuf, this->buf_size);
+    }
+    unsigned int bytes = this->buf_size;
+    if (strip_padding)
+    {
+	unsigned char last = this->outbuf[this->buf_size - 1];
+	if (last <= this->buf_size)
+	{
+	    bool strip = true;
+	    for (unsigned int i = 1; i <= last; ++i)
+	    {
+		if (this->outbuf[this->buf_size - i] != last)
+		{
+		    strip = false;
+		    break;
+		}
+	    }
+	    if (strip)
+	    {
+		bytes -= last;
+	    }
+	}
+    }
+    getNext()->write(this->outbuf, bytes);
+    this->offset = 0;
+}
diff --git a/libqpdf/Pl_AES_PDF.cc b/libqpdf/Pl_AES_PDF.cc
index 5c493cb..ac8bde4 100644
--- a/libqpdf/Pl_AES_PDF.cc
+++ b/libqpdf/Pl_AES_PDF.cc
@@ -3,10 +3,15 @@
 #include <cstring>
 #include <assert.h>
 #include <stdexcept>
-#include <qpdf/rijndael.h>
+
 #include <string>
 #include <stdlib.h>
 
+#ifdef HAVE_GNUTLS
+# include "Pl_AES_PDF-gnutls.cc"
+#else
+# include <qpdf/rijndael.h>
+
 bool Pl_AES_PDF::use_static_iv = false;
 
 Pl_AES_PDF::Pl_AES_PDF(char const* identifier, Pipeline* next,
@@ -263,3 +268,4 @@ Pl_AES_PDF::flush(bool strip_padding)
     getNext()->write(this->outbuf, bytes);
     this->offset = 0;
 }
+#endif
diff --git a/libqpdf/Pl_SHA2.cc b/libqpdf/Pl_SHA2.cc
index 17eff7e..9156451 100644
--- a/libqpdf/Pl_SHA2.cc
+++ b/libqpdf/Pl_SHA2.cc
@@ -13,6 +13,12 @@ Pl_SHA2::Pl_SHA2(int bits, Pipeline* next) :
     {
         resetBits(bits);
     }
+
+#ifdef HAVE_GNUTLS
+    this->ctx256 = nullptr;
+    this->ctx384 = nullptr;
+    this->ctx512 = nullptr;
+#endif
 }
 
 Pl_SHA2::~Pl_SHA2()
diff --git a/libqpdf/RC4-gnutls.cc b/libqpdf/RC4-gnutls.cc
new file mode 100644
index 0000000..093a5b9
--- /dev/null
+++ b/libqpdf/RC4-gnutls.cc
@@ -0,0 +1,51 @@
+#include <qpdf/RC4.hh>
+#include <qpdf/QUtil.hh>
+#include <gnutls/crypto.h>
+
+
+RC4::RC4(unsigned char const* key_data, int key_len)
+{
+    if (key_len == -1)
+    {
+	key_len = strlen(reinterpret_cast<char const*>(key_data));
+    }
+
+    int ret;
+    gnutls_cipher_algorithm_t alg = GNUTLS_CIPHER_ARCFOUR_128;
+
+    this->key.data = const_cast<unsigned char*>(key_data);
+    this->key.size = key_len;
+
+    ret = gnutls_cipher_init(&(this->ctx),
+                             alg,
+                             &(this->key),
+                             NULL);
+    if (ret < 0)
+    {
+	QUtil::throw_system_error(
+	    std::string("GNU TLS: RC4 error: ") + std::string(gnutls_strerror(ret)));
+	this->ctx = nullptr;
+    }
+}
+
+RC4::~RC4()
+{
+    if (this->ctx != nullptr)
+    {
+	gnutls_cipher_deinit(this->ctx);
+	this->ctx = nullptr;
+    }
+}
+
+void
+RC4::process(unsigned char *in_data, int len, unsigned char* out_data)
+{
+    if (out_data == 0)
+    {
+	// Convert in place
+	out_data = in_data;
+    }
+
+    if (this->ctx != nullptr && in_data != nullptr)
+	gnutls_cipher_encrypt2(this->ctx, in_data, len, out_data, len);
+}
diff --git a/libqpdf/RC4.cc b/libqpdf/RC4.cc
index 7639a36..c1288be 100644
--- a/libqpdf/RC4.cc
+++ b/libqpdf/RC4.cc
@@ -2,6 +2,10 @@
 
 #include <string.h>
 
+#ifdef HAVE_GNUTLS
+# include "RC4-gnutls.cc"
+#else
+
 static void swap_byte(unsigned char &a, unsigned char &b)
 {
     unsigned char t;
@@ -53,3 +57,4 @@ RC4::process(unsigned char *in_data, int len, unsigned char* out_data)
 	out_data[i] = in_data[i] ^ key.state[xor_index];
     }
 }
+#endif
diff --git a/libqpdf/SecureRandomDataProvider.cc b/libqpdf/SecureRandomDataProvider.cc
index fe9f1d4..d72269e 100644
--- a/libqpdf/SecureRandomDataProvider.cc
+++ b/libqpdf/SecureRandomDataProvider.cc
@@ -11,6 +11,10 @@
 # endif
 #endif
 
+#ifdef HAVE_GNUTLS
+# include <gnutls/crypto.h>
+#endif
+
 SecureRandomDataProvider::SecureRandomDataProvider()
 {
 }
@@ -89,7 +93,19 @@ class WindowsCryptProvider
 void
 SecureRandomDataProvider::provideRandomData(unsigned char* data, size_t len)
 {
-#if defined(_WIN32)
+#if defined(HAVE_GNUTLS)
+
+    int ret = 0;
+
+    ret = gnutls_rnd(GNUTLS_RND_NONCE, data, len);
+    if (ret < 0)
+    {
+        throw std::runtime_error(
+            "GNU TLS: unable to generate secure random data " +
+            std::string(gnutls_strerror(ret)));
+    }
+
+#elif defined(_WIN32)
 
     // Optimization: make the WindowsCryptProvider static as long as
     // it can be done in a thread-safe fashion.
diff --git a/libqpdf/qpdf/MD5.hh b/libqpdf/qpdf/MD5.hh
index 4cfe027..bb9f756 100644
--- a/libqpdf/qpdf/MD5.hh
+++ b/libqpdf/qpdf/MD5.hh
@@ -11,6 +11,10 @@
 #endif
 #include <string>
 
+#ifdef HAVE_GNUTLS
+# include <gnutls/crypto.h>
+#endif
+
 class MD5
 {
   public:
@@ -87,6 +91,9 @@ class MD5
 
     bool finalized;
     Digest digest_val;
+#ifdef HAVE_GNUTLS
+    gnutls_hash_hd_t ctx;
+#endif
 };
 
 #endif // __MD5_HH__
diff --git a/libqpdf/qpdf/Pl_AES_PDF.hh b/libqpdf/qpdf/Pl_AES_PDF.hh
index 9aa73ad..8059e7d 100644
--- a/libqpdf/qpdf/Pl_AES_PDF.hh
+++ b/libqpdf/qpdf/Pl_AES_PDF.hh
@@ -7,6 +7,10 @@
 # include <stdint.h>
 #endif
 
+#ifdef HAVE_GNUTLS
+# include <gnutls/crypto.h>
+#endif
+
 // This pipeline implements AES-128 and AES-256 with CBC and block
 // padding as specified in the PDF specification.
 
@@ -64,6 +68,11 @@ class Pl_AES_PDF: public Pipeline
     bool use_zero_iv;
     bool use_specified_iv;
     bool disable_padding;
+
+#ifdef HAVE_GNUTLS
+    gnutls_cipher_hd_t ctx;
+    unsigned int keylen;
+#endif
 };
 
 #endif // __PL_AES_PDF_HH__
diff --git a/libqpdf/qpdf/RC4.hh b/libqpdf/qpdf/RC4.hh
index c26f3d0..1421a08 100644
--- a/libqpdf/qpdf/RC4.hh
+++ b/libqpdf/qpdf/RC4.hh
@@ -1,6 +1,12 @@
 #ifndef __RC4_HH__
 #define __RC4_HH__
 
+#include <qpdf/qpdf-config.h>
+
+#ifdef HAVE_GNUTLS
+# include <gnutls/crypto.h>
+#endif
+
 class RC4
 {
   public:
@@ -10,7 +16,15 @@ class RC4
     // out_data = 0 means to encrypt/decrypt in place
     void process(unsigned char* in_data, int len, unsigned char* out_data = 0);
 
+#ifdef HAVE_GNUTLS
+    ~RC4();
+#endif
+
   private:
+#ifdef HAVE_GNUTLS
+    gnutls_cipher_hd_t ctx;
+    gnutls_datum_t key;
+#else
     class RC4Key
     {
       public:
@@ -20,6 +34,7 @@ class RC4
     };
 
     RC4Key key;
+#endif
 };
 
 #endif // __RC4_HH__
diff --git a/libqpdf/qpdf/rijndael-gnutls.h b/libqpdf/qpdf/rijndael-gnutls.h
new file mode 100644
index 0000000..a3e7ff0
--- /dev/null
+++ b/libqpdf/qpdf/rijndael-gnutls.h
@@ -0,0 +1,32 @@
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+
+#include <gnutls/crypto.h>
+
+void rijndaelInit(gnutls_cipher_hd_t * context, unsigned char * key,
+  unsigned int keylen, unsigned char cbc_block[16], unsigned int buf_size);
+int rijndaelSetupEncrypt(uint32_t *rk, const unsigned char *key,
+  int keybits);
+int rijndaelSetupDecrypt(uint32_t *rk, const unsigned char *key,
+  int keybits);
+void rijndaelCBCEncrypt(gnutls_cipher_hd_t context,
+  const unsigned char plaintext[16], unsigned char ciphertext[16],
+  unsigned int buf_size);
+void rijndaelCBCDecrypt(gnutls_cipher_hd_t context,
+  const unsigned char ciphertext[16], unsigned char plaintext[16],
+  unsigned int buf_size);
+void rijndaelECBEncrypt(unsigned char * key, unsigned int keylen,
+  unsigned char cbc_block[16], const unsigned char plaintext[16],
+  unsigned char ciphertext[16], unsigned int buf_size);
+void rijndaelECBDecrypt(unsigned char * key, unsigned int keylen,
+  unsigned char cbc_block[16], const unsigned char ciphertext[16],
+  unsigned char plaintext[16], unsigned int buf_size);
+void rijndaelFinish(gnutls_cipher_hd_t context);
+
+#define KEYLENGTH(keybits) ((keybits)/8)
+#define RKLENGTH(keybits)  ((keybits)/8+28)
+#define NROUNDS(keybits)   ((keybits)/32+6)
diff --git a/libqpdf/qpdf/rijndael.h b/libqpdf/qpdf/rijndael.h
index a9cd71d..9698a8b 100644
--- a/libqpdf/qpdf/rijndael.h
+++ b/libqpdf/qpdf/rijndael.h
@@ -9,6 +9,10 @@
 # include <stdint.h>
 #endif
 
+#ifdef HAVE_GNUTLS
+# include "qpdf/rijndael-gnutls.h"
+#else
+
 int rijndaelSetupEncrypt(uint32_t *rk, const unsigned char *key,
   int keybits);
 int rijndaelSetupDecrypt(uint32_t *rk, const unsigned char *key,
@@ -22,4 +26,6 @@ void rijndaelDecrypt(const uint32_t *rk, int nrounds,
 #define RKLENGTH(keybits)  ((keybits)/8+28)
 #define NROUNDS(keybits)   ((keybits)/32+6)
 
+#endif /* HAVE_GNUTLS */
+
 #endif
diff --git a/libqpdf/rijndael-gnutls.cc b/libqpdf/rijndael-gnutls.cc
new file mode 100644
index 0000000..dd3fdb2
--- /dev/null
+++ b/libqpdf/rijndael-gnutls.cc
@@ -0,0 +1,116 @@
+#include <cstring>
+#include <gnutls/crypto.h>
+#include <qpdf/QUtil.hh>
+#include <qpdf/rijndael-gnutls.h>
+
+typedef uint32_t u32;
+
+/**
+ * Init cryptographic context
+ */
+void rijndaelInit(gnutls_cipher_hd_t * ctx, unsigned char * key,
+  unsigned int keylen, unsigned char cbc_block[16],
+  unsigned int buf_size)
+{
+  int ret;
+  gnutls_cipher_algorithm_t alg;
+  gnutls_datum_t cipher_key;
+  gnutls_datum_t iv;
+
+  cipher_key.data = key;
+
+  switch(keylen) {
+    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 = gnutls_cipher_get_key_size(alg);
+
+  iv.data = cbc_block;
+  iv.size = buf_size;
+
+  ret = gnutls_cipher_init(ctx, alg, &cipher_key, &iv);
+  if (ret < 0)
+  {
+    QUtil::throw_system_error(
+      std::string("GNU TLS: AES error: ") + std::string(gnutls_strerror(ret)));
+    ctx = NULL;
+    return;
+  }
+}
+
+/**
+ * Encrypt string by AES CBC by GNU TLS.
+ */
+void rijndaelCBCEncrypt(gnutls_cipher_hd_t ctx,
+  const unsigned char plaintext[16], unsigned char ciphertext[16],
+  unsigned int buf_size)
+{
+  if (ctx != nullptr)
+    gnutls_cipher_encrypt2(ctx, plaintext, buf_size, ciphertext, buf_size);
+}
+
+/**
+ * Decrypt string by AES CBC by GNU TLS.
+ */
+void rijndaelCBCDecrypt(gnutls_cipher_hd_t ctx,
+  const unsigned char ciphertext[16], unsigned char plaintext[16],
+  unsigned int buf_size)
+{
+  if (ctx != nullptr)
+    gnutls_cipher_decrypt2(ctx, ciphertext, buf_size, plaintext, buf_size);
+}
+
+/**
+ * Encrypt string by AES ECB by GNU TLS.
+ */
+void rijndaelECBEncrypt(unsigned char * key, unsigned int keylen,
+  unsigned char cbc_block[16], const unsigned char ciphertext[16],
+  unsigned char plaintext[16], unsigned int buf_size)
+{
+  gnutls_cipher_hd_t ctx;
+
+  rijndaelInit(&ctx, key, keylen, cbc_block, buf_size);
+
+  rijndaelCBCEncrypt(ctx, ciphertext, plaintext, buf_size);
+
+  rijndaelFinish(ctx);
+}
+
+/**
+ * Decrypt string by AES ECB by GNU TLS.
+ */
+void rijndaelECBDecrypt(unsigned char * key, unsigned int keylen,
+  unsigned char cbc_block[16], const unsigned char ciphertext[16],
+  unsigned char plaintext[16], unsigned int buf_size)
+{
+  gnutls_cipher_hd_t ctx;
+
+  rijndaelInit(&ctx, key, keylen, cbc_block, buf_size);
+
+  rijndaelCBCDecrypt(ctx, ciphertext, plaintext, buf_size);
+
+  rijndaelFinish(ctx);
+}
+
+/**
+ * Finish cryptography context
+ */
+void rijndaelFinish(gnutls_cipher_hd_t ctx)
+{
+  if (ctx != nullptr)
+  {
+    gnutls_cipher_deinit(ctx);
+    ctx = nullptr;
+  }
+}
diff --git a/libqpdf/rijndael.cc b/libqpdf/rijndael.cc
index 7f711df..117c1a1 100644
--- a/libqpdf/rijndael.cc
+++ b/libqpdf/rijndael.cc
@@ -2,6 +2,10 @@
 
 #include "qpdf/rijndael.h"
 
+#ifdef HAVE_GNUTLS
+# include "rijndael-gnutls.cc"
+#else
+
 typedef uint32_t u32;
 typedef unsigned char u8;
 
@@ -1206,3 +1210,4 @@ void rijndaelDecrypt(const u32 *rk, int nrounds, const u8 ciphertext[16],
     rk[3];
   PUTU32(plaintext + 12, s3);
 }
+#endif
diff --git a/libqpdf/sha2-gnutls.c b/libqpdf/sha2-gnutls.c
new file mode 100644
index 0000000..29155d5
--- /dev/null
+++ b/libqpdf/sha2-gnutls.c
@@ -0,0 +1,41 @@
+#include <gnutls/crypto.h>
+#include <stdio.h>
+#include "sph/sph_sha2_gnutls.h"
+
+
+/* see sph_sha2.h */
+void
+sph_sha256_init(void * cc)
+{
+	int ret;
+	gnutls_hash_hd_t * ctx = (gnutls_hash_hd_t*) cc;
+
+	ret = gnutls_hash_init(ctx, GNUTLS_DIG_SHA256);
+	if (ret < 0)
+	{
+		fprintf(stderr, "GNU TLS: SHA256 error : %s\n", gnutls_strerror(ret));
+		cc = NULL;
+	}
+}
+
+/* see sph_sha2.h */
+void
+sph_sha256_close(void * cc, void *dst)
+{
+	gnutls_hash_hd_t * ctx = (gnutls_hash_hd_t*) cc;
+
+	if (ctx != NULL)
+	{
+		gnutls_hash_deinit(*(ctx), dst);
+		cc = NULL;
+	}
+}
+
+/* see sph_sha2.h */
+void sph_sha256(void * cc, const void * data, size_t len)
+{
+	gnutls_hash_hd_t * ctx = (gnutls_hash_hd_t*) cc;
+
+	if (ctx != NULL && data != NULL)
+		gnutls_hash(*(ctx), data, len);
+}
diff --git a/libqpdf/sha2.c b/libqpdf/sha2.c
index 45fdd7e..e240f9b 100644
--- a/libqpdf/sha2.c
+++ b/libqpdf/sha2.c
@@ -35,6 +35,10 @@
 
 #include "sph/sph_sha2.h"
 
+#ifdef HAVE_GNUTLS
+# include "sha2-gnutls.c"
+#else
+
 #if SPH_SMALL_FOOTPRINT && !defined SPH_SMALL_FOOTPRINT_SHA2
 #define SPH_SMALL_FOOTPRINT_SHA2   1
 #endif
@@ -688,3 +692,5 @@ sph_sha224_comp(const sph_u32 msg[16], sph_u32 val[8])
 	SHA2_ROUND_BODY(SHA2_IN, val);
 #undef SHA2_IN
 }
+
+#endif /* HAVE_GNUTLS */
diff --git a/libqpdf/sha2big-gnutls.c b/libqpdf/sha2big-gnutls.c
new file mode 100644
index 0000000..494e2b4
--- /dev/null
+++ b/libqpdf/sha2big-gnutls.c
@@ -0,0 +1,80 @@
+#include <gnutls/crypto.h>
+#include <stdio.h>
+#include "sph/sph_sha2_gnutls.h"
+
+
+/* see sph_sha3.h */
+void
+sph_sha384_init(void * cc)
+{
+	int ret;
+	gnutls_hash_hd_t * ctx = (gnutls_hash_hd_t*) cc;
+
+	ret = gnutls_hash_init(ctx, GNUTLS_DIG_SHA384);
+	if (ret < 0)
+	{
+		fprintf(stderr, "GNU TLS: SHA384 error: %s\n", gnutls_strerror(ret));
+		cc = NULL;
+	}
+}
+
+/* see sph_sha3.h */
+void
+sph_sha384_close(void * cc, void *dst)
+{
+	gnutls_hash_hd_t * ctx = (gnutls_hash_hd_t*) cc;
+
+	if (ctx != NULL && dst != NULL)
+	{
+		gnutls_hash_deinit(*(ctx), dst);
+		cc = NULL;
+	}
+}
+
+/* see sph_sha3.h */
+void
+sph_sha384(void * cc, const void * data, size_t len)
+{
+	gnutls_hash_hd_t * ctx = (gnutls_hash_hd_t*) cc;
+
+	if (ctx != NULL && data != NULL)
+		gnutls_hash(*(ctx), data, len);
+}
+
+/* see sph_sha3.h */
+void
+sph_sha512_init(void * cc)
+{
+	int ret;
+	gnutls_hash_hd_t * ctx = (gnutls_hash_hd_t*) cc;
+
+	ret = gnutls_hash_init(ctx, GNUTLS_DIG_SHA512);
+	if (ret < 0)
+	{
+		fprintf(stderr, "GNU TLS: SHA512 error: %s\n", gnutls_strerror(ret));
+		cc = NULL;
+	}
+}
+
+/* see sph_sha3.h */
+void
+sph_sha512_close(void * cc, void * dst)
+{
+	gnutls_hash_hd_t * ctx = (gnutls_hash_hd_t*) cc;
+
+	if (ctx != NULL && dst != NULL)
+	{
+		gnutls_hash_deinit(*(ctx), dst);
+		cc = NULL;
+	}
+}
+
+/* see sph_sha3.h */
+void
+sph_sha512(void * cc, const void * data, size_t len)
+{
+	gnutls_hash_hd_t * ctx = (gnutls_hash_hd_t*) cc;
+
+	if (ctx != NULL && data != NULL)
+		gnutls_hash(*(ctx), data, len);
+}
diff --git a/libqpdf/sha2big.c b/libqpdf/sha2big.c
index e4aadbd..3194a10 100644
--- a/libqpdf/sha2big.c
+++ b/libqpdf/sha2big.c
@@ -35,6 +35,10 @@
 
 #include "sph/sph_sha2.h"
 
+#ifdef HAVE_GNUTLS
+# include "sha2big-gnutls.c"
+#else
+
 #if SPH_64
 
 #define CH(X, Y, Z)    ((((Y) ^ (Z)) & (X)) ^ (Z))
@@ -245,3 +249,5 @@ sph_sha384_comp(const sph_u64 msg[16], sph_u64 val[8])
 }
 
 #endif
+
+#endif /* HAVE_GNUTLS */
diff --git a/libqpdf/sph/sph_sha2.h b/libqpdf/sph/sph_sha2.h
index 4bff9cd..584c6ab 100644
--- a/libqpdf/sph/sph_sha2.h
+++ b/libqpdf/sph/sph_sha2.h
@@ -47,6 +47,12 @@ extern "C" {
 #include <stddef.h>
 #include "sph_types.h"
 
+#include <qpdf/qpdf-config.h>
+
+#ifdef HAVE_GNUTLS
+# include "sph_sha2_gnutls.h"
+#else
+
 /**
  * Output size (in bits) for SHA-224.
  */
@@ -371,6 +377,8 @@ void sph_sha512_comp(const sph_u64 msg[16], sph_u64 val[8]);
 
 #endif
 
+#endif /* HAVE_GNUTLS */
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libqpdf/sph/sph_sha2_gnutls.h b/libqpdf/sph/sph_sha2_gnutls.h
new file mode 100644
index 0000000..1fb26a4
--- /dev/null
+++ b/libqpdf/sph/sph_sha2_gnutls.h
@@ -0,0 +1,88 @@
+#include <qpdf/qpdf-config.h>
+#include <gnutls/crypto.h>
+
+typedef gnutls_hash_hd_t sph_sha256_context;
+typedef gnutls_hash_hd_t sph_sha384_context;
+typedef gnutls_hash_hd_t sph_sha512_context;
+
+/**
+ * Initialize a SHA-256 context.
+ *
+ * @param cc   the SHA-256 context (pointer to
+ *             a <code>sph_sha256_context</code>)
+ */
+void sph_sha256_init(void *cc);
+
+/**
+ * Process some data bytes, for SHA-256.
+ *
+ * @param cc     the SHA-224 context
+ * @param data   the input data
+ * @param len    the input data length (in bytes)
+ */
+void sph_sha256(void *cc, const void *data, size_t len);
+
+/**
+ * Terminate the current SHA-256 computation and output the result into the
+ * provided buffer. The destination buffer must be wide enough to
+ * accomodate the result (32 bytes).
+ *
+ * @param cc    the SHA-256 context
+ * @param dst   the destination buffer
+ */
+void sph_sha256_close(void *cc, void *dst);
+
+/**
+ * Initialize a SHA-384 context.
+ *
+ * @param cc   the SHA-384 context (pointer to
+ *             a <code>sph_sha384_context</code>)
+ */
+void sph_sha384_init(void *cc);
+
+/**
+ * Process some data bytes. It is acceptable that <code>len</code> is zero
+ * (in which case this function does nothing).
+ *
+ * @param cc     the SHA-384 context
+ * @param data   the input data
+ * @param len    the input data length (in bytes)
+ */
+void sph_sha384(void *cc, const void *data, size_t len);
+
+/**
+ * Terminate the current SHA-384 computation and output the result into the
+ * provided buffer. The destination buffer must be wide enough to
+ * accomodate the result (48 bytes).
+ *
+ * @param cc    the SHA-384 context
+ * @param dst   the destination buffer
+ */
+void sph_sha384_close(void *cc, void *dst);
+
+/**
+ * Initialize a SHA-512 context.
+ *
+ * @param cc   the SHA-512 context (pointer to
+ *             a <code>sph_sha512_context</code>)
+ */
+void sph_sha512_init(void *cc);
+
+/**
+ * Process some data bytes, for SHA-512.
+ *
+ * @param cc     the SHA-384 context
+ * @param data   the input data
+ * @param len    the input data length (in bytes)
+ */
+void sph_sha512(void *cc, const void *data, size_t len);
+
+/**
+ * Terminate the current SHA-512 computation and output the result into the
+ * provided buffer. The destination buffer must be wide enough to
+ * accomodate the result (64 bytes).
+ *
+ * @param cc    the SHA-512 context
+ * @param dst   the destination buffer
+ */
+void sph_sha512_close(void *cc, void *dst);
diff --git a/libqpdf/sph/sph_types.h b/libqpdf/sph/sph_types.h
index eab09a2..36d1ff6 100644
--- a/libqpdf/sph/sph_types.h
+++ b/libqpdf/sph/sph_types.h
@@ -48,6 +48,7 @@
 #define SPH_TYPES_H__
 
 #include <limits.h>
+#include <qpdf/qpdf-config.h>
 
 /*
  * All our I/O functions are defined over octet streams. We do not know