summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2013-11-30 18:02:56 +0100
committerJay Berkenbilt <ejb@ql.org>2013-12-14 21:17:35 +0100
commit5e3bad2f86665b35155095b91a2d672fc7335870 (patch)
tree984d4830ee0dd3b8e90bae913f411a84c8e876d4
parente9a319fb9536347aeab076cdb18e1ff97eb66c07 (diff)
downloadqpdf-5e3bad2f86665b35155095b91a2d672fc7335870.tar.zst
Refactor random data generation
Add new RandomDataProvider object and implement existing random number generation in terms of that. This enables end users to supply their own random data providers.
-rw-r--r--ChangeLog6
-rw-r--r--include/qpdf/QUtil.hh20
-rw-r--r--include/qpdf/RandomDataProvider.hh32
-rw-r--r--libqpdf/InsecureRandomDataProvider.cc49
-rw-r--r--libqpdf/QUtil.cc114
-rw-r--r--libqpdf/SecureRandomDataProvider.cc86
-rw-r--r--libqpdf/build.mk2
-rw-r--r--libqpdf/qpdf/InsecureRandomDataProvider.hh27
-rw-r--r--libqpdf/qpdf/SecureRandomDataProvider.hh22
-rw-r--r--libtests/build.mk1
-rw-r--r--libtests/qtest/random.test16
-rw-r--r--libtests/random.cc65
12 files changed, 361 insertions, 79 deletions
diff --git a/ChangeLog b/ChangeLog
index 12cb789b..84b8297c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,6 +3,12 @@
* Allow anyspace rather than just newline to follow xref header.
This allows qpdf to read a wider range of damaged files.
+2013-11-30 Jay Berkenbilt <ejb@ql.org>
+
+ * Allow user-supplied random data provider to be used in place of
+ OS-provided or insecure random number generation. See
+ documentation for 5.1.0 for details.
+
2013-11-29 Jay Berkenbilt <ejb@ql.org>
* If NO_GET_ENVIRONMENT is #defined, for Windows only,
diff --git a/include/qpdf/QUtil.hh b/include/qpdf/QUtil.hh
index cbdc065c..f61fa844 100644
--- a/include/qpdf/QUtil.hh
+++ b/include/qpdf/QUtil.hh
@@ -15,6 +15,8 @@
#include <stdexcept>
#include <stdio.h>
+class RandomDataProvider;
+
namespace QUtil
{
// This is a collection of useful utility functions that don't
@@ -123,8 +125,26 @@ namespace QUtil
QPDF_DLL
void srandom(unsigned int seed);
+ // Initialize a buffer with random bytes. By default, qpdf tries
+ // to use a secure random number source. It can be configured at
+ // compile time to use an insecure random number source (from
+ // stdlib). You can also call setRandomDataProvider with a
+ // RandomDataProvider, in which case this method will get its
+ // random bytes from that.
+
QPDF_DLL
void initializeWithRandomBytes(unsigned char* data, size_t len);
+
+ // Supply a random data provider. If not supplied, depending on
+ // compile time options, qpdf will either use the operating
+ // system's secure random number source or an insecure random
+ // source from stdlib. The caller is responsible for managing the
+ // memory for the RandomDataProvider. This method modifies a
+ // static variable. If you are providing your own random data
+ // provider, you should call this at the beginning of your program
+ // before creating any QPDF objects.
+ QPDF_DLL
+ void setRandomDataProvider(RandomDataProvider*);
};
#endif // __QUTIL_HH__
diff --git a/include/qpdf/RandomDataProvider.hh b/include/qpdf/RandomDataProvider.hh
new file mode 100644
index 00000000..68c9cb6e
--- /dev/null
+++ b/include/qpdf/RandomDataProvider.hh
@@ -0,0 +1,32 @@
+/* Copyright (c) 2005-2013 Jay Berkenbilt
+ *
+ * This file is part of qpdf. This software may be distributed under
+ * the terms of version 2 of the Artistic License which may be found
+ * in the source distribution. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#ifndef __RANDOMDATAPROVIDER_HH__
+#define __RANDOMDATAPROVIDER_HH__
+
+#include <string.h> // for size_t
+
+class RandomDataProvider
+{
+ public:
+ virtual ~RandomDataProvider()
+ {
+ }
+ virtual void provideRandomData(unsigned char* data, size_t len) = 0;
+
+ protected:
+ RandomDataProvider()
+ {
+ }
+
+ private:
+ RandomDataProvider(RandomDataProvider const&);
+ RandomDataProvider& operator=(RandomDataProvider const&);
+};
+
+#endif // __RANDOMDATAPROVIDER_HH__
diff --git a/libqpdf/InsecureRandomDataProvider.cc b/libqpdf/InsecureRandomDataProvider.cc
new file mode 100644
index 00000000..5f637746
--- /dev/null
+++ b/libqpdf/InsecureRandomDataProvider.cc
@@ -0,0 +1,49 @@
+#include <qpdf/InsecureRandomDataProvider.hh>
+
+#include <qpdf/qpdf-config.h>
+#include <qpdf/QUtil.hh>
+#include <stdlib.h>
+
+InsecureRandomDataProvider::InsecureRandomDataProvider() :
+ seeded_random(false)
+{
+}
+
+InsecureRandomDataProvider::~InsecureRandomDataProvider()
+{
+}
+
+void
+InsecureRandomDataProvider::provideRandomData(unsigned char* data, size_t len)
+{
+ for (size_t i = 0; i < len; ++i)
+ {
+ data[i] = static_cast<unsigned char>((this->random() & 0xff0) >> 4);
+ }
+}
+
+long
+InsecureRandomDataProvider::random()
+{
+ if (! this->seeded_random)
+ {
+ // Seed the random number generator with something simple, but
+ // just to be interesting, don't use the unmodified current
+ // time. It would be better if this were a more secure seed.
+ QUtil::srandom(QUtil::get_current_time() ^ 0xcccc);
+ this->seeded_random = true;
+ }
+
+# ifdef HAVE_RANDOM
+ return ::random();
+# else
+ return rand();
+# endif
+}
+
+RandomDataProvider*
+InsecureRandomDataProvider::getInstance()
+{
+ static InsecureRandomDataProvider instance;
+ return &instance;
+}
diff --git a/libqpdf/QUtil.cc b/libqpdf/QUtil.cc
index 01e0b6e7..c5fe535c 100644
--- a/libqpdf/QUtil.cc
+++ b/libqpdf/QUtil.cc
@@ -3,6 +3,10 @@
#include <qpdf/QUtil.hh>
#include <qpdf/PointerHolder.hh>
+#ifdef USE_INSECURE_RANDOM
+# include <qpdf/InsecureRandomDataProvider.hh>
+#endif
+#include <qpdf/SecureRandomDataProvider.hh>
#include <cmath>
#include <iomanip>
@@ -18,7 +22,6 @@
#include <Windows.h>
#include <direct.h>
#include <io.h>
-#include <Wincrypt.h>
#else
#include <unistd.h>
#endif
@@ -383,38 +386,7 @@ QUtil::toUTF8(unsigned long uval)
return result;
}
-#ifdef USE_INSECURE_RANDOM
-
-long
-QUtil::random()
-{
- static bool seeded_random = false;
- if (! seeded_random)
- {
- // Seed the random number generator with something simple, but
- // just to be interesting, don't use the unmodified current
- // time. It would be better if this were a more secure seed.
- QUtil::srandom(QUtil::get_current_time() ^ 0xcccc);
- seeded_random = true;
- }
-
-# ifdef HAVE_RANDOM
- return ::random();
-# else
- return rand();
-# endif
-}
-
-void
-QUtil::initializeWithRandomBytes(unsigned char* data, size_t len)
-{
- for (size_t i = 0; i < len; ++i)
- {
- data[i] = static_cast<unsigned char>((QUtil::random() & 0xff0) >> 4);
- }
-}
-
-#else
+// Random data support
long
QUtil::random()
@@ -426,66 +398,50 @@ QUtil::random()
return result;
}
-#ifdef _WIN32
-class WindowsCryptProvider
+static RandomDataProvider* random_data_provider = 0;
+
+#ifdef USE_INSECURE_RANDOM
+static RandomDataProvider* insecure_random_data_provider =
+ InsecureRandomDataProvider::getInstance();
+#else
+static RandomDataProvider* insecure_random_data_provider = 0;
+#endif
+static RandomDataProvider* secure_random_data_provider =
+ SecureRandomDataProvider::getInstance();
+
+static void
+initialize_random_data_provider()
{
- public:
- WindowsCryptProvider()
+ if (random_data_provider == 0)
{
- if (! CryptAcquireContext(&crypt_prov, NULL, NULL, PROV_RSA_FULL, 0))
+ if (secure_random_data_provider)
+ {
+ random_data_provider = secure_random_data_provider;
+ }
+ else if (insecure_random_data_provider)
{
- throw std::runtime_error("unable to acquire crypt context");
+ random_data_provider = insecure_random_data_provider;
}
}
- ~WindowsCryptProvider()
+ if (random_data_provider == 0)
{
- // Ignore error
- CryptReleaseContext(crypt_prov, 0);
+ throw std::logic_error("QPDF has no random data provider");
}
+}
- HCRYPTPROV crypt_prov;
-};
-#endif
+void
+QUtil::setRandomDataProvider(RandomDataProvider* p)
+{
+ random_data_provider = p;
+}
void
QUtil::initializeWithRandomBytes(unsigned char* data, size_t len)
{
-#if defined(_WIN32)
-
- // Optimization: make the WindowsCryptProvider static as long as
- // it can be done in a thread-safe fashion.
- WindowsCryptProvider c;
- if (! CryptGenRandom(c.crypt_prov, len, reinterpret_cast<BYTE*>(data)))
- {
- throw std::runtime_error("unable to generate secure random data");
- }
-
-#elif defined(RANDOM_DEVICE)
-
- // Optimization: wrap the file open and close in a class so that
- // the file is closed in a destructor, then make this static to
- // keep the file handle open. Only do this if it can be done in a
- // thread-safe fashion.
- FILE* f = QUtil::safe_fopen(RANDOM_DEVICE, "rb");
- size_t fr = fread(data, 1, len, f);
- fclose(f);
- if (fr != len)
- {
- throw std::runtime_error(
- "unable to read " +
- QUtil::int_to_string(len) +
- " bytes from " + std::string(RANDOM_DEVICE));
- }
-
-#else
-
-# error "Don't know how to generate secure random numbers on this platform. See random number generation in the top-level README"
-
-#endif
+ initialize_random_data_provider();
+ random_data_provider->provideRandomData(data, len);
}
-#endif
-
void
QUtil::srandom(unsigned int seed)
{
diff --git a/libqpdf/SecureRandomDataProvider.cc b/libqpdf/SecureRandomDataProvider.cc
new file mode 100644
index 00000000..14ef55a7
--- /dev/null
+++ b/libqpdf/SecureRandomDataProvider.cc
@@ -0,0 +1,86 @@
+#include <qpdf/SecureRandomDataProvider.hh>
+
+#include <qpdf/qpdf-config.h>
+#include <qpdf/QUtil.hh>
+#ifdef _WIN32
+# include <Windows.h>
+# include <direct.h>
+# include <io.h>
+# ifndef SKIP_OS_SECURE_RANDOM
+# include <Wincrypt.h>
+# endif
+#endif
+
+SecureRandomDataProvider::SecureRandomDataProvider()
+{
+}
+
+SecureRandomDataProvider::~SecureRandomDataProvider()
+{
+}
+
+#ifdef _WIN32
+
+class WindowsCryptProvider
+{
+ public:
+ WindowsCryptProvider()
+ {
+ if (! CryptAcquireContext(&crypt_prov, NULL, NULL, PROV_RSA_FULL, 0))
+ {
+ throw std::runtime_error("unable to acquire crypt context");
+ }
+ }
+ ~WindowsCryptProvider()
+ {
+ // Ignore error
+ CryptReleaseContext(crypt_prov, 0);
+ }
+
+ HCRYPTPROV crypt_prov;
+};
+#endif
+
+void
+SecureRandomDataProvider::provideRandomData(unsigned char* data, size_t len)
+{
+#if defined(_WIN32)
+
+ // Optimization: make the WindowsCryptProvider static as long as
+ // it can be done in a thread-safe fashion.
+ WindowsCryptProvider c;
+ if (! CryptGenRandom(c.crypt_prov, len, reinterpret_cast<BYTE*>(data)))
+ {
+ throw std::runtime_error("unable to generate secure random data");
+ }
+
+#elif defined(RANDOM_DEVICE)
+
+ // Optimization: wrap the file open and close in a class so that
+ // the file is closed in a destructor, then make this static to
+ // keep the file handle open. Only do this if it can be done in a
+ // thread-safe fashion.
+ FILE* f = QUtil::safe_fopen(RANDOM_DEVICE, "rb");
+ size_t fr = fread(data, 1, len, f);
+ fclose(f);
+ if (fr != len)
+ {
+ throw std::runtime_error(
+ "unable to read " +
+ QUtil::int_to_string(len) +
+ " bytes from " + std::string(RANDOM_DEVICE));
+ }
+
+#else
+
+# error "Don't know how to generate secure random numbers on this platform. See random number generation in the top-level README"
+
+#endif
+}
+
+RandomDataProvider*
+SecureRandomDataProvider::getInstance()
+{
+ static SecureRandomDataProvider instance;
+ return &instance;
+}
diff --git a/libqpdf/build.mk b/libqpdf/build.mk
index 20781230..4287b0de 100644
--- a/libqpdf/build.mk
+++ b/libqpdf/build.mk
@@ -11,6 +11,7 @@ SRCS_libqpdf = \
libqpdf/BufferInputSource.cc \
libqpdf/FileInputSource.cc \
libqpdf/InputSource.cc \
+ libqpdf/InsecureRandomDataProvider.cc \
libqpdf/MD5.cc \
libqpdf/OffsetInputSource.cc \
libqpdf/PCRE.cc \
@@ -57,6 +58,7 @@ SRCS_libqpdf = \
libqpdf/QTC.cc \
libqpdf/QUtil.cc \
libqpdf/RC4.cc \
+ libqpdf/SecureRandomDataProvider.cc \
libqpdf/qpdf-c.cc \
libqpdf/rijndael.cc \
libqpdf/sha2.c \
diff --git a/libqpdf/qpdf/InsecureRandomDataProvider.hh b/libqpdf/qpdf/InsecureRandomDataProvider.hh
new file mode 100644
index 00000000..530ee3cb
--- /dev/null
+++ b/libqpdf/qpdf/InsecureRandomDataProvider.hh
@@ -0,0 +1,27 @@
+#ifndef __INSECURERANDOMDATAPROVIDER_HH__
+#define __INSECURERANDOMDATAPROVIDER_HH__
+
+#include <qpdf/RandomDataProvider.hh>
+#include <qpdf/DLL.h>
+
+class InsecureRandomDataProvider: public RandomDataProvider
+{
+ public:
+ QPDF_DLL
+ InsecureRandomDataProvider();
+ QPDF_DLL
+ virtual ~InsecureRandomDataProvider();
+
+ QPDF_DLL
+ virtual void provideRandomData(unsigned char* data, size_t len);
+
+ QPDF_DLL
+ static RandomDataProvider* getInstance();
+
+ private:
+ long random();
+
+ bool seeded_random;
+};
+
+#endif // __INSECURERANDOMDATAPROVIDER_HH__
diff --git a/libqpdf/qpdf/SecureRandomDataProvider.hh b/libqpdf/qpdf/SecureRandomDataProvider.hh
new file mode 100644
index 00000000..178c73c0
--- /dev/null
+++ b/libqpdf/qpdf/SecureRandomDataProvider.hh
@@ -0,0 +1,22 @@
+#ifndef __SECURERANDOMDATAPROVIDER_HH__
+#define __SECURERANDOMDATAPROVIDER_HH__
+
+#include <qpdf/RandomDataProvider.hh>
+#include <qpdf/DLL.h>
+
+class SecureRandomDataProvider: public RandomDataProvider
+{
+ public:
+ QPDF_DLL
+ SecureRandomDataProvider();
+ QPDF_DLL
+ virtual ~SecureRandomDataProvider();
+
+ QPDF_DLL
+ virtual void provideRandomData(unsigned char* data, size_t len);
+
+ QPDF_DLL
+ static RandomDataProvider* getInstance();
+};
+
+#endif // __SECURERANDOMDATAPROVIDER_HH__
diff --git a/libtests/build.mk b/libtests/build.mk
index 7a535953..22d9299e 100644
--- a/libtests/build.mk
+++ b/libtests/build.mk
@@ -12,6 +12,7 @@ BINS_libtests = \
png_filter \
pointer_holder \
qutil \
+ random \
rc4 \
sha2
diff --git a/libtests/qtest/random.test b/libtests/qtest/random.test
new file mode 100644
index 00000000..c3b4b9d7
--- /dev/null
+++ b/libtests/qtest/random.test
@@ -0,0 +1,16 @@
+#!/usr/bin/env perl
+require 5.008;
+BEGIN { $^W = 1; }
+use strict;
+
+require TestDriver;
+
+my $td = new TestDriver('random');
+
+$td->runtest("Random Data Providers",
+ {$td->COMMAND => "random"},
+ {$td->STRING => "random: end of tests\n",
+ $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+
+$td->report(1);
diff --git a/libtests/random.cc b/libtests/random.cc
new file mode 100644
index 00000000..644fdd91
--- /dev/null
+++ b/libtests/random.cc
@@ -0,0 +1,65 @@
+#include <qpdf/QUtil.hh>
+#include <qpdf/InsecureRandomDataProvider.hh>
+#include <qpdf/SecureRandomDataProvider.hh>
+#include <iostream>
+
+class BogusRandomDataProvider: public RandomDataProvider
+{
+ public:
+ virtual ~BogusRandomDataProvider()
+ {
+ }
+ BogusRandomDataProvider()
+ {
+ }
+ virtual void provideRandomData(unsigned char* data, size_t len)
+ {
+ for (size_t i = 0; i < len; ++i)
+ {
+ data[i] = static_cast<unsigned char>(i & 0xff);
+ }
+ }
+};
+
+int main()
+{
+ long r1 = QUtil::random();
+ long r2 = QUtil::random();
+ if (r1 == r2)
+ {
+ std::cout << "fail: two randoms were the same\n";
+ }
+ InsecureRandomDataProvider irdp;
+ irdp.provideRandomData(reinterpret_cast<unsigned char*>(&r1), 4);
+ irdp.provideRandomData(reinterpret_cast<unsigned char*>(&r2), 4);
+ if (r1 == r2)
+ {
+ std::cout << "fail: two insecure randoms were the same\n";
+ }
+ SecureRandomDataProvider srdp;
+ srdp.provideRandomData(reinterpret_cast<unsigned char*>(&r1), 4);
+ srdp.provideRandomData(reinterpret_cast<unsigned char*>(&r2), 4);
+ if (r1 == r2)
+ {
+ std::cout << "fail: two secure randoms were the same\n";
+ }
+ BogusRandomDataProvider brdp;
+ QUtil::setRandomDataProvider(&brdp);
+ r1 = QUtil::random();
+ r2 = QUtil::random();
+ if (r1 != r2)
+ {
+ std::cout << "fail: two bogus randoms were different\n";
+ }
+ unsigned char buf[4];
+ QUtil::initializeWithRandomBytes(buf, 4);
+ if (! ((buf[0] == 0) &&
+ (buf[1] == 1) &&
+ (buf[2] == 2) &&
+ (buf[3] == 3)))
+ {
+ std::cout << "fail: bogus random didn't provide correct bytes\n";
+ }
+ std::cout << "random: end of tests\n";
+ return 0;
+}