Skip to content

Commit

Permalink
restore a subset of the rand module
Browse files Browse the repository at this point in the history
  • Loading branch information
reaperhulk committed Nov 20, 2017
1 parent fe0120f commit e4287c8
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 1 deletion.
4 changes: 3 additions & 1 deletion CHANGELOG.rst
Expand Up @@ -23,7 +23,9 @@ Deprecations:
Changes:
^^^^^^^^

*none*
- Re-added a subset of the ``OpenSSL.rand`` module.
This subset allows conscientious users to reseed the OpenSSL CSPRNG after fork.
`#708 <https://github.com/pyca/pyopenssl/pull/708>`_


----
Expand Down
82 changes: 82 additions & 0 deletions src/OpenSSL/rand.py
@@ -0,0 +1,82 @@
"""
PRNG management routines, thin wrappers.
"""

from functools import partial

from OpenSSL._util import (
ffi as _ffi,
lib as _lib,
exception_from_error_queue as _exception_from_error_queue,
path_string as _path_string)


class Error(Exception):
"""
An error occurred in an :mod:`OpenSSL.rand` API.
If the current RAND method supports any errors, this is raised when needed.
The default method does not raise this when the entropy pool is depleted.
Whenever this exception is raised directly, it has a list of error messages
from the OpenSSL error queue, where each item is a tuple *(lib, function,
reason)*. Here *lib*, *function* and *reason* are all strings, describing
where and what the problem is.
See :manpage:`err(3)` for more information.
"""


_raise_current_error = partial(_exception_from_error_queue, Error)

_unspecified = object()

_builtin_bytes = bytes


def add(buffer, entropy):
"""
Mix bytes from *string* into the PRNG state.
The *entropy* argument is (the lower bound of) an estimate of how much
randomness is contained in *string*, measured in bytes.
For more information, see e.g. :rfc:`1750`.
This function is only relevant if you are forking Python processes and
need to reseed the CSPRNG after fork.
:param buffer: Buffer with random data.
:param entropy: The entropy (in bytes) measurement of the buffer.
:return: :obj:`None`
"""
if not isinstance(buffer, _builtin_bytes):
raise TypeError("buffer must be a byte string")

if not isinstance(entropy, int):
raise TypeError("entropy must be an integer")

_lib.RAND_add(buffer, len(buffer), entropy)


def status():
"""
Check whether the PRNG has been seeded with enough data.
:return: 1 if the PRNG is seeded enough, 0 otherwise.
"""
return _lib.RAND_status()


def cleanup():
"""
Erase the memory used by the PRNG.
This is a wrapper for the C function ``RAND_cleanup``.
:return: :obj:`None`
"""
# TODO Nothing tests this call actually being made, or made properly.
_lib.RAND_cleanup()

48 changes: 48 additions & 0 deletions tests/test_rand.py
@@ -0,0 +1,48 @@
# Copyright (c) Frederick Dean
# See LICENSE for details.

"""
Unit tests for `OpenSSL.rand`.
"""

import pytest

from OpenSSL import rand


class TestRand(object):

@pytest.mark.parametrize('args', [
(b"foo", None),
(None, 3),
])
def test_add_wrong_args(self, args):
"""
`OpenSSL.rand.add` raises `TypeError` if called with arguments not of
type `str` and `int`.
"""
with pytest.raises(TypeError):
rand.add(*args)

def test_add(self):
"""
`OpenSSL.rand.add` adds entropy to the PRNG.
"""
rand.add(b'hamburger', 3)

def test_status(self):
"""
`OpenSSL.rand.status` returns `1` if the PRNG has sufficient entropy,
`0` otherwise.
"""
# It's hard to know what it is actually going to return. Different
# OpenSSL random engines decide differently whether they have enough
# entropy or not.
assert rand.status() in (0, 1)

def test_cleanup(self):
"""
`OpenSSL.rand.cleanup` releases the memory used by the PRNG and
returns `None`.
"""
assert rand.cleanup() is None

0 comments on commit e4287c8

Please sign in to comment.