Commit 77198d5310d961ba3605db74fe1d213bb5d19f34

Authored by Jay Berkenbilt
1 parent 52749b85

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

ChangeLog
1 2020-04-06 Jay Berkenbilt <ejb@ql.org> 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 * Source-level incompatibility: remove QUtil::srandom. There was 11 * Source-level incompatibility: remove QUtil::srandom. There was
4 no reason to ever call this, and it didn't do anything unless 12 no reason to ever call this, and it didn't do anything unless
5 insecure random number generation was compiled in, which it is not 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,7 +111,9 @@ If you are packaging qpdf for a distribution and preparing a build that is run b
111 111
112 # Random Number Generation 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 * 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. 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 * 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. 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 * 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. 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,6 +41,11 @@ class QPDF_DLL_CLASS QPDFCryptoImpl
41 QPDF_DLL 41 QPDF_DLL
42 virtual ~QPDFCryptoImpl() = default; 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 // Hashing 49 // Hashing
45 50
46 typedef unsigned char MD5_Digest[16]; 51 typedef unsigned char MD5_Digest[16];
include/qpdf/QUtil.hh
@@ -263,35 +263,25 @@ namespace QUtil @@ -263,35 +263,25 @@ namespace QUtil
263 QPDF_DLL 263 QPDF_DLL
264 std::vector<std::string> possible_repaired_encodings(std::string); 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 QPDF_DLL 267 QPDF_DLL
272 long random(); 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 QPDF_DLL 271 QPDF_DLL
282 void initializeWithRandomBytes(unsigned char* data, size_t len); 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 QPDF_DLL 285 QPDF_DLL
296 void setRandomDataProvider(RandomDataProvider*); 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,6 +30,18 @@ QPDFCrypto_gnutls::~QPDFCrypto_gnutls()
30 } 30 }
31 31
32 void 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 QPDFCrypto_gnutls::MD5_init() 45 QPDFCrypto_gnutls::MD5_init()
34 { 46 {
35 MD5_finalize(); 47 MD5_finalize();
libqpdf/QPDFCrypto_native.cc
1 #include <qpdf/QPDFCrypto_native.hh> 1 #include <qpdf/QPDFCrypto_native.hh>
2 #include <qpdf/QUtil.hh> 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 void 39 void
5 QPDFCrypto_native::MD5_init() 40 QPDFCrypto_native::MD5_init()
6 { 41 {
libqpdf/QPDFCrypto_openssl.cc
@@ -39,6 +39,12 @@ QPDFCrypto_openssl::~QPDFCrypto_openssl() @@ -39,6 +39,12 @@ QPDFCrypto_openssl::~QPDFCrypto_openssl()
39 } 39 }
40 40
41 void 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 QPDFCrypto_openssl::MD5_init() 48 QPDFCrypto_openssl::MD5_init()
43 { 49 {
44 check_openssl(EVP_MD_CTX_reset(md_ctx)); 50 check_openssl(EVP_MD_CTX_reset(md_ctx));
libqpdf/QUtil.cc
@@ -3,10 +3,7 @@ @@ -3,10 +3,7 @@
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 +#include <qpdf/CryptoRandomDataProvider.hh>
10 #include <qpdf/QPDFSystemError.hh> 7 #include <qpdf/QPDFSystemError.hh>
11 #include <qpdf/QTC.hh> 8 #include <qpdf/QTC.hh>
12 #include <qpdf/QIntC.hh> 9 #include <qpdf/QIntC.hh>
@@ -891,29 +888,9 @@ class RandomDataProviderProvider @@ -891,29 +888,9 @@ class RandomDataProviderProvider
891 }; 888 };
892 889
893 RandomDataProviderProvider::RandomDataProviderProvider() : 890 RandomDataProviderProvider::RandomDataProviderProvider() :
894 - default_provider(0), 891 + default_provider(CryptoRandomDataProvider::getInstance()),
895 current_provider(0) 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 this->current_provider = default_provider; 894 this->current_provider = default_provider;
918 } 895 }
919 896
libqpdf/build.mk
@@ -27,6 +27,7 @@ SRCS_libqpdf = \ @@ -27,6 +27,7 @@ SRCS_libqpdf = \
27 libqpdf/BufferInputSource.cc \ 27 libqpdf/BufferInputSource.cc \
28 libqpdf/ClosedFileInputSource.cc \ 28 libqpdf/ClosedFileInputSource.cc \
29 libqpdf/ContentNormalizer.cc \ 29 libqpdf/ContentNormalizer.cc \
  30 + libqpdf/CryptoRandomDataProvider.cc \
30 libqpdf/FileInputSource.cc \ 31 libqpdf/FileInputSource.cc \
31 libqpdf/InputSource.cc \ 32 libqpdf/InputSource.cc \
32 libqpdf/InsecureRandomDataProvider.cc \ 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,6 +15,8 @@ class QPDFCrypto_gnutls: public QPDFCryptoImpl
15 QPDF_DLL 15 QPDF_DLL
16 virtual ~QPDFCrypto_gnutls(); 16 virtual ~QPDFCrypto_gnutls();
17 17
  18 + virtual void provideRandomData(unsigned char* data, size_t len);
  19 +
18 virtual void MD5_init(); 20 virtual void MD5_init();
19 virtual void MD5_update(unsigned char const* data, size_t len); 21 virtual void MD5_update(unsigned char const* data, size_t len);
20 virtual void MD5_finalize(); 22 virtual void MD5_finalize();
libqpdf/qpdf/QPDFCrypto_native.hh
@@ -17,6 +17,8 @@ class QPDFCrypto_native: public QPDFCryptoImpl @@ -17,6 +17,8 @@ class QPDFCrypto_native: public QPDFCryptoImpl
17 QPDF_DLL 17 QPDF_DLL
18 virtual ~QPDFCrypto_native() = default; 18 virtual ~QPDFCrypto_native() = default;
19 19
  20 + virtual void provideRandomData(unsigned char* data, size_t len);
  21 +
20 virtual void MD5_init(); 22 virtual void MD5_init();
21 virtual void MD5_update(unsigned char const* data, size_t len); 23 virtual void MD5_update(unsigned char const* data, size_t len);
22 virtual void MD5_finalize(); 24 virtual void MD5_finalize();
libqpdf/qpdf/QPDFCrypto_openssl.hh
@@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
9 #else 9 #else
10 #include <openssl/evp.h> 10 #include <openssl/evp.h>
11 #endif 11 #endif
  12 +#include <openssl/rand.h>
12 13
13 class QPDFCrypto_openssl: public QPDFCryptoImpl 14 class QPDFCrypto_openssl: public QPDFCryptoImpl
14 { 15 {
@@ -18,6 +19,8 @@ class QPDFCrypto_openssl: public QPDFCryptoImpl @@ -18,6 +19,8 @@ class QPDFCrypto_openssl: public QPDFCryptoImpl
18 QPDF_DLL 19 QPDF_DLL
19 ~QPDFCrypto_openssl() override; 20 ~QPDFCrypto_openssl() override;
20 21
  22 + void provideRandomData(unsigned char* data, size_t len) override;
  23 +
21 void MD5_init() override; 24 void MD5_init() override;
22 void MD5_update(unsigned char const* data, size_t len) override; 25 void MD5_update(unsigned char const* data, size_t len) override;
23 void MD5_finalize() override; 26 void MD5_finalize() override;
libtests/qtest/random.test
@@ -7,10 +7,30 @@ require TestDriver; @@ -7,10 +7,30 @@ require TestDriver;
7 7
8 my $td = new TestDriver('random'); 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,25 +3914,16 @@ outfile.pdf&lt;/option&gt;
3914 <title>Random Number Generation</title> 3914 <title>Random Number Generation</title>
3915 <para> 3915 <para>
3916 QPDF generates random numbers to support generation of encrypted 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 </para> 3927 </para>
3937 </sect1> 3928 </sect1>
3938 <sect1 id="ref.adding-and-remove-pages"> 3929 <sect1 id="ref.adding-and-remove-pages">
@@ -4908,6 +4899,14 @@ print &quot;\n&quot;; @@ -4908,6 +4899,14 @@ print &quot;\n&quot;;
4908 <itemizedlist> 4899 <itemizedlist>
4909 <listitem> 4900 <listitem>
4910 <para> 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 Add a new version of 4910 Add a new version of
4912 <function>QPDFObjectHandle::StreamDataProvider::provideStreamData</function> 4911 <function>QPDFObjectHandle::StreamDataProvider::provideStreamData</function>
4913 that accepts the <function>suppress_warnings</function> and 4912 that accepts the <function>suppress_warnings</function> and