1# Copyright 2012-2019, Damian Johnson and The Tor Project
2# See LICENSE for licensing information
3
4"""
5Checks for stem dependencies.
6
7Aside from Python itself Stem only has soft dependencies, which is to say
8module unavailability only impacts features that require it. For example,
9descriptor signature validation requires 'cryptography'. If unavailable
10stem will still read descriptors - just without signature checks.
11
12::
13
14  check_requirements - checks for minimum requirements for running stem
15  is_python_3 - checks if python 3.0 or later is available
16  is_sqlite_available - checks if the sqlite3 module is available
17  is_crypto_available - checks if the cryptography module is available
18  is_zstd_available - checks if the zstd module is available
19  is_lzma_available - checks if the lzma module is available
20  is_mock_available - checks if the mock module is available
21"""
22
23import functools
24import hashlib
25import inspect
26import platform
27import sys
28
29# TODO: in stem 2.x consider replacing these functions with requirement
30# annotations (like our tests)
31
32CRYPTO_UNAVAILABLE = "Unable to import the cryptography module. Because of this we'll be unable to verify descriptor signature integrity. You can get cryptography from: https://pypi.org/project/cryptography/"
33ZSTD_UNAVAILABLE = 'ZSTD compression requires the zstandard module (https://pypi.org/project/zstandard/)'
34LZMA_UNAVAILABLE = 'LZMA compression requires the lzma module (https://docs.python.org/3/library/lzma.html)'
35ED25519_UNSUPPORTED = 'Unable to verify descriptor ed25519 certificate integrity. ed25519 is not supported by installed versions of OpenSSL and/or cryptography'
36
37
38def check_requirements():
39  """
40  Checks that we meet the minimum requirements to run stem. If we don't then
41  this raises an ImportError with the issue.
42
43  :raises: **ImportError** with the problem if we don't meet stem's
44    requirements
45  """
46
47  major_version, minor_version = sys.version_info[0:2]
48
49  if major_version < 2 or (major_version == 2 and minor_version < 6):
50    raise ImportError('stem requires python version 2.6 or greater')
51
52
53def _is_python_26():
54  """
55  Checks if we're running python 2.6. This isn't for users as it'll be removed
56  in stem 2.0 (when python 2.6 support goes away).
57
58  .. deprecated:: 1.8.0
59     Stem 2.x will remove this method along with Python 2.x support.
60
61  :returns: **True** if we're running python 2.6, **False** otherwise
62  """
63
64  major_version, minor_version = sys.version_info[0:2]
65
66  return major_version == 2 and minor_version == 6
67
68
69def is_python_27():
70  """
71  Checks if we're running python 2.7 or above (including the 3.x series).
72
73  .. deprecated:: 1.5.0
74     Stem 2.x will remove this method along with Python 2.x support.
75
76  :returns: **True** if we meet this requirement and **False** otherwise
77  """
78
79  major_version, minor_version = sys.version_info[0:2]
80
81  return major_version > 2 or (major_version == 2 and minor_version >= 7)
82
83
84def is_python_3():
85  """
86  Checks if we're in the 3.0 - 3.x range.
87
88  .. deprecated:: 1.8.0
89     Stem 2.x will remove this method along with Python 2.x support.
90
91  :returns: **True** if we meet this requirement and **False** otherwise
92  """
93
94  return sys.version_info[0] == 3
95
96
97def is_pypy():
98  """
99  Checks if we're running PyPy.
100
101  .. versionadded:: 1.7.0
102
103  :returns: **True** if running pypy, **False** otherwise
104  """
105
106  return platform.python_implementation() == 'PyPy'
107
108
109def is_sqlite_available():
110  """
111  Checks if the sqlite3 module is available. Usually this is built in, but some
112  platforms such as FreeBSD and Gentoo exclude it by default.
113
114  .. versionadded:: 1.6.0
115
116  :returns: **True** if we can use the sqlite3 module and **False** otherwise
117  """
118
119  try:
120    import sqlite3
121    return True
122  except ImportError:
123    return False
124
125
126def is_crypto_available(ed25519 = False):
127  """
128  Checks if the cryptography functions we use are available. This is used for
129  verifying relay descriptor signatures.
130
131  :param bool ed25519: check for `ed25519 support
132    <https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/>`_,
133    which requires both cryptography version 2.6 and OpenSSL support
134
135  :returns: **True** if we can use the cryptography module and **False**
136    otherwise
137  """
138
139  from stem.util import log
140
141  try:
142    from cryptography.utils import int_from_bytes, int_to_bytes
143    from cryptography.hazmat.backends import default_backend
144    from cryptography.hazmat.backends.openssl.backend import backend
145    from cryptography.hazmat.primitives.asymmetric import rsa
146    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
147    from cryptography.hazmat.primitives.serialization import load_der_public_key
148
149    if not hasattr(rsa.RSAPrivateKey, 'sign'):
150      raise ImportError()
151
152    if ed25519:
153      # The following import confirms cryptography support (ie. version 2.6+),
154      # whereas ed25519_supported() checks for OpenSSL bindings.
155
156      from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
157
158      if not hasattr(backend, 'ed25519_supported') or not backend.ed25519_supported():
159        log.log_once('stem.prereq._is_crypto_ed25519_supported', log.INFO, ED25519_UNSUPPORTED)
160        return False
161
162    return True
163  except ImportError:
164    log.log_once('stem.prereq.is_crypto_available', log.INFO, CRYPTO_UNAVAILABLE)
165    return False
166
167
168def is_zstd_available():
169  """
170  Checks if the `zstd module <https://pypi.org/project/zstandard/>`_ is
171  available.
172
173  .. versionadded:: 1.7.0
174
175  :returns: **True** if we can use the zstd module and **False** otherwise
176  """
177
178  try:
179    # Unfortunately the zstandard module uses the same namespace as another
180    # zstd module (https://pypi.org/project/zstd/), so we need to
181    # differentiate them.
182
183    import zstd
184    return hasattr(zstd, 'ZstdDecompressor')
185  except ImportError:
186    from stem.util import log
187    log.log_once('stem.prereq.is_zstd_available', log.INFO, ZSTD_UNAVAILABLE)
188    return False
189
190
191def is_lzma_available():
192  """
193  Checks if the `lzma module <https://docs.python.org/3/library/lzma.html>`_ is
194  available. This was added as a builtin in Python 3.3.
195
196  .. versionadded:: 1.7.0
197
198  :returns: **True** if we can use the lzma module and **False** otherwise
199  """
200
201  try:
202    import lzma
203    return True
204  except ImportError:
205    from stem.util import log
206    log.log_once('stem.prereq.is_lzma_available', log.INFO, LZMA_UNAVAILABLE)
207    return False
208
209
210def is_mock_available():
211  """
212  Checks if the mock module is available. In python 3.3 and up it is a builtin
213  unittest module, but before this it needed to be `installed separately
214  <https://pypi.org/project/mock/>`_. Imports should be as follows....
215
216  ::
217
218    try:
219      # added in python 3.3
220      from unittest.mock import Mock
221    except ImportError:
222      from mock import Mock
223
224  :returns: **True** if the mock module is available and **False** otherwise
225  """
226
227  try:
228    # checks for python 3.3 version
229    import unittest.mock
230    return True
231  except ImportError:
232    pass
233
234  try:
235    import mock
236
237    # check for mock's patch.dict() which was introduced in version 0.7.0
238
239    if not hasattr(mock.patch, 'dict'):
240      raise ImportError()
241
242    # check for mock's new_callable argument for patch() which was introduced in version 0.8.0
243
244    if 'new_callable' not in inspect.getargspec(mock.patch).args:
245      raise ImportError()
246
247    return True
248  except ImportError:
249    return False
250
251
252def _is_lru_cache_available():
253  """
254  Functools added lru_cache to the standard library in Python 3.2. Prior to
255  this using a bundled implementation. We're also using this with Python 3.5
256  due to a buggy implementation. (:trac:`26412`)
257  """
258
259  major_version, minor_version = sys.version_info[0:2]
260
261  if major_version == 3 and minor_version == 5:
262    return False
263  else:
264    return hasattr(functools, 'lru_cache')
265
266
267def _is_sha3_available():
268  """
269  Check if hashlib has sha3 support. This requires Python 3.6+ *or* the `pysha3
270  module <https://github.com/tiran/pysha3>`_.
271  """
272
273  # If pysha3 is present then importing sha3 will monkey patch the methods we
274  # want onto hashlib.
275
276  if not hasattr(hashlib, 'sha3_256') or not hasattr(hashlib, 'shake_256'):
277    try:
278      import sha3
279    except ImportError:
280      pass
281
282  return hasattr(hashlib, 'sha3_256') and hasattr(hashlib, 'shake_256')
283