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 + + * 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 * 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 #include +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 // 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 + +#include +#include +#include + +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((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 #include +#ifdef USE_INSECURE_RANDOM +# include +#endif +#include #include #include @@ -18,7 +22,6 @@ #include #include #include -#include #else #include #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((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) { - throw std::runtime_error("unable to acquire crypt context"); + random_data_provider = secure_random_data_provider; + } + else if (insecure_random_data_provider) + { + 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(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 + +#include +#include +#ifdef _WIN32 +# include +# include +# include +# ifndef SKIP_OS_SECURE_RANDOM +# include +# 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(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 +#include + +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 +#include + +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 +#include +#include +#include + +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(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(&r1), 4); + irdp.provideRandomData(reinterpret_cast(&r2), 4); + if (r1 == r2) + { + std::cout << "fail: two insecure randoms were the same\n"; + } + SecureRandomDataProvider srdp; + srdp.provideRandomData(reinterpret_cast(&r1), 4); + srdp.provideRandomData(reinterpret_cast(&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; +}