rand.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. """
  2. PRNG management routines, thin wrappers.
  3. See the file RATIONALE for a short explanation of why this module was written.
  4. """
  5. from functools import partial
  6. from six import integer_types as _integer_types
  7. from OpenSSL._util import (
  8. ffi as _ffi,
  9. lib as _lib,
  10. exception_from_error_queue as _exception_from_error_queue,
  11. path_string as _path_string)
  12. class Error(Exception):
  13. """
  14. An error occurred in an `OpenSSL.rand` API.
  15. """
  16. _raise_current_error = partial(_exception_from_error_queue, Error)
  17. _unspecified = object()
  18. _builtin_bytes = bytes
  19. def bytes(num_bytes):
  20. """
  21. Get some random bytes as a string.
  22. :param num_bytes: The number of bytes to fetch
  23. :return: A string of random bytes
  24. """
  25. if not isinstance(num_bytes, _integer_types):
  26. raise TypeError("num_bytes must be an integer")
  27. if num_bytes < 0:
  28. raise ValueError("num_bytes must not be negative")
  29. result_buffer = _ffi.new("char[]", num_bytes)
  30. result_code = _lib.RAND_bytes(result_buffer, num_bytes)
  31. if result_code == -1:
  32. # TODO: No tests for this code path. Triggering a RAND_bytes failure
  33. # might involve supplying a custom ENGINE? That's hard.
  34. _raise_current_error()
  35. return _ffi.buffer(result_buffer)[:]
  36. def add(buffer, entropy):
  37. """
  38. Add data with a given entropy to the PRNG
  39. :param buffer: Buffer with random data
  40. :param entropy: The entropy (in bytes) measurement of the buffer
  41. :return: None
  42. """
  43. if not isinstance(buffer, _builtin_bytes):
  44. raise TypeError("buffer must be a byte string")
  45. if not isinstance(entropy, int):
  46. raise TypeError("entropy must be an integer")
  47. # TODO Nothing tests this call actually being made, or made properly.
  48. _lib.RAND_add(buffer, len(buffer), entropy)
  49. def seed(buffer):
  50. """
  51. Alias for rand_add, with entropy equal to length
  52. :param buffer: Buffer with random data
  53. :return: None
  54. """
  55. if not isinstance(buffer, _builtin_bytes):
  56. raise TypeError("buffer must be a byte string")
  57. # TODO Nothing tests this call actually being made, or made properly.
  58. _lib.RAND_seed(buffer, len(buffer))
  59. def status():
  60. """
  61. Retrieve the status of the PRNG
  62. :return: True if the PRNG is seeded enough, false otherwise
  63. """
  64. return _lib.RAND_status()
  65. def egd(path, bytes=_unspecified):
  66. """
  67. Query an entropy gathering daemon (EGD) for random data and add it to the
  68. PRNG. I haven't found any problems when the socket is missing, the function
  69. just returns 0.
  70. :param path: The path to the EGD socket
  71. :param bytes: (optional) The number of bytes to read, default is 255
  72. :returns: The number of bytes read (NB: a value of 0 isn't necessarily an
  73. error, check rand.status())
  74. """
  75. if not isinstance(path, _builtin_bytes):
  76. raise TypeError("path must be a byte string")
  77. if bytes is _unspecified:
  78. bytes = 255
  79. elif not isinstance(bytes, int):
  80. raise TypeError("bytes must be an integer")
  81. return _lib.RAND_egd_bytes(path, bytes)
  82. def cleanup():
  83. """
  84. Erase the memory used by the PRNG.
  85. :return: None
  86. """
  87. # TODO Nothing tests this call actually being made, or made properly.
  88. _lib.RAND_cleanup()
  89. def load_file(filename, maxbytes=_unspecified):
  90. """
  91. Seed the PRNG with data from a file
  92. :param filename: The file to read data from (``bytes`` or ``unicode``).
  93. :param maxbytes: (optional) The number of bytes to read, default is to read
  94. the entire file
  95. :return: The number of bytes read
  96. """
  97. filename = _path_string(filename)
  98. if maxbytes is _unspecified:
  99. maxbytes = -1
  100. elif not isinstance(maxbytes, int):
  101. raise TypeError("maxbytes must be an integer")
  102. return _lib.RAND_load_file(filename, maxbytes)
  103. def write_file(filename):
  104. """
  105. Save PRNG state to a file
  106. :param filename: The file to write data to (``bytes`` or ``unicode``).
  107. :return: The number of bytes written
  108. """
  109. filename = _path_string(filename)
  110. return _lib.RAND_write_file(filename)
  111. # TODO There are no tests for screen at all
  112. def screen():
  113. """
  114. Add the current contents of the screen to the PRNG state. Availability:
  115. Windows.
  116. :return: None
  117. """
  118. _lib.RAND_screen()
  119. if getattr(_lib, 'RAND_screen', None) is None:
  120. del screen
  121. # TODO There are no tests for the RAND strings being loaded, whatever that
  122. # means.
  123. _lib.ERR_load_RAND_strings()