Commit 4229457068d6a28cc11b506f127a7bb650ab18c1

Authored by Jay Berkenbilt
1 parent 25687ddd

Security: use a secure random number generator

If not available, give an error.  The user may also configure qpdf to
use an insecure random number generator.
ChangeLog
1 2013-10-05 Jay Berkenbilt <ejb@ql.org> 1 2013-10-05 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * Use cryptographically secure random number generation when
  4 + available. See additional notes in README.
  5 +
3 * Replace some assert() calls with std::logic_error exceptions. 6 * Replace some assert() calls with std::logic_error exceptions.
4 Ideally there shouldn't be assert() calls outside of testing. 7 Ideally there shouldn't be assert() calls outside of testing.
5 This change may make a few more potential code errors in handling 8 This change may make a few more potential code errors in handling
@@ -167,3 +167,22 @@ the test suite fails, test failure detail will be included in the @@ -167,3 +167,22 @@ the test suite fails, test failure detail will be included in the
167 build output. Otherwise, you will have to have access to the 167 build output. Otherwise, you will have to have access to the
168 qtest.log file from the build to view test failures. The debian 168 qtest.log file from the build to view test failures. The debian
169 packages for qpdf enable this option, for example. 169 packages for qpdf enable this option, for example.
  170 +
  171 +
  172 +Random Number Generation
  173 +========================
  174 +
  175 +When the qpdf detects either the Windows cryptography API or the
  176 +existence of /dev/urandom, /dev/arandom, or /dev/random, it uses them
  177 +to generate cryptography secure random numbers. If none of these
  178 +conditions are true, the build will fail with an error. It is
  179 +possible to configure qpdf with the --enable-insecure-random option,
  180 +in which case it will generate random numbers with stdlib's random()
  181 +or rand() calls instead. These random numbers are not cryptography
  182 +secure, but the qpdf library is fully functional using them. Using
  183 +non-secure random numbers means that it's easier in some cases to
  184 +guess encryption keys. If you're not generating encrypted files,
  185 +there's no advantage to using secure random numbers.
  186 +
  187 +If you are building qpdf on a platform that qpdf doesn't know how to
  188 +generate secure random numbers on, a patch would be welcome.
@@ -76,12 +76,11 @@ General @@ -76,12 +76,11 @@ General
76 and replace the /Pages key of the root dictionary with the new 76 and replace the /Pages key of the root dictionary with the new
77 tree. 77 tree.
78 78
79 - * Improve the random number seed to make it more secure so that we  
80 - have stronger random numbers, particularly when multiple files are  
81 - generated in the same second. This code may need to be  
82 - OS-specific. Probably we should add a method in QUtil to seed with  
83 - a strong random number and call this automatically the first time  
84 - QUtil::random() is called. 79 + * Secure random number generation could be made more efficient by
  80 + using a local static to ensure a single random device or crypt
  81 + provider as long as this can be done in a thread-safe fashion. In
  82 + the initial implementation, this is being skipped to avoid having
  83 + to add any dependencies on threading libraries.
85 84
86 * Study what's required to support savable forms that can be saved by 85 * Study what's required to support savable forms that can be saved by
87 Adobe Reader. Does this require actually signing the document with 86 Adobe Reader. Does this require actually signing the document with
configure.ac
@@ -16,6 +16,23 @@ AC_PROG_CXX @@ -16,6 +16,23 @@ AC_PROG_CXX
16 AC_HEADER_STDC 16 AC_HEADER_STDC
17 LT_INIT([win32-dll]) 17 LT_INIT([win32-dll])
18 18
  19 +AC_ARG_ENABLE(insecure-random,
  20 + AS_HELP_STRING([--enable-insecure-random],
  21 + [whether to use stdlib's random number generator (default is no)]),
  22 + [if test "$enableval" = "yes"; then
  23 + qpdf_INSECURE_RANDOM=1;
  24 + else
  25 + qpdf_INSECURE_RANDOM=0;
  26 + fi], [qpdf_INSECURE_RANDOM=0])
  27 +if test "$qpdf_INSECURE_RANDOM" = "1"; then
  28 + AC_MSG_RESULT(yes)
  29 + AC_DEFINE([USE_INSECURE_RANDOM], [1], [Whether to use inscure random numbers])
  30 +else
  31 + AC_MSG_RESULT(no)
  32 +fi
  33 +
  34 +AX_RANDOM_DEVICE
  35 +
19 USE_EXTERNAL_LIBS=0 36 USE_EXTERNAL_LIBS=0
20 AC_MSG_CHECKING(for whether to use external libraries distribution) 37 AC_MSG_CHECKING(for whether to use external libraries distribution)
21 AC_ARG_ENABLE(external-libs, 38 AC_ARG_ENABLE(external-libs,
@@ -54,6 +71,23 @@ if test &quot;$BUILD_INTERNAL_LIBS&quot; = &quot;0&quot;; then @@ -54,6 +71,23 @@ if test &quot;$BUILD_INTERNAL_LIBS&quot; = &quot;0&quot;; then
54 AC_SEARCH_LIBS(pcre_compile,pcre,,[MISSING_PCRE=1; MISSING_ANY=1]) 71 AC_SEARCH_LIBS(pcre_compile,pcre,,[MISSING_PCRE=1; MISSING_ANY=1])
55 fi 72 fi
56 73
  74 +if test "x$qpdf_INSECURE_RANDOM" != "x1"; then
  75 + OLIBS=$LIBS
  76 + LIBS="$LIBS Advapi32.lib"
  77 + AC_MSG_CHECKING(for Advapi32 library)
  78 + AC_LINK_IFELSE([AC_LANG_PROGRAM(
  79 + [[#pragma comment(lib, "crypt32.lib")
  80 + #include <windows.h>
  81 + #include <Wincrypt.h>
  82 + HCRYPTPROV cp;]],
  83 + [CryptAcquireContext(&cp, NULL, NULL, PROV_RSA_FULL, 0);]
  84 + )],
  85 + [AC_MSG_RESULT(yes)
  86 + LIBS="$OLIBS -lAdvapi32"],
  87 + [AC_MSG_RESULT(no)
  88 + LIBS=$OLIBS])
  89 +fi
  90 +
57 QPDF_LARGE_FILE_TEST_PATH= 91 QPDF_LARGE_FILE_TEST_PATH=
58 AC_SUBST(QPDF_LARGE_FILE_TEST_PATH) 92 AC_SUBST(QPDF_LARGE_FILE_TEST_PATH)
59 AC_ARG_WITH(large-file-test-path, 93 AC_ARG_WITH(large-file-test-path,
copy_dlls
@@ -20,7 +20,7 @@ while (&lt;O&gt;) @@ -20,7 +20,7 @@ while (&lt;O&gt;)
20 { 20 {
21 my $dll = $1; 21 my $dll = $1;
22 $dll =~ tr/A-Z/a-z/; 22 $dll =~ tr/A-Z/a-z/;
23 - next if $dll =~ m/^(kernel32|user32|msvcrt)\.dll$/; 23 + next if $dll =~ m/^(kernel32|user32|msvcrt|advapi32)\.dll$/;
24 $dlls{$dll} = 1; 24 $dlls{$dll} = 1;
25 } 25 }
26 elsif (m/^Magic.*\((PE.+?)\)/) 26 elsif (m/^Magic.*\((PE.+?)\)/)
include/qpdf/QUtil.hh
@@ -108,12 +108,18 @@ namespace QUtil @@ -108,12 +108,18 @@ namespace QUtil
108 QPDF_DLL 108 QPDF_DLL
109 std::string toUTF8(unsigned long uval); 109 std::string toUTF8(unsigned long uval);
110 110
111 - // Wrapper around random from stdlib. Calls srandom automatically  
112 - // the first time it is called. 111 + // If secure random number generation is supported on your
  112 + // platform and qpdf was not compiled with insecure random number
  113 + // generation, this returns a crytographically secure random
  114 + // number. Otherwise it falls back to random from stdlib and
  115 + // calls srandom automatically the first time it is called.
113 QPDF_DLL 116 QPDF_DLL
114 long random(); 117 long random();
115 118
116 - // Wrapper around srandom from stdlib. 119 + // Wrapper around srandom from stdlib. Seeds the standard library
  120 + // weak random number generator, which is not used if secure
  121 + // random number generation is being used. You never need to call
  122 + // this method as it is called automatically if needed.
117 QPDF_DLL 123 QPDF_DLL
118 void srandom(unsigned int seed); 124 void srandom(unsigned int seed);
119 125
libqpdf/QUtil.cc
@@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
17 #include <Windows.h> 17 #include <Windows.h>
18 #include <direct.h> 18 #include <direct.h>
19 #include <io.h> 19 #include <io.h>
  20 +#include <Wincrypt.h>
20 #else 21 #else
21 #include <unistd.h> 22 #include <unistd.h>
22 #endif 23 #endif
@@ -377,6 +378,8 @@ QUtil::toUTF8(unsigned long uval) @@ -377,6 +378,8 @@ QUtil::toUTF8(unsigned long uval)
377 return result; 378 return result;
378 } 379 }
379 380
  381 +#ifdef USE_INSECURE_RANDOM
  382 +
380 long 383 long
381 QUtil::random() 384 QUtil::random()
382 { 385 {
@@ -390,28 +393,100 @@ QUtil::random() @@ -390,28 +393,100 @@ QUtil::random()
390 seeded_random = true; 393 seeded_random = true;
391 } 394 }
392 395
393 -#ifdef HAVE_RANDOM 396 +# ifdef HAVE_RANDOM
394 return ::random(); 397 return ::random();
395 -#else 398 +# else
396 return rand(); 399 return rand();
397 -#endif 400 +# endif
398 } 401 }
399 402
400 void 403 void
401 -QUtil::srandom(unsigned int seed) 404 +QUtil::initializeWithRandomBytes(unsigned char* data, size_t len)
402 { 405 {
403 -#ifdef HAVE_RANDOM  
404 - ::srandom(seed); 406 + for (size_t i = 0; i < len; ++i)
  407 + {
  408 + data[i] = static_cast<unsigned char>((QUtil::random() & 0xff0) >> 4);
  409 + }
  410 +}
  411 +
405 #else 412 #else
406 - srand(seed);  
407 -#endif 413 +
  414 +long
  415 +QUtil::random()
  416 +{
  417 + long result = 0L;
  418 + initializeWithRandomBytes(
  419 + reinterpret_cast<unsigned char*>(&result),
  420 + sizeof(result));
  421 + return result;
408 } 422 }
409 423
  424 +#ifdef _WIN32
  425 +class WindowsCryptProvider
  426 +{
  427 + public:
  428 + WindowsCryptProvider()
  429 + {
  430 + if (! CryptAcquireContext(&crypt_prov, NULL, NULL, PROV_RSA_FULL, 0))
  431 + {
  432 + throw std::runtime_error("unable to acquire crypt context");
  433 + }
  434 + }
  435 + ~WindowsCryptProvider()
  436 + {
  437 + // Ignore error
  438 + CryptReleaseContext(crypt_prov, 0);
  439 + }
  440 +
  441 + HCRYPTPROV crypt_prov;
  442 +};
  443 +#endif
  444 +
410 void 445 void
411 QUtil::initializeWithRandomBytes(unsigned char* data, size_t len) 446 QUtil::initializeWithRandomBytes(unsigned char* data, size_t len)
412 { 447 {
413 - for (size_t i = 0; i < len; ++i) 448 +#if defined(_WIN32)
  449 +
  450 + // Optimization: make the WindowsCryptProvider static as long as
  451 + // it can be done in a thread-safe fashion.
  452 + WindowsCryptProvider c;
  453 + if (! CryptGenRandom(c.crypt_prov, len, reinterpret_cast<BYTE*>(data)))
414 { 454 {
415 - data[i] = static_cast<unsigned char>((QUtil::random() & 0xff0) >> 4); 455 + throw std::runtime_error("unable to generate secure random data");
  456 + }
  457 +
  458 +#elif defined(RANDOM_DEVICE)
  459 +
  460 + // Optimization: wrap the file open and close in a class so that
  461 + // the file is closed in a destructor, then make this static to
  462 + // keep the file handle open. Only do this if it can be done in a
  463 + // thread-safe fashion.
  464 + FILE* f = QUtil::safe_fopen(RANDOM_DEVICE, "rb");
  465 + size_t fr = fread(data, 1, len, f);
  466 + fclose(f);
  467 + if (fr != len)
  468 + {
  469 + throw std::runtime_error(
  470 + "unable to read " +
  471 + QUtil::int_to_string(len) +
  472 + " bytes from " + std::string(RANDOM_DEVICE));
416 } 473 }
  474 +
  475 +#else
  476 +
  477 +# error "Don't know how to generate secure random numbers on this platform. See random number generation in the top-level README"
  478 +
  479 +#endif
  480 +}
  481 +
  482 +#endif
  483 +
  484 +void
  485 +QUtil::srandom(unsigned int seed)
  486 +{
  487 +#ifdef HAVE_RANDOM
  488 + ::srandom(seed);
  489 +#else
  490 + srand(seed);
  491 +#endif
417 } 492 }
m4/ax_random_device.m4 0 → 100644
  1 +dnl @synopsis AX_RANDOM_DEVICE
  2 +dnl
  3 +dnl This macro will check for a random device, allowing the user to explicitly
  4 +dnl set the path. The user uses '--with-random=FILE' as an argument to
  5 +dnl configure.
  6 +dnl
  7 +dnl If A random device is found then HAVE_RANDOM_DEVICE is set to 1 and
  8 +dnl RANDOM_DEVICE contains the path.
  9 +dnl
  10 +dnl @category Miscellaneous
  11 +dnl @author Martin Ebourne
  12 +dnl @version 2005/07/01
  13 +dnl @license AllPermissive
  14 +
  15 +AC_DEFUN([AX_RANDOM_DEVICE], [
  16 + AC_ARG_WITH([random],
  17 + [AC_HELP_STRING([--with-random=FILE], [Use FILE as random number seed [auto-detected]])],
  18 + [RANDOM_DEVICE="$withval"],
  19 + [AC_CHECK_FILE("/dev/urandom", [RANDOM_DEVICE="/dev/urandom";],
  20 + [AC_CHECK_FILE("/dev/arandom", [RANDOM_DEVICE="/dev/arandom";],
  21 + [AC_CHECK_FILE("/dev/random", [RANDOM_DEVICE="/dev/random";])]
  22 + )])
  23 + ])
  24 + if test "x$RANDOM_DEVICE" != "x" ; then
  25 + AC_DEFINE([HAVE_RANDOM_DEVICE], 1,
  26 + [Define to 1 (and set RANDOM_DEVICE) if a random device is available])
  27 + AC_SUBST([RANDOM_DEVICE])
  28 + AC_DEFINE_UNQUOTED([RANDOM_DEVICE], ["$RANDOM_DEVICE"],
  29 + [Define to the filename of the random device (and set HAVE_RANDOM_DEVICE)])
  30 + fi
  31 +])dnl