Commit 5e3bad2f86665b35155095b91a2d672fc7335870

Authored by Jay Berkenbilt
1 parent e9a319fb

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.
ChangeLog
@@ -3,6 +3,12 @@ @@ -3,6 +3,12 @@
3 * Allow anyspace rather than just newline to follow xref header. 3 * Allow anyspace rather than just newline to follow xref header.
4 This allows qpdf to read a wider range of damaged files. 4 This allows qpdf to read a wider range of damaged files.
5 5
  6 +2013-11-30 Jay Berkenbilt <ejb@ql.org>
  7 +
  8 + * Allow user-supplied random data provider to be used in place of
  9 + OS-provided or insecure random number generation. See
  10 + documentation for 5.1.0 for details.
  11 +
6 2013-11-29 Jay Berkenbilt <ejb@ql.org> 12 2013-11-29 Jay Berkenbilt <ejb@ql.org>
7 13
8 * If NO_GET_ENVIRONMENT is #defined, for Windows only, 14 * If NO_GET_ENVIRONMENT is #defined, for Windows only,
include/qpdf/QUtil.hh
@@ -15,6 +15,8 @@ @@ -15,6 +15,8 @@
15 #include <stdexcept> 15 #include <stdexcept>
16 #include <stdio.h> 16 #include <stdio.h>
17 17
  18 +class RandomDataProvider;
  19 +
18 namespace QUtil 20 namespace QUtil
19 { 21 {
20 // This is a collection of useful utility functions that don't 22 // This is a collection of useful utility functions that don't
@@ -123,8 +125,26 @@ namespace QUtil @@ -123,8 +125,26 @@ namespace QUtil
123 QPDF_DLL 125 QPDF_DLL
124 void srandom(unsigned int seed); 126 void srandom(unsigned int seed);
125 127
  128 + // Initialize a buffer with random bytes. By default, qpdf tries
  129 + // to use a secure random number source. It can be configured at
  130 + // compile time to use an insecure random number source (from
  131 + // stdlib). You can also call setRandomDataProvider with a
  132 + // RandomDataProvider, in which case this method will get its
  133 + // random bytes from that.
  134 +
126 QPDF_DLL 135 QPDF_DLL
127 void initializeWithRandomBytes(unsigned char* data, size_t len); 136 void initializeWithRandomBytes(unsigned char* data, size_t len);
  137 +
  138 + // Supply a random data provider. If not supplied, depending on
  139 + // compile time options, qpdf will either use the operating
  140 + // system's secure random number source or an insecure random
  141 + // source from stdlib. The caller is responsible for managing the
  142 + // memory for the RandomDataProvider. This method modifies a
  143 + // static variable. If you are providing your own random data
  144 + // provider, you should call this at the beginning of your program
  145 + // before creating any QPDF objects.
  146 + QPDF_DLL
  147 + void setRandomDataProvider(RandomDataProvider*);
128 }; 148 };
129 149
130 #endif // __QUTIL_HH__ 150 #endif // __QUTIL_HH__
include/qpdf/RandomDataProvider.hh 0 → 100644
  1 +/* Copyright (c) 2005-2013 Jay Berkenbilt
  2 + *
  3 + * This file is part of qpdf. This software may be distributed under
  4 + * the terms of version 2 of the Artistic License which may be found
  5 + * in the source distribution. It is provided "as is" without express
  6 + * or implied warranty.
  7 + */
  8 +
  9 +#ifndef __RANDOMDATAPROVIDER_HH__
  10 +#define __RANDOMDATAPROVIDER_HH__
  11 +
  12 +#include <string.h> // for size_t
  13 +
  14 +class RandomDataProvider
  15 +{
  16 + public:
  17 + virtual ~RandomDataProvider()
  18 + {
  19 + }
  20 + virtual void provideRandomData(unsigned char* data, size_t len) = 0;
  21 +
  22 + protected:
  23 + RandomDataProvider()
  24 + {
  25 + }
  26 +
  27 + private:
  28 + RandomDataProvider(RandomDataProvider const&);
  29 + RandomDataProvider& operator=(RandomDataProvider const&);
  30 +};
  31 +
  32 +#endif // __RANDOMDATAPROVIDER_HH__
libqpdf/InsecureRandomDataProvider.cc 0 → 100644
  1 +#include <qpdf/InsecureRandomDataProvider.hh>
  2 +
  3 +#include <qpdf/qpdf-config.h>
  4 +#include <qpdf/QUtil.hh>
  5 +#include <stdlib.h>
  6 +
  7 +InsecureRandomDataProvider::InsecureRandomDataProvider() :
  8 + seeded_random(false)
  9 +{
  10 +}
  11 +
  12 +InsecureRandomDataProvider::~InsecureRandomDataProvider()
  13 +{
  14 +}
  15 +
  16 +void
  17 +InsecureRandomDataProvider::provideRandomData(unsigned char* data, size_t len)
  18 +{
  19 + for (size_t i = 0; i < len; ++i)
  20 + {
  21 + data[i] = static_cast<unsigned char>((this->random() & 0xff0) >> 4);
  22 + }
  23 +}
  24 +
  25 +long
  26 +InsecureRandomDataProvider::random()
  27 +{
  28 + if (! this->seeded_random)
  29 + {
  30 + // Seed the random number generator with something simple, but
  31 + // just to be interesting, don't use the unmodified current
  32 + // time. It would be better if this were a more secure seed.
  33 + QUtil::srandom(QUtil::get_current_time() ^ 0xcccc);
  34 + this->seeded_random = true;
  35 + }
  36 +
  37 +# ifdef HAVE_RANDOM
  38 + return ::random();
  39 +# else
  40 + return rand();
  41 +# endif
  42 +}
  43 +
  44 +RandomDataProvider*
  45 +InsecureRandomDataProvider::getInstance()
  46 +{
  47 + static InsecureRandomDataProvider instance;
  48 + return &instance;
  49 +}
libqpdf/QUtil.cc
@@ -3,6 +3,10 @@ @@ -3,6 +3,10 @@
3 3
4 #include <qpdf/QUtil.hh> 4 #include <qpdf/QUtil.hh>
5 #include <qpdf/PointerHolder.hh> 5 #include <qpdf/PointerHolder.hh>
  6 +#ifdef USE_INSECURE_RANDOM
  7 +# include <qpdf/InsecureRandomDataProvider.hh>
  8 +#endif
  9 +#include <qpdf/SecureRandomDataProvider.hh>
6 10
7 #include <cmath> 11 #include <cmath>
8 #include <iomanip> 12 #include <iomanip>
@@ -18,7 +22,6 @@ @@ -18,7 +22,6 @@
18 #include <Windows.h> 22 #include <Windows.h>
19 #include <direct.h> 23 #include <direct.h>
20 #include <io.h> 24 #include <io.h>
21 -#include <Wincrypt.h>  
22 #else 25 #else
23 #include <unistd.h> 26 #include <unistd.h>
24 #endif 27 #endif
@@ -383,38 +386,7 @@ QUtil::toUTF8(unsigned long uval) @@ -383,38 +386,7 @@ QUtil::toUTF8(unsigned long uval)
383 return result; 386 return result;
384 } 387 }
385 388
386 -#ifdef USE_INSECURE_RANDOM  
387 -  
388 -long  
389 -QUtil::random()  
390 -{  
391 - static bool seeded_random = false;  
392 - if (! seeded_random)  
393 - {  
394 - // Seed the random number generator with something simple, but  
395 - // just to be interesting, don't use the unmodified current  
396 - // time. It would be better if this were a more secure seed.  
397 - QUtil::srandom(QUtil::get_current_time() ^ 0xcccc);  
398 - seeded_random = true;  
399 - }  
400 -  
401 -# ifdef HAVE_RANDOM  
402 - return ::random();  
403 -# else  
404 - return rand();  
405 -# endif  
406 -}  
407 -  
408 -void  
409 -QUtil::initializeWithRandomBytes(unsigned char* data, size_t len)  
410 -{  
411 - for (size_t i = 0; i < len; ++i)  
412 - {  
413 - data[i] = static_cast<unsigned char>((QUtil::random() & 0xff0) >> 4);  
414 - }  
415 -}  
416 -  
417 -#else 389 +// Random data support
418 390
419 long 391 long
420 QUtil::random() 392 QUtil::random()
@@ -426,66 +398,50 @@ QUtil::random() @@ -426,66 +398,50 @@ QUtil::random()
426 return result; 398 return result;
427 } 399 }
428 400
429 -#ifdef _WIN32  
430 -class WindowsCryptProvider 401 +static RandomDataProvider* random_data_provider = 0;
  402 +
  403 +#ifdef USE_INSECURE_RANDOM
  404 +static RandomDataProvider* insecure_random_data_provider =
  405 + InsecureRandomDataProvider::getInstance();
  406 +#else
  407 +static RandomDataProvider* insecure_random_data_provider = 0;
  408 +#endif
  409 +static RandomDataProvider* secure_random_data_provider =
  410 + SecureRandomDataProvider::getInstance();
  411 +
  412 +static void
  413 +initialize_random_data_provider()
431 { 414 {
432 - public:  
433 - WindowsCryptProvider() 415 + if (random_data_provider == 0)
434 { 416 {
435 - if (! CryptAcquireContext(&crypt_prov, NULL, NULL, PROV_RSA_FULL, 0)) 417 + if (secure_random_data_provider)
  418 + {
  419 + random_data_provider = secure_random_data_provider;
  420 + }
  421 + else if (insecure_random_data_provider)
436 { 422 {
437 - throw std::runtime_error("unable to acquire crypt context"); 423 + random_data_provider = insecure_random_data_provider;
438 } 424 }
439 } 425 }
440 - ~WindowsCryptProvider() 426 + if (random_data_provider == 0)
441 { 427 {
442 - // Ignore error  
443 - CryptReleaseContext(crypt_prov, 0); 428 + throw std::logic_error("QPDF has no random data provider");
444 } 429 }
  430 +}
445 431
446 - HCRYPTPROV crypt_prov;  
447 -};  
448 -#endif 432 +void
  433 +QUtil::setRandomDataProvider(RandomDataProvider* p)
  434 +{
  435 + random_data_provider = p;
  436 +}
449 437
450 void 438 void
451 QUtil::initializeWithRandomBytes(unsigned char* data, size_t len) 439 QUtil::initializeWithRandomBytes(unsigned char* data, size_t len)
452 { 440 {
453 -#if defined(_WIN32)  
454 -  
455 - // Optimization: make the WindowsCryptProvider static as long as  
456 - // it can be done in a thread-safe fashion.  
457 - WindowsCryptProvider c;  
458 - if (! CryptGenRandom(c.crypt_prov, len, reinterpret_cast<BYTE*>(data)))  
459 - {  
460 - throw std::runtime_error("unable to generate secure random data");  
461 - }  
462 -  
463 -#elif defined(RANDOM_DEVICE)  
464 -  
465 - // Optimization: wrap the file open and close in a class so that  
466 - // the file is closed in a destructor, then make this static to  
467 - // keep the file handle open. Only do this if it can be done in a  
468 - // thread-safe fashion.  
469 - FILE* f = QUtil::safe_fopen(RANDOM_DEVICE, "rb");  
470 - size_t fr = fread(data, 1, len, f);  
471 - fclose(f);  
472 - if (fr != len)  
473 - {  
474 - throw std::runtime_error(  
475 - "unable to read " +  
476 - QUtil::int_to_string(len) +  
477 - " bytes from " + std::string(RANDOM_DEVICE));  
478 - }  
479 -  
480 -#else  
481 -  
482 -# error "Don't know how to generate secure random numbers on this platform. See random number generation in the top-level README"  
483 -  
484 -#endif 441 + initialize_random_data_provider();
  442 + random_data_provider->provideRandomData(data, len);
485 } 443 }
486 444
487 -#endif  
488 -  
489 void 445 void
490 QUtil::srandom(unsigned int seed) 446 QUtil::srandom(unsigned int seed)
491 { 447 {
libqpdf/SecureRandomDataProvider.cc 0 → 100644
  1 +#include <qpdf/SecureRandomDataProvider.hh>
  2 +
  3 +#include <qpdf/qpdf-config.h>
  4 +#include <qpdf/QUtil.hh>
  5 +#ifdef _WIN32
  6 +# include <Windows.h>
  7 +# include <direct.h>
  8 +# include <io.h>
  9 +# ifndef SKIP_OS_SECURE_RANDOM
  10 +# include <Wincrypt.h>
  11 +# endif
  12 +#endif
  13 +
  14 +SecureRandomDataProvider::SecureRandomDataProvider()
  15 +{
  16 +}
  17 +
  18 +SecureRandomDataProvider::~SecureRandomDataProvider()
  19 +{
  20 +}
  21 +
  22 +#ifdef _WIN32
  23 +
  24 +class WindowsCryptProvider
  25 +{
  26 + public:
  27 + WindowsCryptProvider()
  28 + {
  29 + if (! CryptAcquireContext(&crypt_prov, NULL, NULL, PROV_RSA_FULL, 0))
  30 + {
  31 + throw std::runtime_error("unable to acquire crypt context");
  32 + }
  33 + }
  34 + ~WindowsCryptProvider()
  35 + {
  36 + // Ignore error
  37 + CryptReleaseContext(crypt_prov, 0);
  38 + }
  39 +
  40 + HCRYPTPROV crypt_prov;
  41 +};
  42 +#endif
  43 +
  44 +void
  45 +SecureRandomDataProvider::provideRandomData(unsigned char* data, size_t len)
  46 +{
  47 +#if defined(_WIN32)
  48 +
  49 + // Optimization: make the WindowsCryptProvider static as long as
  50 + // it can be done in a thread-safe fashion.
  51 + WindowsCryptProvider c;
  52 + if (! CryptGenRandom(c.crypt_prov, len, reinterpret_cast<BYTE*>(data)))
  53 + {
  54 + throw std::runtime_error("unable to generate secure random data");
  55 + }
  56 +
  57 +#elif defined(RANDOM_DEVICE)
  58 +
  59 + // Optimization: wrap the file open and close in a class so that
  60 + // the file is closed in a destructor, then make this static to
  61 + // keep the file handle open. Only do this if it can be done in a
  62 + // thread-safe fashion.
  63 + FILE* f = QUtil::safe_fopen(RANDOM_DEVICE, "rb");
  64 + size_t fr = fread(data, 1, len, f);
  65 + fclose(f);
  66 + if (fr != len)
  67 + {
  68 + throw std::runtime_error(
  69 + "unable to read " +
  70 + QUtil::int_to_string(len) +
  71 + " bytes from " + std::string(RANDOM_DEVICE));
  72 + }
  73 +
  74 +#else
  75 +
  76 +# error "Don't know how to generate secure random numbers on this platform. See random number generation in the top-level README"
  77 +
  78 +#endif
  79 +}
  80 +
  81 +RandomDataProvider*
  82 +SecureRandomDataProvider::getInstance()
  83 +{
  84 + static SecureRandomDataProvider instance;
  85 + return &instance;
  86 +}
libqpdf/build.mk
@@ -11,6 +11,7 @@ SRCS_libqpdf = \ @@ -11,6 +11,7 @@ SRCS_libqpdf = \
11 libqpdf/BufferInputSource.cc \ 11 libqpdf/BufferInputSource.cc \
12 libqpdf/FileInputSource.cc \ 12 libqpdf/FileInputSource.cc \
13 libqpdf/InputSource.cc \ 13 libqpdf/InputSource.cc \
  14 + libqpdf/InsecureRandomDataProvider.cc \
14 libqpdf/MD5.cc \ 15 libqpdf/MD5.cc \
15 libqpdf/OffsetInputSource.cc \ 16 libqpdf/OffsetInputSource.cc \
16 libqpdf/PCRE.cc \ 17 libqpdf/PCRE.cc \
@@ -57,6 +58,7 @@ SRCS_libqpdf = \ @@ -57,6 +58,7 @@ SRCS_libqpdf = \
57 libqpdf/QTC.cc \ 58 libqpdf/QTC.cc \
58 libqpdf/QUtil.cc \ 59 libqpdf/QUtil.cc \
59 libqpdf/RC4.cc \ 60 libqpdf/RC4.cc \
  61 + libqpdf/SecureRandomDataProvider.cc \
60 libqpdf/qpdf-c.cc \ 62 libqpdf/qpdf-c.cc \
61 libqpdf/rijndael.cc \ 63 libqpdf/rijndael.cc \
62 libqpdf/sha2.c \ 64 libqpdf/sha2.c \
libqpdf/qpdf/InsecureRandomDataProvider.hh 0 → 100644
  1 +#ifndef __INSECURERANDOMDATAPROVIDER_HH__
  2 +#define __INSECURERANDOMDATAPROVIDER_HH__
  3 +
  4 +#include <qpdf/RandomDataProvider.hh>
  5 +#include <qpdf/DLL.h>
  6 +
  7 +class InsecureRandomDataProvider: public RandomDataProvider
  8 +{
  9 + public:
  10 + QPDF_DLL
  11 + InsecureRandomDataProvider();
  12 + QPDF_DLL
  13 + virtual ~InsecureRandomDataProvider();
  14 +
  15 + QPDF_DLL
  16 + virtual void provideRandomData(unsigned char* data, size_t len);
  17 +
  18 + QPDF_DLL
  19 + static RandomDataProvider* getInstance();
  20 +
  21 + private:
  22 + long random();
  23 +
  24 + bool seeded_random;
  25 +};
  26 +
  27 +#endif // __INSECURERANDOMDATAPROVIDER_HH__
libqpdf/qpdf/SecureRandomDataProvider.hh 0 → 100644
  1 +#ifndef __SECURERANDOMDATAPROVIDER_HH__
  2 +#define __SECURERANDOMDATAPROVIDER_HH__
  3 +
  4 +#include <qpdf/RandomDataProvider.hh>
  5 +#include <qpdf/DLL.h>
  6 +
  7 +class SecureRandomDataProvider: public RandomDataProvider
  8 +{
  9 + public:
  10 + QPDF_DLL
  11 + SecureRandomDataProvider();
  12 + QPDF_DLL
  13 + virtual ~SecureRandomDataProvider();
  14 +
  15 + QPDF_DLL
  16 + virtual void provideRandomData(unsigned char* data, size_t len);
  17 +
  18 + QPDF_DLL
  19 + static RandomDataProvider* getInstance();
  20 +};
  21 +
  22 +#endif // __SECURERANDOMDATAPROVIDER_HH__
libtests/build.mk
@@ -12,6 +12,7 @@ BINS_libtests = \ @@ -12,6 +12,7 @@ BINS_libtests = \
12 png_filter \ 12 png_filter \
13 pointer_holder \ 13 pointer_holder \
14 qutil \ 14 qutil \
  15 + random \
15 rc4 \ 16 rc4 \
16 sha2 17 sha2
17 18
libtests/qtest/random.test 0 → 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +BEGIN { $^W = 1; }
  4 +use strict;
  5 +
  6 +require TestDriver;
  7 +
  8 +my $td = new TestDriver('random');
  9 +
  10 +$td->runtest("Random Data Providers",
  11 + {$td->COMMAND => "random"},
  12 + {$td->STRING => "random: end of tests\n",
  13 + $td->EXIT_STATUS => 0},
  14 + $td->NORMALIZE_NEWLINES);
  15 +
  16 +$td->report(1);
libtests/random.cc 0 → 100644
  1 +#include <qpdf/QUtil.hh>
  2 +#include <qpdf/InsecureRandomDataProvider.hh>
  3 +#include <qpdf/SecureRandomDataProvider.hh>
  4 +#include <iostream>
  5 +
  6 +class BogusRandomDataProvider: public RandomDataProvider
  7 +{
  8 + public:
  9 + virtual ~BogusRandomDataProvider()
  10 + {
  11 + }
  12 + BogusRandomDataProvider()
  13 + {
  14 + }
  15 + virtual void provideRandomData(unsigned char* data, size_t len)
  16 + {
  17 + for (size_t i = 0; i < len; ++i)
  18 + {
  19 + data[i] = static_cast<unsigned char>(i & 0xff);
  20 + }
  21 + }
  22 +};
  23 +
  24 +int main()
  25 +{
  26 + long r1 = QUtil::random();
  27 + long r2 = QUtil::random();
  28 + if (r1 == r2)
  29 + {
  30 + std::cout << "fail: two randoms were the same\n";
  31 + }
  32 + InsecureRandomDataProvider irdp;
  33 + irdp.provideRandomData(reinterpret_cast<unsigned char*>(&r1), 4);
  34 + irdp.provideRandomData(reinterpret_cast<unsigned char*>(&r2), 4);
  35 + if (r1 == r2)
  36 + {
  37 + std::cout << "fail: two insecure randoms were the same\n";
  38 + }
  39 + SecureRandomDataProvider srdp;
  40 + srdp.provideRandomData(reinterpret_cast<unsigned char*>(&r1), 4);
  41 + srdp.provideRandomData(reinterpret_cast<unsigned char*>(&r2), 4);
  42 + if (r1 == r2)
  43 + {
  44 + std::cout << "fail: two secure randoms were the same\n";
  45 + }
  46 + BogusRandomDataProvider brdp;
  47 + QUtil::setRandomDataProvider(&brdp);
  48 + r1 = QUtil::random();
  49 + r2 = QUtil::random();
  50 + if (r1 != r2)
  51 + {
  52 + std::cout << "fail: two bogus randoms were different\n";
  53 + }
  54 + unsigned char buf[4];
  55 + QUtil::initializeWithRandomBytes(buf, 4);
  56 + if (! ((buf[0] == 0) &&
  57 + (buf[1] == 1) &&
  58 + (buf[2] == 2) &&
  59 + (buf[3] == 3)))
  60 + {
  61 + std::cout << "fail: bogus random didn't provide correct bytes\n";
  62 + }
  63 + std::cout << "random: end of tests\n";
  64 + return 0;
  65 +}