Commit 77198d5310d961ba3605db74fe1d213bb5d19f34

Authored by Jay Berkenbilt
1 parent 52749b85

Delegate random number generation to crypto provider (fixes #418)

ChangeLog
1 1 2020-04-06 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Move random number generation into the crypto providers. The old
  4 + os-based secure random number generation with fallback to insecure
  5 + random number generation (only if allowed at build time) has moved
  6 + into the native crypto provider. If using other providers
  7 + (currently gnutls or openssl), random number generation will use
  8 + those libraries. The old interfaces for supplying your own random
  9 + number generator are still in place. Fixes #418.
  10 +
3 11 * Source-level incompatibility: remove QUtil::srandom. There was
4 12 no reason to ever call this, and it didn't do anything unless
5 13 insecure random number generation was compiled in, which it is not
... ...
README.md
... ... @@ -111,7 +111,9 @@ If you are packaging qpdf for a distribution and preparing a build that is run b
111 111  
112 112 # Random Number Generation
113 113  
114   -By default, when `qpdf` detects either the Windows cryptography API or the existence of `/dev/urandom`, `/dev/arandom`, or `/dev/random`, it uses them to generate cryptography secure random numbers. If none of these conditions are true, the build will fail with an error. This behavior can be modified in several ways:
  114 +By default, qpdf uses the crypto provider for generating random numbers. The rest of this applies only if you are using the native crypto provider.
  115 +
  116 +If the native crypto provider is in use, then, when `qpdf` detects either the Windows cryptography API or the existence of `/dev/urandom`, `/dev/arandom`, or `/dev/random`, it uses them to generate cryptographically secure random numbers. If none of these conditions are true, the build will fail with an error. This behavior can be modified in several ways:
115 117 * If you configure with `--disable-os-secure-random` or define `SKIP_OS_SECURE_RANDOM`, qpdf will not attempt to use Windows cryptography or the random device. You must either supply your own random data provider or allow use of insecure random numbers.
116 118 * If you configure qpdf with the `--enable-insecure-random` option or define `USE_INSECURE_RANDOM`, qpdf will try insecure random numbers if OS-provided secure random numbers are disabled. This is not a fallback. In order for insecure random numbers to be used, you must also disable OS secure random numbers since, otherwise, failure to find OS secure random numbers is a compile error. The insecure random number source is stdlib's `random()` or `rand()` calls. These random numbers are not cryptography secure, but the qpdf library is fully functional using them. Using non-secure random numbers means that it's easier in some cases to guess encryption keys. If you're not generating encrypted files, there's no advantage to using secure random numbers.
117 119 * In all cases, you may supply your own random data provider. To do this, derive a class from `qpdf/RandomDataProvider` (since version 5.1.0) and call `QUtil::setRandomDataProvider` before you create any `QPDF` objects. If you supply your own random data provider, it will always be used even if support for one of the other random data providers is compiled in. If you wish to avoid any possibility of your build of qpdf from using anything but a user-supplied random data provider, you can define `SKIP_OS_SECURE_RANDOM` and not `USE_INSECURE_RANDOM`. In this case, qpdf will throw a runtime error if any attempt is made to generate random numbers and no random data provider has been supplied.
... ...
include/qpdf/QPDFCryptoImpl.hh
... ... @@ -41,6 +41,11 @@ class QPDF_DLL_CLASS QPDFCryptoImpl
41 41 QPDF_DLL
42 42 virtual ~QPDFCryptoImpl() = default;
43 43  
  44 + // Random Number Generation
  45 +
  46 + QPDF_DLL
  47 + virtual void provideRandomData(unsigned char* data, size_t len) = 0;
  48 +
44 49 // Hashing
45 50  
46 51 typedef unsigned char MD5_Digest[16];
... ...
include/qpdf/QUtil.hh
... ... @@ -263,35 +263,25 @@ namespace QUtil
263 263 QPDF_DLL
264 264 std::vector<std::string> possible_repaired_encodings(std::string);
265 265  
266   - // If secure random number generation is supported on your
267   - // platform and qpdf was not compiled with insecure random number
268   - // generation, this returns a cryptographically secure random
269   - // number. Otherwise it falls back to random from stdlib and
270   - // calls srandom automatically the first time it is called.
  266 + // Return a cryptographically secure random number.
271 267 QPDF_DLL
272 268 long random();
273 269  
274   - // Initialize a buffer with random bytes. By default, qpdf tries
275   - // to use a secure random number source. It can be configured at
276   - // compile time to use an insecure random number source (from
277   - // stdlib). You can also call setRandomDataProvider with a
278   - // RandomDataProvider, in which case this method will get its
279   - // random bytes from that.
280   -
  270 + // Initialize a buffer with cryptographically secure random bytes.
281 271 QPDF_DLL
282 272 void initializeWithRandomBytes(unsigned char* data, size_t len);
283 273  
284   - // Supply a random data provider. If not supplied, depending on
285   - // compile time options, qpdf will either use the operating
286   - // system's secure random number source or an insecure random
287   - // source from stdlib. The caller is responsible for managing the
288   - // memory for the RandomDataProvider. This method modifies a
289   - // static variable. If you are providing your own random data
290   - // provider, you should call this at the beginning of your program
291   - // before creating any QPDF objects. Passing a null to this
292   - // method will reset the library back to whichever of the built-in
293   - // random data handlers is appropriate based on how qpdf was
294   - // compiled.
  274 + // Supply a random data provider. Starting in qpdf 10.0.0, qpdf
  275 + // uses the crypto provider as its source of random numbers. If
  276 + // you are using the native crypto provider, then qpdf will either
  277 + // use the operating system's secure random number source or, only
  278 + // if enabled at build time, an insecure random source from
  279 + // stdlib. The caller is responsible for managing the memory for
  280 + // the RandomDataProvider. This method modifies a static variable.
  281 + // If you are providing your own random data provider, you should
  282 + // call this at the beginning of your program before creating any
  283 + // QPDF objects. Passing a null to this method will reset the
  284 + // library back to its default random data provider.
295 285 QPDF_DLL
296 286 void setRandomDataProvider(RandomDataProvider*);
297 287  
... ...
libqpdf/CryptoRandomDataProvider.cc 0 โ†’ 100644
  1 +#include <qpdf/CryptoRandomDataProvider.hh>
  2 +#include <qpdf/QPDFCryptoProvider.hh>
  3 +
  4 +CryptoRandomDataProvider::CryptoRandomDataProvider()
  5 +{
  6 +}
  7 +
  8 +CryptoRandomDataProvider::~CryptoRandomDataProvider()
  9 +{
  10 +}
  11 +
  12 +void
  13 +CryptoRandomDataProvider::provideRandomData(unsigned char* data, size_t len)
  14 +{
  15 + auto crypto = QPDFCryptoProvider::getImpl();
  16 + crypto->provideRandomData(data, len);
  17 +}
  18 +
  19 +RandomDataProvider*
  20 +CryptoRandomDataProvider::getInstance()
  21 +{
  22 + static CryptoRandomDataProvider instance;
  23 + return &instance;
  24 +}
... ...
libqpdf/QPDFCrypto_gnutls.cc
... ... @@ -30,6 +30,18 @@ QPDFCrypto_gnutls::~QPDFCrypto_gnutls()
30 30 }
31 31  
32 32 void
  33 +QPDFCrypto_gnutls::provideRandomData(unsigned char* data, size_t len)
  34 +{
  35 + int code = gnutls_rnd (GNUTLS_RND_KEY, data, len);
  36 + if (code < 0)
  37 + {
  38 + throw std::runtime_error(
  39 + std::string("gnutls: random number generation error: ") +
  40 + std::string(gnutls_strerror(code)));
  41 + }
  42 +}
  43 +
  44 +void
33 45 QPDFCrypto_gnutls::MD5_init()
34 46 {
35 47 MD5_finalize();
... ...
libqpdf/QPDFCrypto_native.cc
1 1 #include <qpdf/QPDFCrypto_native.hh>
2 2 #include <qpdf/QUtil.hh>
3 3  
  4 +#ifdef USE_INSECURE_RANDOM
  5 +# include <qpdf/InsecureRandomDataProvider.hh>
  6 +#endif
  7 +#include <qpdf/SecureRandomDataProvider.hh>
  8 +
  9 +static RandomDataProvider* getRandomProvider()
  10 +{
  11 +#ifdef USE_INSECURE_RANDOM
  12 + static RandomDataProvider* insecure_random_data_provider =
  13 + InsecureRandomDataProvider::getInstance();
  14 +#else
  15 + static RandomDataProvider* insecure_random_data_provider = 0;
  16 +#endif
  17 + static RandomDataProvider* secure_random_data_provider =
  18 + SecureRandomDataProvider::getInstance();
  19 +
  20 + static RandomDataProvider* provider = (
  21 + secure_random_data_provider ? secure_random_data_provider
  22 + : insecure_random_data_provider ? insecure_random_data_provider
  23 + : 0);
  24 +
  25 + if (provider == 0)
  26 + {
  27 + throw std::logic_error("QPDFCrypto_native has no random data provider");
  28 + }
  29 +
  30 + return provider;
  31 +}
  32 +
  33 +void
  34 +QPDFCrypto_native::provideRandomData(unsigned char* data, size_t len)
  35 +{
  36 + getRandomProvider()->provideRandomData(data, len);
  37 +}
  38 +
4 39 void
5 40 QPDFCrypto_native::MD5_init()
6 41 {
... ...
libqpdf/QPDFCrypto_openssl.cc
... ... @@ -39,6 +39,12 @@ QPDFCrypto_openssl::~QPDFCrypto_openssl()
39 39 }
40 40  
41 41 void
  42 +QPDFCrypto_openssl::provideRandomData(unsigned char* data, size_t len)
  43 +{
  44 + check_openssl(RAND_bytes(data, QIntC::to_int(len)));
  45 +}
  46 +
  47 +void
42 48 QPDFCrypto_openssl::MD5_init()
43 49 {
44 50 check_openssl(EVP_MD_CTX_reset(md_ctx));
... ...
libqpdf/QUtil.cc
... ... @@ -3,10 +3,7 @@
3 3  
4 4 #include <qpdf/QUtil.hh>
5 5 #include <qpdf/PointerHolder.hh>
6   -#ifdef USE_INSECURE_RANDOM
7   -# include <qpdf/InsecureRandomDataProvider.hh>
8   -#endif
9   -#include <qpdf/SecureRandomDataProvider.hh>
  6 +#include <qpdf/CryptoRandomDataProvider.hh>
10 7 #include <qpdf/QPDFSystemError.hh>
11 8 #include <qpdf/QTC.hh>
12 9 #include <qpdf/QIntC.hh>
... ... @@ -891,29 +888,9 @@ class RandomDataProviderProvider
891 888 };
892 889  
893 890 RandomDataProviderProvider::RandomDataProviderProvider() :
894   - default_provider(0),
  891 + default_provider(CryptoRandomDataProvider::getInstance()),
895 892 current_provider(0)
896 893 {
897   -#ifdef USE_INSECURE_RANDOM
898   - static RandomDataProvider* insecure_random_data_provider =
899   - InsecureRandomDataProvider::getInstance();
900   -#else
901   - static RandomDataProvider* insecure_random_data_provider = 0;
902   -#endif
903   - static RandomDataProvider* secure_random_data_provider =
904   - SecureRandomDataProvider::getInstance();
905   -
906   - this->default_provider = (
907   - secure_random_data_provider ? secure_random_data_provider
908   - : insecure_random_data_provider ? insecure_random_data_provider
909   - : 0);
910   -
911   - // QUtil.hh has comments indicating that getRandomDataProvider(),
912   - // which calls this method, never returns null.
913   - if (this->default_provider == 0)
914   - {
915   - throw std::logic_error("QPDF has no random data provider");
916   - }
917 894 this->current_provider = default_provider;
918 895 }
919 896  
... ...
libqpdf/build.mk
... ... @@ -27,6 +27,7 @@ SRCS_libqpdf = \
27 27 libqpdf/BufferInputSource.cc \
28 28 libqpdf/ClosedFileInputSource.cc \
29 29 libqpdf/ContentNormalizer.cc \
  30 + libqpdf/CryptoRandomDataProvider.cc \
30 31 libqpdf/FileInputSource.cc \
31 32 libqpdf/InputSource.cc \
32 33 libqpdf/InsecureRandomDataProvider.cc \
... ...
libqpdf/qpdf/CryptoRandomDataProvider.hh 0 โ†’ 100644
  1 +#ifndef CRYPTORANDOMDATAPROVIDER_HH
  2 +#define CRYPTORANDOMDATAPROVIDER_HH
  3 +
  4 +#include <qpdf/RandomDataProvider.hh>
  5 +#include <qpdf/DLL.h>
  6 +
  7 +class CryptoRandomDataProvider: public RandomDataProvider
  8 +{
  9 + public:
  10 + QPDF_DLL
  11 + CryptoRandomDataProvider();
  12 + QPDF_DLL
  13 + virtual ~CryptoRandomDataProvider();
  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 // CRYPTORANDOMDATAPROVIDER_HH
... ...
libqpdf/qpdf/QPDFCrypto_gnutls.hh
... ... @@ -15,6 +15,8 @@ class QPDFCrypto_gnutls: public QPDFCryptoImpl
15 15 QPDF_DLL
16 16 virtual ~QPDFCrypto_gnutls();
17 17  
  18 + virtual void provideRandomData(unsigned char* data, size_t len);
  19 +
18 20 virtual void MD5_init();
19 21 virtual void MD5_update(unsigned char const* data, size_t len);
20 22 virtual void MD5_finalize();
... ...
libqpdf/qpdf/QPDFCrypto_native.hh
... ... @@ -17,6 +17,8 @@ class QPDFCrypto_native: public QPDFCryptoImpl
17 17 QPDF_DLL
18 18 virtual ~QPDFCrypto_native() = default;
19 19  
  20 + virtual void provideRandomData(unsigned char* data, size_t len);
  21 +
20 22 virtual void MD5_init();
21 23 virtual void MD5_update(unsigned char const* data, size_t len);
22 24 virtual void MD5_finalize();
... ...
libqpdf/qpdf/QPDFCrypto_openssl.hh
... ... @@ -9,6 +9,7 @@
9 9 #else
10 10 #include <openssl/evp.h>
11 11 #endif
  12 +#include <openssl/rand.h>
12 13  
13 14 class QPDFCrypto_openssl: public QPDFCryptoImpl
14 15 {
... ... @@ -18,6 +19,8 @@ class QPDFCrypto_openssl: public QPDFCryptoImpl
18 19 QPDF_DLL
19 20 ~QPDFCrypto_openssl() override;
20 21  
  22 + void provideRandomData(unsigned char* data, size_t len) override;
  23 +
21 24 void MD5_init() override;
22 25 void MD5_update(unsigned char const* data, size_t len) override;
23 26 void MD5_finalize() override;
... ...
libtests/qtest/random.test
... ... @@ -7,10 +7,30 @@ require TestDriver;
7 7  
8 8 my $td = new TestDriver('random');
9 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);
  10 +my @providers = ();
  11 +if (exists $ENV{'QPDF_CRYPTO_PROVIDER'})
  12 +{
  13 + push(@providers, $ENV{'QPDF_CRYPTO_PROVIDER'});
  14 +}
  15 +else
  16 +{
  17 + open(Q, "qpdf --show-crypto|") or die;
  18 + while (<Q>)
  19 + {
  20 + s/\s+$//s;
  21 + push(@providers, $_);
  22 + }
  23 + close(Q);
  24 +}
  25 +foreach my $p (@providers)
  26 +{
  27 + $ENV{'QPDF_CRYPTO_PROVIDER'} = $p;
15 28  
16   -$td->report(1);
  29 + $td->runtest("Random Data Providers ($p)",
  30 + {$td->COMMAND => "random"},
  31 + {$td->STRING => "random: end of tests\n",
  32 + $td->EXIT_STATUS => 0},
  33 + $td->NORMALIZE_NEWLINES);
  34 +}
  35 +
  36 +$td->report(scalar(@providers));
... ...
manual/qpdf-manual.xml
... ... @@ -3914,25 +3914,16 @@ outfile.pdf&lt;/option&gt;
3914 3914 <title>Random Number Generation</title>
3915 3915 <para>
3916 3916 QPDF generates random numbers to support generation of encrypted
3917   - data. Versions prior to 5.0.1 used <function>random</function> or
3918   - <function>rand</function> from <filename>stdlib</filename> to
3919   - generate random numbers. Version 5.0.1, if available, used
3920   - operating system-provided secure random number generation instead,
3921   - enabling use of <filename>stdlib</filename> random number
3922   - generation only if enabled by a compile-time option. Starting in
3923   - version 5.1.0, use of insecure random numbers was disabled unless
3924   - enabled at compile time. Starting in version 5.1.0, it is also
3925   - possible for you to disable use of OS-provided secure random
3926   - numbers. This is especially useful on Windows if you want to
3927   - avoid a dependency on Microsoft's cryptography API. In this case,
3928   - you must provide your own random data provider. Regardless of how
3929   - you compile qpdf, starting in version 5.1.0, it is possible for
3930   - you to provide your own random data provider at runtime. This
3931   - would enable you to use some software-based secure pseudorandom
3932   - number generator and to avoid use of whatever the operating system
3933   - provides. For details on how to do this, please refer to the
3934   - top-level README.md file in the source distribution and to comments
3935   - in <filename>QUtil.hh</filename>.
  3917 + data. Starting in qpdf 10.0.0, qpdf uses the crypto provider as
  3918 + its source of random numbers. Older versions used the OS-provided
  3919 + source of secure random numbers or, if allowed at build time,
  3920 + insecure random numbers from stdlib. Starting with version 5.1.0,
  3921 + you can disable use of OS-provided secure random numbers at build
  3922 + time. This is especially useful on Windows if you want to avoid a
  3923 + dependency on Microsoft's cryptography API. You can also supply
  3924 + your own random data provider. For details on how to do this,
  3925 + please refer to the top-level README.md file in the source
  3926 + distribution and to comments in <filename>QUtil.hh</filename>.
3936 3927 </para>
3937 3928 </sect1>
3938 3929 <sect1 id="ref.adding-and-remove-pages">
... ... @@ -4908,6 +4899,14 @@ print &quot;\n&quot;;
4908 4899 <itemizedlist>
4909 4900 <listitem>
4910 4901 <para>
  4902 + Random number generation is now delegated to the crypto
  4903 + provider. The old behavior is still used by the native
  4904 + crypto provider. It is still possible to provide your own
  4905 + random number generator.
  4906 + </para>
  4907 + </listitem>
  4908 + <listitem>
  4909 + <para>
4911 4910 Add a new version of
4912 4911 <function>QPDFObjectHandle::StreamDataProvider::provideStreamData</function>
4913 4912 that accepts the <function>suppress_warnings</function> and
... ...