1#! /usr/bin/env python
2#
3#  setup.py : Distutils setup script
4#
5# ===================================================================
6# The contents of this file are dedicated to the public domain.  To
7# the extent that dedication to the public domain is not available,
8# everyone is granted a worldwide, perpetual, royalty-free,
9# non-exclusive license to exercise all rights associated with the
10# contents of this file for any purpose whatsoever.
11# No rights are reserved.
12#
13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
17# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
18# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20# SOFTWARE.
21# ===================================================================
22
23from __future__ import print_function
24
25try:
26    from setuptools import Extension, Command, setup
27    from setuptools.command.build_ext import build_ext
28except ImportError:
29    from distutils.core import Extension, Command, setup
30    from distutils.command.build_ext import build_ext
31
32from distutils.command.build_py import build_py
33import re
34import os
35import sys
36import shutil
37import struct
38
39from compiler_opt import set_compiler_options
40
41
42use_separate_namespace = os.path.isfile(".separate_namespace")
43
44project_name = "pycryptodome"
45package_root = "Crypto"
46other_project = "pycryptodomex"
47other_root = "Cryptodome"
48
49if use_separate_namespace:
50    project_name, other_project = other_project, project_name
51    package_root, other_root = other_root, package_root
52
53longdesc = """
54PyCryptodome
55============
56
57PyCryptodome is a self-contained Python package of low-level
58cryptographic primitives.
59
60It supports Python 2.7, Python 3.5 and newer, and PyPy.
61
62You can install it with::
63
64    pip install THIS_PROJECT
65
66All modules are installed under the ``THIS_ROOT`` package.
67
68Check the OTHER_PROJECT_ project for the equivalent library that
69works under the ``OTHER_ROOT`` package.
70
71PyCryptodome is a fork of PyCrypto. It brings several enhancements
72with respect to the last official version of PyCrypto (2.6.1),
73for instance:
74
75* Authenticated encryption modes (GCM, CCM, EAX, SIV, OCB)
76* Accelerated AES on Intel platforms via AES-NI
77* First class support for PyPy
78* Elliptic curves cryptography (NIST P-256, P-384 and P-521 curves only)
79* Better and more compact API (`nonce` and `iv` attributes for ciphers,
80  automatic generation of random nonces and IVs, simplified CTR cipher mode,
81  and more)
82* SHA-3 (including SHAKE XOFs) and BLAKE2 hash algorithms
83* Salsa20 and ChaCha20 stream ciphers
84* scrypt and HKDF
85* Deterministic (EC)DSA
86* Password-protected PKCS#8 key containers
87* Shamir's Secret Sharing scheme
88* Random numbers get sourced directly from the OS (and not from a CSPRNG in userspace)
89* Simplified install process, including better support for Windows
90* Cleaner RSA and DSA key generation (largely based on FIPS 186-4)
91* Major clean ups and simplification of the code base
92
93PyCryptodome is not a wrapper to a separate C library like *OpenSSL*.
94To the largest possible extent, algorithms are implemented in pure Python.
95Only the pieces that are extremely critical to performance (e.g. block ciphers)
96are implemented as C extensions.
97
98For more information, see the `homepage`_.
99
100All the code can be downloaded from `GitHub`_.
101
102.. _OTHER_PROJECT: https://pypi.python.org/pypi/OTHER_PROJECT
103.. _`homepage`: http://www.pycryptodome.org
104.. _GitHub: https://github.com/Legrandin/pycryptodome
105""".replace("THIS_PROJECT", project_name).\
106    replace("THIS_ROOT", package_root).\
107    replace("OTHER_PROJECT", other_project).\
108    replace("OTHER_ROOT", other_root)
109
110
111class PCTBuildExt (build_ext):
112
113    # Avoid linking Python's dynamic library
114    def get_libraries(self, ext):
115        return []
116
117
118class PCTBuildPy(build_py):
119    def find_package_modules(self, package, package_dir, *args, **kwargs):
120        modules = build_py.find_package_modules(self, package, package_dir,
121                                                *args, **kwargs)
122
123        # Exclude certain modules
124        retval = []
125        for item in modules:
126            pkg, module = item[:2]
127            retval.append(item)
128        return retval
129
130
131class TestCommand(Command):
132    "Run self-test"
133
134    # Long option name, short option name, description
135    user_options = [
136        ('skip-slow-tests', None, 'Skip slow tests'),
137        ('wycheproof-warnings', None, 'Show warnings from wycheproof tests'),
138        ('module=', 'm', 'Test a single module (e.g. Cipher, PublicKey)'),
139    ]
140
141    def initialize_options(self):
142        self.build_dir = None
143        self.skip_slow_tests = None
144        self.wycheproof_warnings = None
145        self.module = None
146
147    def finalize_options(self):
148        self.set_undefined_options('install', ('build_lib', 'build_dir'))
149        self.config = {'slow_tests': not self.skip_slow_tests,
150                       'wycheproof_warnings': self.wycheproof_warnings}
151
152    def run(self):
153        # Run sub commands
154        for cmd_name in self.get_sub_commands():
155            self.run_command(cmd_name)
156
157        # Run SelfTest
158        old_path = sys.path[:]
159        self.announce("running self-tests on " + package_root)
160        try:
161            sys.path.insert(0, self.build_dir)
162
163            if use_separate_namespace:
164                from Cryptodome import SelfTest
165                from Cryptodome.Math import Numbers
166            else:
167                from Crypto import SelfTest
168                from Crypto.Math import Numbers
169
170            moduleObj = None
171            if self.module:
172                if self.module.count('.') == 0:
173                    # Test a whole a sub-package
174                    full_module = package_root + ".SelfTest." + self.module
175                    module_name = self.module
176                else:
177                    # Test only a module
178                    # Assume only one dot is present
179                    comps = self.module.split('.')
180                    module_name = "test_" + comps[1]
181                    full_module = package_root + ".SelfTest." + comps[0] + "." + module_name
182                # Import sub-package or module
183                moduleObj = __import__(full_module, globals(), locals(), module_name)
184
185            print(package_root + ".Math implementation:",
186                     str(Numbers._implementation))
187
188            SelfTest.run(module=moduleObj, verbosity=self.verbose, stream=sys.stdout, config=self.config)
189        finally:
190            # Restore sys.path
191            sys.path[:] = old_path
192
193        # Run slower self-tests
194        self.announce("running extended self-tests")
195
196    sub_commands = [('build', None)]
197
198
199def create_cryptodome_lib():
200    assert os.path.isdir("lib/Crypto")
201
202    try:
203        shutil.rmtree("lib/Cryptodome")
204    except OSError:
205        pass
206    for root_src, dirs, files in os.walk("lib/Crypto"):
207
208        root_dst, nr_repl = re.subn('Crypto', 'Cryptodome', root_src)
209        assert nr_repl == 1
210
211        for dir_name in dirs:
212            full_dir_name_dst = os.path.join(root_dst, dir_name)
213            if not os.path.exists(full_dir_name_dst):
214                os.makedirs(full_dir_name_dst)
215
216        for file_name in files:
217            full_file_name_src = os.path.join(root_src, file_name)
218            full_file_name_dst = os.path.join(root_dst, file_name)
219
220            print("Copying file %s to %s" % (full_file_name_src, full_file_name_dst))
221            shutil.copy2(full_file_name_src, full_file_name_dst)
222
223            if full_file_name_src.split(".")[-1] not in ("py", "pyi"):
224                if full_file_name_src != "py.typed":
225                    continue
226
227            if sys.version_info[0] > 2:
228                extra_param = { "encoding": "utf-8" }
229            else:
230                extra_param = {}
231            with open(full_file_name_dst, "rt", **extra_param) as fd:
232                content = (fd.read().
233                           replace("Crypto.", "Cryptodome.").
234                           replace("Crypto ", "Cryptodome ").
235                           replace("'Crypto'", "'Cryptodome'").
236                           replace('"Crypto"', '"Cryptodome"'))
237            os.remove(full_file_name_dst)
238            with open(full_file_name_dst, "wt", **extra_param) as fd:
239                fd.write(content)
240
241
242# Parameters for setup
243packages =  [
244    "Crypto",
245    "Crypto.Cipher",
246    "Crypto.Hash",
247    "Crypto.IO",
248    "Crypto.PublicKey",
249    "Crypto.Protocol",
250    "Crypto.Random",
251    "Crypto.Signature",
252    "Crypto.Util",
253    "Crypto.Math",
254    "Crypto.SelfTest",
255    "Crypto.SelfTest.Cipher",
256    "Crypto.SelfTest.Hash",
257    "Crypto.SelfTest.IO",
258    "Crypto.SelfTest.Protocol",
259    "Crypto.SelfTest.PublicKey",
260    "Crypto.SelfTest.Random",
261    "Crypto.SelfTest.Signature",
262    "Crypto.SelfTest.Util",
263    "Crypto.SelfTest.Math",
264]
265package_data = {
266    "Crypto" : [ "py.typed", "*.pyi" ],
267    "Crypto.Cipher" : [ "*.pyi" ],
268    "Crypto.Hash" : [ "*.pyi" ],
269    "Crypto.Math" : [ "*.pyi" ],
270    "Crypto.Protocol" : [ "*.pyi" ],
271    "Crypto.PublicKey" : [ "*.pyi" ],
272    "Crypto.Random" : [ "*.pyi" ],
273    "Crypto.Signature" : [ "*.pyi" ],
274    "Crypto.IO" : [ "*.pyi" ],
275    "Crypto.Util" : [ "*.pyi" ],
276}
277
278ext_modules = [
279    # Hash functions
280    Extension("Crypto.Hash._MD2",
281        include_dirs=['src/'],
282        sources=["src/MD2.c"],
283        py_limited_api=True),
284    Extension("Crypto.Hash._MD4",
285        include_dirs=['src/'],
286        sources=["src/MD4.c"],
287        py_limited_api=True),
288    Extension("Crypto.Hash._MD5",
289        include_dirs=['src/'],
290        sources=["src/MD5.c"],
291        py_limited_api=True),
292    Extension("Crypto.Hash._SHA1",
293        include_dirs=['src/'],
294        sources=["src/SHA1.c"],
295        py_limited_api=True),
296    Extension("Crypto.Hash._SHA256",
297        include_dirs=['src/'],
298        sources=["src/SHA256.c"],
299        py_limited_api=True),
300    Extension("Crypto.Hash._SHA224",
301        include_dirs=['src/'],
302        sources=["src/SHA224.c"],
303        py_limited_api=True),
304    Extension("Crypto.Hash._SHA384",
305        include_dirs=['src/'],
306        sources=["src/SHA384.c"],
307        py_limited_api=True),
308    Extension("Crypto.Hash._SHA512",
309        include_dirs=['src/'],
310        sources=["src/SHA512.c"],
311        py_limited_api=True),
312    Extension("Crypto.Hash._RIPEMD160",
313        include_dirs=['src/'],
314        sources=["src/RIPEMD160.c"],
315        py_limited_api=True),
316    Extension("Crypto.Hash._keccak",
317        include_dirs=['src/'],
318        sources=["src/keccak.c"],
319        py_limited_api=True),
320    Extension("Crypto.Hash._BLAKE2b",
321        include_dirs=['src/'],
322        sources=["src/blake2b.c"],
323        py_limited_api=True),
324    Extension("Crypto.Hash._BLAKE2s",
325        include_dirs=['src/'],
326        sources=["src/blake2s.c"],
327        py_limited_api=True),
328    Extension("Crypto.Hash._ghash_portable",
329        include_dirs=['src/'],
330        sources=['src/ghash_portable.c'],
331        py_limited_api=True),
332    Extension("Crypto.Hash._ghash_clmul",
333        include_dirs=['src/'],
334        sources=['src/ghash_clmul.c'],
335        py_limited_api=True),
336
337    # MACs
338    Extension("Crypto.Hash._poly1305",
339        include_dirs=['src/'],
340        sources=["src/poly1305.c"],
341        py_limited_api=True),
342
343    # Block encryption algorithms
344    Extension("Crypto.Cipher._raw_aes",
345        include_dirs=['src/'],
346        sources=["src/AES.c"],
347        py_limited_api=True),
348    Extension("Crypto.Cipher._raw_aesni",
349        include_dirs=['src/'],
350        sources=["src/AESNI.c"],
351        py_limited_api=True),
352    Extension("Crypto.Cipher._raw_arc2",
353        include_dirs=['src/'],
354        sources=["src/ARC2.c"],
355        py_limited_api=True),
356    Extension("Crypto.Cipher._raw_blowfish",
357        include_dirs=['src/'],
358        sources=["src/blowfish.c"],
359        py_limited_api=True),
360    Extension("Crypto.Cipher._raw_eksblowfish",
361        include_dirs=['src/'],
362        define_macros=[('EKS',None),],
363        sources=["src/blowfish.c"],
364        py_limited_api=True),
365    Extension("Crypto.Cipher._raw_cast",
366        include_dirs=['src/'],
367        sources=["src/CAST.c"],
368        py_limited_api=True),
369    Extension("Crypto.Cipher._raw_des",
370        include_dirs=['src/', 'src/libtom/'],
371        sources=["src/DES.c"],
372        py_limited_api=True),
373    Extension("Crypto.Cipher._raw_des3",
374        include_dirs=['src/', 'src/libtom/'],
375        sources=["src/DES3.c"],
376        py_limited_api=True),
377    Extension("Crypto.Util._cpuid_c",
378        include_dirs=['src/'],
379        sources=['src/cpuid.c'],
380        py_limited_api=True),
381
382    # Chaining modes
383    Extension("Crypto.Cipher._raw_ecb",
384        include_dirs=['src/'],
385        sources=["src/raw_ecb.c"],
386        py_limited_api=True),
387    Extension("Crypto.Cipher._raw_cbc",
388        include_dirs=['src/'],
389        sources=["src/raw_cbc.c"],
390        py_limited_api=True),
391    Extension("Crypto.Cipher._raw_cfb",
392        include_dirs=['src/'],
393        sources=["src/raw_cfb.c"],
394        py_limited_api=True),
395    Extension("Crypto.Cipher._raw_ofb",
396        include_dirs=['src/'],
397        sources=["src/raw_ofb.c"],
398        py_limited_api=True),
399    Extension("Crypto.Cipher._raw_ctr",
400        include_dirs=['src/'],
401        sources=["src/raw_ctr.c"],
402        py_limited_api=True),
403    Extension("Crypto.Cipher._raw_ocb",
404        sources=["src/raw_ocb.c"],
405        py_limited_api=True),
406
407    # Stream ciphers
408    Extension("Crypto.Cipher._ARC4",
409        include_dirs=['src/'],
410        sources=["src/ARC4.c"],
411        py_limited_api=True),
412    Extension("Crypto.Cipher._Salsa20",
413        include_dirs=['src/', 'src/libtom/'],
414        sources=["src/Salsa20.c"],
415        py_limited_api=True),
416    Extension("Crypto.Cipher._chacha20",
417        include_dirs=['src/'],
418        sources=["src/chacha20.c"],
419        py_limited_api=True),
420
421    # Others
422    Extension("Crypto.Protocol._scrypt",
423        include_dirs=['src/'],
424        sources=["src/scrypt.c"],
425        py_limited_api=True),
426
427    # Utility modules
428    Extension("Crypto.Util._strxor",
429        include_dirs=['src/'],
430        sources=['src/strxor.c'],
431        py_limited_api=True),
432
433    # ECC
434    Extension("Crypto.PublicKey._ec_ws",
435        include_dirs=['src/'],
436        sources=['src/modexp_utils.c', 'src/siphash.c', 'src/ec_ws.c',
437                 'src/mont.c', 'src/p256_table.c', 'src/p384_table.c',
438                 'src/p521_table.c'],
439        py_limited_api=True,
440        ),
441
442    # Math
443    Extension("Crypto.Math._modexp",
444        include_dirs=['src/'],
445        sources=['src/modexp.c', 'src/siphash.c', 'src/modexp_utils.c', 'src/mont.c'],
446        py_limited_api=True,
447        ),
448]
449
450if use_separate_namespace:
451
452    # Fix-up setup information
453    for i in range(len(packages)):
454        packages[i] = packages[i].replace("Crypto", "Cryptodome")
455    new_package_data = {}
456    for k, v in package_data.items():
457        new_package_data[k.replace("Crypto", "Cryptodome")] = v
458    package_data = new_package_data
459    for ext in ext_modules:
460        ext.name = ext.name.replace("Crypto", "Cryptodome")
461
462    # Recreate lib/Cryptodome from scratch, unless it is the only
463    # directory available
464    if os.path.isdir("lib/Crypto"):
465        create_cryptodome_lib()
466
467# Add compiler specific options.
468set_compiler_options(package_root, ext_modules)
469
470# By doing this we need to change version information in a single file
471with open(os.path.join("lib", package_root, "__init__.py")) as init_root:
472    for line in init_root:
473        if line.startswith("version_info"):
474            version_tuple = eval(line.split("=")[1])
475
476version_string = ".".join([str(x) for x in version_tuple])
477
478setup(
479    name=project_name,
480    version=version_string,
481    description="Cryptographic library for Python",
482    long_description=longdesc,
483    author="Helder Eijs",
484    author_email="helderijs@gmail.com",
485    url="https://www.pycryptodome.org",
486    platforms='Posix; MacOS X; Windows',
487    zip_safe=False,
488    python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
489    classifiers=[
490        'Development Status :: 5 - Production/Stable',
491        'License :: OSI Approved :: BSD License',
492        'License :: OSI Approved :: Apache Software License',
493        'License :: Public Domain',
494        'Intended Audience :: Developers',
495        'Operating System :: Unix',
496        'Operating System :: Microsoft :: Windows',
497        'Operating System :: MacOS :: MacOS X',
498        'Topic :: Security :: Cryptography',
499        'Programming Language :: Python :: 2',
500        'Programming Language :: Python :: 2.7',
501        'Programming Language :: Python :: 3',
502        'Programming Language :: Python :: 3.5',
503        'Programming Language :: Python :: 3.6',
504        'Programming Language :: Python :: 3.7',
505        'Programming Language :: Python :: 3.8',
506        'Programming Language :: Python :: 3.9',
507    ],
508    license="BSD, Public Domain",
509    packages=packages,
510    package_dir={"": "lib"},
511    package_data=package_data,
512    cmdclass={
513        'build_ext': PCTBuildExt,
514        'build_py': PCTBuildPy,
515        'test': TestCommand,
516        },
517    ext_modules=ext_modules,
518)
519