diff --git a/MANIFEST.in b/MANIFEST.in index 521c312..ea1c0a0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include LICENSE README.rst -recursive-include bcrypt/crypt_blowfish-1.3 * +include src/build_bcrypt.py +recursive-include src/crypt_blowfish-1.3 * diff --git a/bcrypt/__init__.py b/bcrypt/__init__.py deleted file mode 100644 index 29a4701..0000000 --- a/bcrypt/__init__.py +++ /dev/null @@ -1,150 +0,0 @@ -# Author:: Donald Stufft () -# Copyright:: Copyright (c) 2013 Donald Stufft -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -from __future__ import absolute_import -from __future__ import division - -import binascii -import os -import sys -import threading - -from cffi import FFI -from cffi.verifier import Verifier - -import six - -from .__about__ import ( - __author__, __copyright__, __email__, __license__, __summary__, __title__, - __uri__, __version__, -) - - -__all__ = [ - "__title__", "__summary__", "__uri__", "__version__", "__author__", - "__email__", "__license__", "__copyright__", - "gensalt", "hashpw", -] - - -def _create_modulename(cdef_sources, source, sys_version): - """ - This is the same as CFFI's create modulename except we don't include the - CFFI version. - """ - key = '\x00'.join([sys_version[:3], source, cdef_sources]) - key = key.encode('utf-8') - k1 = hex(binascii.crc32(key[0::2]) & 0xffffffff) - k1 = k1.lstrip('0x').rstrip('L') - k2 = hex(binascii.crc32(key[1::2]) & 0xffffffff) - k2 = k2.lstrip('0').rstrip('L') - return '_bcrypt_cffi_{0}{1}'.format(k1, k2) - - -def _compile_module(*args, **kwargs): - raise RuntimeError( - "Attempted implicit compile of a cffi module. All cffi modules should " - "be pre-compiled at installation time." - ) - - -class LazyLibrary(object): - def __init__(self, ffi): - self._ffi = ffi - self._lib = None - self._lock = threading.Lock() - - def __getattr__(self, name): - if self._lib is None: - with self._lock: - # We no cover this because this guard is here just to protect - # against concurrent loads of this library. This is pretty - # hard to test and the logic is simple and should hold fine - # without testing (famous last words). - if self._lib is None: # pragma: no cover - self._lib = self._ffi.verifier.load_library() - - return getattr(self._lib, name) - - -_crypt_blowfish_dir = "crypt_blowfish-1.3" -_bundled_dir = os.path.join(os.path.dirname(__file__), _crypt_blowfish_dir) - - -CDEF = """ - char *crypt_gensalt_rn(const char *prefix, unsigned long count, - const char *input, int size, char *output, int output_size); - - char *crypt_rn(const char *key, const char *setting, void *data, int size); -""" - -SOURCE = """ - #include "ow-crypt.h" -""" - -_ffi = FFI() -_ffi.cdef(CDEF) -_ffi.verifier = Verifier( - _ffi, - SOURCE, - sources=[ - str(os.path.join(_bundled_dir, "crypt_blowfish.c")), - str(os.path.join(_bundled_dir, "crypt_gensalt.c")), - str(os.path.join(_bundled_dir, "wrapper.c")), - # How can we get distutils to work with a .S file? - # Set bcrypt/crypt_blowfish-1.3/crypt_blowfish.c#57 back to 1 if we - # get ASM loaded. - # str(os.path.join(_bundled_dir, "x86.S")), - ], - include_dirs=[str(_bundled_dir)], - modulename=_create_modulename(CDEF, SOURCE, sys.version), -) - -# Patch the Verifier() instance to prevent CFFI from compiling the module -_ffi.verifier.compile_module = _compile_module -_ffi.verifier._compile_module = _compile_module - - -_bcrypt_lib = LazyLibrary(_ffi) - - -def gensalt(rounds=12): - salt = os.urandom(16) - output = _ffi.new("unsigned char[]", 30) - - retval = _bcrypt_lib.crypt_gensalt_rn( - b"$2a$", rounds, salt, len(salt), output, len(output), - ) - - if not retval: - raise ValueError("Invalid rounds") - - return _ffi.string(output) - - -def hashpw(password, salt): - if isinstance(password, six.text_type) or isinstance(salt, six.text_type): - raise TypeError("Unicode-objects must be encoded before hashing") - - if b"\x00" in password: - raise ValueError("password may not contain NUL bytes") - - hashed = _ffi.new("unsigned char[]", 128) - retval = _bcrypt_lib.crypt_rn(password, salt, hashed, len(hashed)) - - if not retval: - raise ValueError("Invalid salt") - - return _ffi.string(hashed) diff --git a/setup.py b/setup.py index 370a753..f635d94 100644 --- a/setup.py +++ b/setup.py @@ -7,48 +7,21 @@ from setuptools.command.install import install from setuptools.command.test import test -CFFI_DEPENDENCY = "cffi>=0.8" +CFFI_DEPENDENCY = "cffi>=1.1" SIX_DEPENDENCY = "six>=1.4.1" +CFFI_MODULES = [ + "src/build_bcrypt.py:ffi", +] + + # Manually extract the __about__ __about__ = {} -with open("bcrypt/__about__.py") as fp: +with open("src/bcrypt/__about__.py") as fp: exec(fp.read(), __about__) -def get_ext_modules(): - from bcrypt import _ffi - return [_ffi.verifier.get_extension()] - - -class CFFIBuild(build): - """ - This class exists, instead of just providing ``ext_modules=[...]`` directly - in ``setup()`` because importing cryptography requires we have several - packages installed first. - - By doing the imports here we ensure that packages listed in - ``setup_requires`` are already installed. - """ - - def finalize_options(self): - self.distribution.ext_modules = get_ext_modules() - build.finalize_options(self) - - -class CFFIInstall(install): - """ - As a consequence of CFFIBuild and it's late addition of ext_modules, we - need the equivalent for the ``install`` command to install into platlib - install-dir rather than purelib. - """ - - def finalize_options(self): - self.distribution.ext_modules = get_ext_modules() - install.finalize_options(self) - - class PyTest(test): def finalize_options(self): test.finalize_options(self) @@ -159,12 +132,11 @@ def keywords_with_side_effects(argv): } else: return { - "setup_requires": [CFFI_DEPENDENCY, SIX_DEPENDENCY], + "setup_requires": [CFFI_DEPENDENCY], "cmdclass": { - "build": CFFIBuild, - "install": CFFIInstall, "test": PyTest, - } + }, + "cffi_modules": CFFI_MODULES, } @@ -231,14 +203,11 @@ setup( "pytest", ], + package_dir={"": "src"}, packages=[ "bcrypt", ], - package_data={ - "bcrypt": ["crypt_blowfish-1.3/*"], - }, - zip_safe=False, classifiers=[ @@ -252,5 +221,7 @@ setup( "Programming Language :: Python :: 3.3", ], + ext_package="bcrypt", + **keywords_with_side_effects(sys.argv) ) diff --git a/bcrypt/__about__.py b/src/bcrypt/__about__.py similarity index 100% rename from bcrypt/__about__.py rename to src/bcrypt/__about__.py diff --git a/src/bcrypt/__init__.py b/src/bcrypt/__init__.py new file mode 100644 index 0000000..10f860b --- /dev/null +++ b/src/bcrypt/__init__.py @@ -0,0 +1,65 @@ +# Author:: Donald Stufft () +# Copyright:: Copyright (c) 2013 Donald Stufft +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import +from __future__ import division + +import os + +import six + +from bcrypt import _bcrypt + +from .__about__ import ( + __author__, __copyright__, __email__, __license__, __summary__, __title__, + __uri__, __version__, +) + + +__all__ = [ + "__title__", "__summary__", "__uri__", "__version__", "__author__", + "__email__", "__license__", "__copyright__", + "gensalt", "hashpw", +] + + +def gensalt(rounds=12): + salt = os.urandom(16) + output = _bcrypt.ffi.new("unsigned char[]", 30) + + retval = _bcrypt.lib.crypt_gensalt_rn( + b"$2a$", rounds, salt, len(salt), output, len(output), + ) + + if not retval: + raise ValueError("Invalid rounds") + + return _bcrypt.ffi.string(output) + + +def hashpw(password, salt): + if isinstance(password, six.text_type) or isinstance(salt, six.text_type): + raise TypeError("Unicode-objects must be encoded before hashing") + + if b"\x00" in password: + raise ValueError("password may not contain NUL bytes") + + hashed = _bcrypt.ffi.new("unsigned char[]", 128) + retval = _bcrypt.lib.crypt_rn(password, salt, hashed, len(hashed)) + + if not retval: + raise ValueError("Invalid salt") + + return _bcrypt.ffi.string(hashed) diff --git a/src/build_bcrypt.py b/src/build_bcrypt.py new file mode 100644 index 0000000..b468c08 --- /dev/null +++ b/src/build_bcrypt.py @@ -0,0 +1,47 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os.path + +from cffi import FFI + + +BLOWFISH_DIR = os.path.join(os.path.dirname(__file__), "crypt_blowfish-1.3") + + +ffi = FFI() + +ffi.cdef( + """ + char *crypt_gensalt_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size); + + char *crypt_rn(const char *key, const char *setting, void *data, int size); + """ +) + +ffi.set_source( + "_bcrypt", + """ + #include "ow-crypt.h" + """, + sources=[ + os.path.join(BLOWFISH_DIR, "crypt_blowfish.c"), + os.path.join(BLOWFISH_DIR, "crypt_gensalt.c"), + os.path.join(BLOWFISH_DIR, "wrapper.c"), + # How can we get distutils to work with a .S file? + # Set bcrypt/crypt_blowfish-1.3/crypt_blowfish.c#57 back to 1 if we + # get ASM loaded. + # os.path.join(BLOWFISH_DIR, "x86.S"), + ], + include_dirs=[BLOWFISH_DIR], +) diff --git a/bcrypt/crypt_blowfish-1.3/LINKS b/src/crypt_blowfish-1.3/LINKS similarity index 100% rename from bcrypt/crypt_blowfish-1.3/LINKS rename to src/crypt_blowfish-1.3/LINKS diff --git a/bcrypt/crypt_blowfish-1.3/Makefile b/src/crypt_blowfish-1.3/Makefile similarity index 100% rename from bcrypt/crypt_blowfish-1.3/Makefile rename to src/crypt_blowfish-1.3/Makefile diff --git a/bcrypt/crypt_blowfish-1.3/PERFORMANCE b/src/crypt_blowfish-1.3/PERFORMANCE similarity index 100% rename from bcrypt/crypt_blowfish-1.3/PERFORMANCE rename to src/crypt_blowfish-1.3/PERFORMANCE diff --git a/bcrypt/crypt_blowfish-1.3/README b/src/crypt_blowfish-1.3/README similarity index 100% rename from bcrypt/crypt_blowfish-1.3/README rename to src/crypt_blowfish-1.3/README diff --git a/bcrypt/crypt_blowfish-1.3/crypt.3 b/src/crypt_blowfish-1.3/crypt.3 similarity index 100% rename from bcrypt/crypt_blowfish-1.3/crypt.3 rename to src/crypt_blowfish-1.3/crypt.3 diff --git a/bcrypt/crypt_blowfish-1.3/crypt.h b/src/crypt_blowfish-1.3/crypt.h similarity index 100% rename from bcrypt/crypt_blowfish-1.3/crypt.h rename to src/crypt_blowfish-1.3/crypt.h diff --git a/bcrypt/crypt_blowfish-1.3/crypt_blowfish.c b/src/crypt_blowfish-1.3/crypt_blowfish.c similarity index 100% rename from bcrypt/crypt_blowfish-1.3/crypt_blowfish.c rename to src/crypt_blowfish-1.3/crypt_blowfish.c diff --git a/bcrypt/crypt_blowfish-1.3/crypt_blowfish.h b/src/crypt_blowfish-1.3/crypt_blowfish.h similarity index 100% rename from bcrypt/crypt_blowfish-1.3/crypt_blowfish.h rename to src/crypt_blowfish-1.3/crypt_blowfish.h diff --git a/bcrypt/crypt_blowfish-1.3/crypt_gensalt.c b/src/crypt_blowfish-1.3/crypt_gensalt.c similarity index 100% rename from bcrypt/crypt_blowfish-1.3/crypt_gensalt.c rename to src/crypt_blowfish-1.3/crypt_gensalt.c diff --git a/bcrypt/crypt_blowfish-1.3/crypt_gensalt.h b/src/crypt_blowfish-1.3/crypt_gensalt.h similarity index 100% rename from bcrypt/crypt_blowfish-1.3/crypt_gensalt.h rename to src/crypt_blowfish-1.3/crypt_gensalt.h diff --git a/bcrypt/crypt_blowfish-1.3/glibc-2.1.3-crypt.diff b/src/crypt_blowfish-1.3/glibc-2.1.3-crypt.diff similarity index 100% rename from bcrypt/crypt_blowfish-1.3/glibc-2.1.3-crypt.diff rename to src/crypt_blowfish-1.3/glibc-2.1.3-crypt.diff diff --git a/bcrypt/crypt_blowfish-1.3/glibc-2.14-crypt.diff b/src/crypt_blowfish-1.3/glibc-2.14-crypt.diff similarity index 100% rename from bcrypt/crypt_blowfish-1.3/glibc-2.14-crypt.diff rename to src/crypt_blowfish-1.3/glibc-2.14-crypt.diff diff --git a/bcrypt/crypt_blowfish-1.3/glibc-2.3.6-crypt.diff b/src/crypt_blowfish-1.3/glibc-2.3.6-crypt.diff similarity index 100% rename from bcrypt/crypt_blowfish-1.3/glibc-2.3.6-crypt.diff rename to src/crypt_blowfish-1.3/glibc-2.3.6-crypt.diff diff --git a/bcrypt/crypt_blowfish-1.3/ow-crypt.h b/src/crypt_blowfish-1.3/ow-crypt.h similarity index 100% rename from bcrypt/crypt_blowfish-1.3/ow-crypt.h rename to src/crypt_blowfish-1.3/ow-crypt.h diff --git a/bcrypt/crypt_blowfish-1.3/wrapper.c b/src/crypt_blowfish-1.3/wrapper.c similarity index 100% rename from bcrypt/crypt_blowfish-1.3/wrapper.c rename to src/crypt_blowfish-1.3/wrapper.c diff --git a/bcrypt/crypt_blowfish-1.3/x86.S b/src/crypt_blowfish-1.3/x86.S similarity index 100% rename from bcrypt/crypt_blowfish-1.3/x86.S rename to src/crypt_blowfish-1.3/x86.S diff --git a/tests/test_bcrypt.py b/tests/test_bcrypt.py index 5793811..c91a62e 100644 --- a/tests/test_bcrypt.py +++ b/tests/test_bcrypt.py @@ -7,11 +7,6 @@ import six import bcrypt -def test_raise_implicit_compile(): - with pytest.raises(RuntimeError): - bcrypt._compile_module() - - def test_gensalt_basic(monkeypatch): monkeypatch.setattr(os, "urandom", lambda n: b"0000000000000000") assert bcrypt.gensalt() == b"$2a$12$KB.uKB.uKB.uKB.uKB.uK."