From 26973e70513c188abd556a63066c9f0c1e2af9ed Mon Sep 17 00:00:00 2001 From: Rodolphe Breard Date: Sat, 16 Dec 2017 19:21:18 +0100 Subject: [PATCH] Add the password sub-module --- MANIFEST.in | 4 ++ Pipfile | 15 ++++++ Pipfile.lock | 102 +++++++++++++++++++++++++++++++++++++++++ README.md | 34 -------------- README.rst | 38 +++++++++++++++ libreauth/__init__.py | 38 +++++++++++++++ libreauth/oath.py | 33 +++++++++++++ libreauth/password.py | 85 ++++++++++++++++++++++++++++++++++ setup.cfg | 4 ++ setup.py | 31 +++++++++++++ tests/__init__.py | 0 tests/test_password.py | 64 ++++++++++++++++++++++++++ 12 files changed, 414 insertions(+), 34 deletions(-) create mode 100644 MANIFEST.in create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100644 README.md create mode 100644 README.rst create mode 100644 libreauth/__init__.py create mode 100644 libreauth/oath.py create mode 100644 libreauth/password.py create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/test_password.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d74d57d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include CHANGELOG.md +include CONTRIBUTING.md +include LICENSE-EN.txt +include LICENSE-FR.txt diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..5e9abdb --- /dev/null +++ b/Pipfile @@ -0,0 +1,15 @@ +[[source]] + +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + + +[packages] + + + +[dev-packages] + +twine = "*" +wheel = "*" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..a02fded --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,102 @@ +{ + "_meta": { + "hash": { + "sha256": "bc2f3999187c6f5eee3a8a8f26c22b796b5034c7a412de94fd6f95c37c020cc4" + }, + "host-environment-markers": { + "implementation_name": "cpython", + "implementation_version": "3.6.3", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "4.13.12-1-ARCH", + "platform_system": "Linux", + "platform_version": "#1 SMP PREEMPT Wed Nov 8 11:54:06 CET 2017", + "python_full_version": "3.6.3", + "python_version": "3.6", + "sys_platform": "linux" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": { + "certifi": { + "hashes": [ + "sha256:244be0d93b71e93fc0a0a479862051414d0e00e16435707e5bf5000f92e04694", + "sha256:5ec74291ca1136b40f0379e1128ff80e866597e4e2c1e755739a913bbc3613c0" + ], + "version": "==2017.11.5" + }, + "chardet": { + "hashes": [ + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" + ], + "version": "==3.0.4" + }, + "idna": { + "hashes": [ + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" + ], + "version": "==2.6" + }, + "pkginfo": { + "hashes": [ + "sha256:31a49103180ae1518b65d3f4ce09c784e2bc54e338197668b4fb7dc539521024", + "sha256:bb1a6aeabfc898f5df124e7e00303a5b3ec9a489535f346bfbddb081af93f89e" + ], + "version": "==1.4.1" + }, + "requests": { + "hashes": [ + "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", + "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + ], + "version": "==2.18.4" + }, + "requests-toolbelt": { + "hashes": [ + "sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237", + "sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5" + ], + "version": "==0.8.0" + }, + "tqdm": { + "hashes": [ + "sha256:733ce813ab83e17a03da34043c6265e29f6731e3cbbbe305b12694ced0af6770", + "sha256:7ca803c2ea268c6bdb541e2dac74a3af23cf4bf7b4132a6a78926d255f8c8df1" + ], + "version": "==4.19.4" + }, + "twine": { + "hashes": [ + "sha256:d3ce5c480c22ccfb761cd358526e862b32546d2fe4bc93d46b5cf04ea3cc46ca", + "sha256:caa45b7987fc96321258cd7668e3be2ff34064f5c66d2d975b641adca659c1ab" + ], + "version": "==1.9.1" + }, + "urllib3": { + "hashes": [ + "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", + "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + ], + "version": "==1.22" + }, + "wheel": { + "hashes": [ + "sha256:e721e53864f084f956f40f96124a74da0631ac13fbbd1ba99e8e2b5e9cafdf64", + "sha256:9515fe0a94e823fd90b08d22de45d7bde57c90edce705b22f5e1ecf7e1b653c8" + ], + "version": "==0.30.0" + } + } +} diff --git a/README.md b/README.md deleted file mode 100644 index dde7d60..0000000 --- a/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Python LibreAuth - -Python bindings to the LibreAuth library. -LibreAuth is a collection of tools for user authentication written in Rust. - - -## Features - -:warning: This is a work in progress. Some features may not be available. - -- Password / passphrase authentication - - [ ] no character-set limitation - - [ ] reasonable lenth limit ([security vs. DOS](http://arstechnica.com/security/2013/09/long-passwords-are-good-but-too-much-length-can-be-bad-for-security/)) - - [ ] strong, evolutive and retro-compatible password hashing functions - - [ ] optional NIST Special Publication 800-63B compatibility -- HOTP - HMAC-based One-time Password Algorithm ([OATH](http://www.openauthentication.org/) - [RFC 4226](https://tools.ietf.org/html/rfc4226)) - - [ ] the key can be passed as bytes, an ASCII string, an hexadicimal string or a base32 string - - [ ] customizable counter - - [ ] customizable hash function (sha1, sha256, sha512) - - [ ] customizable output length - - [ ] customizable output alphabet -- TOTP - Time-based One-time Password Algorithm ([OATH](http://www.openauthentication.org/) - [RFC 6238](https://tools.ietf.org/html/rfc6238)) - - [ ] the key can be passed as bytes, an ASCII string, an hexadicimal string or a base32 string - - [ ] customizable timestamp - - [ ] customizable period - - [ ] customizable initial time (T0) - - [ ] customizable hash function (sha1, sha256, sha512) - - [ ] customizable output length - - [ ] customizable output alphabet - - [ ] customizable positive and negative period tolerance -- ~~U2F - Universal 2nd Factor~~ ([FIDO Alliance](https://fidoalliance.org/specifications/download/)) :warning: Not started - - [ ] virtual device API - - [ ] client API - - [ ] server API diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..a8060b5 --- /dev/null +++ b/README.rst @@ -0,0 +1,38 @@ +Python LibreAuth +================ + +Python bindings to the LibreAuth library. +LibreAuth is a collection of tools for user authentication written in Rust. + + +Features +-------- + +This is a work in progress. Some features may not be available. + +* Password / passphrase authentication + - ✓ no character-set limitation + - ✓ reasonable lenth limit ([security vs. DOS](http://arstechnica.com/security/2013/09/long-passwords-are-good-but-too-much-length-can-be-bad-for-security/)) + - ✓ strong, evolutive and retro-compatible password hashing functions + - ✓ optional NIST Special Publication 800-63B compatibility +* HOTP - HMAC-based One-time Password Algorithm ([OATH](http://www.openauthentication.org/) - [RFC 4226](https://tools.ietf.org/html/rfc4226)) + - ✗ the key can be passed as bytes, an ASCII string, an hexadicimal string or a base32 string + - ✗ customizable counter + - ✗ customizable hash function (sha1, sha256, sha512) + - ✗ customizable output length + - ✗ customizable output alphabet +* TOTP - Time-based One-time Password Algorithm ([OATH](http://www.openauthentication.org/) - [RFC 6238](https://tools.ietf.org/html/rfc6238)) + - ✗ the key can be passed as bytes, an ASCII string, an hexadicimal string or a base32 string + - ✗ customizable timestamp + - ✗ customizable period + - ✗ customizable initial time (T0) + - ✗ customizable hash function (sha1, sha256, sha512) + - ✗ customizable output length + - ✗ customizable output alphabet + - ✗ customizable positive and negative period tolerance + + +Requirements +------------ + +LibreAuth 0.6 or higher. diff --git a/libreauth/__init__.py b/libreauth/__init__.py new file mode 100644 index 0000000..190eda6 --- /dev/null +++ b/libreauth/__init__.py @@ -0,0 +1,38 @@ +# Copyright Rodolphe Breard (2017) +# Author: Rodolphe Breard (2017) +# +# This software is a computer library whose purpose is to offer a +# collection of tools for user authentication. +# +# This software is governed by the CeCILL license under French law and +# abiding by the rules of distribution of free software. You can use, +# modify and/ or redistribute the software under the terms of the CeCILL +# license as circulated by CEA, CNRS and INRIA at the following URL +# "http://www.cecill.info". +# +# As a counterpart to the access to the source code and rights to copy, +# modify and redistribute granted by the license, users are provided only +# with a limited warranty and the software's author, the holder of the +# economic rights, and the successive licensors have only limited +# liability. +# +# In this respect, the user's attention is drawn to the risks associated +# with loading, using, modifying and/or developing or reproducing the +# software by the user in light of its specific status of free software, +# that may mean that it is complicated to manipulate, and that also +# therefore means that it is reserved for developers and experienced +# professionals having in-depth computer knowledge. Users are therefore +# encouraged to load and test the software's suitability as regards their +# requirements in conditions enabling the security of their systems and/or +# data to be ensured and, more generally, to use and operate it in the +# same conditions as regards security. +# +# The fact that you are presently reading this means that you have had +# knowledge of the CeCILL license and that you accept its terms. + +from ctypes.util import find_library +from ctypes import cdll +from os import getenv + +lib_path = getenv('LIBREAUTH_LIB_PATH') or find_library('libreauth') +lib = cdll.LoadLibrary(lib_path) diff --git a/libreauth/oath.py b/libreauth/oath.py new file mode 100644 index 0000000..53862a5 --- /dev/null +++ b/libreauth/oath.py @@ -0,0 +1,33 @@ +# Copyright Rodolphe Breard (2017) +# Author: Rodolphe Breard (2017) +# +# This software is a computer library whose purpose is to offer a +# collection of tools for user authentication. +# +# This software is governed by the CeCILL license under French law and +# abiding by the rules of distribution of free software. You can use, +# modify and/ or redistribute the software under the terms of the CeCILL +# license as circulated by CEA, CNRS and INRIA at the following URL +# "http://www.cecill.info". +# +# As a counterpart to the access to the source code and rights to copy, +# modify and redistribute granted by the license, users are provided only +# with a limited warranty and the software's author, the holder of the +# economic rights, and the successive licensors have only limited +# liability. +# +# In this respect, the user's attention is drawn to the risks associated +# with loading, using, modifying and/or developing or reproducing the +# software by the user in light of its specific status of free software, +# that may mean that it is complicated to manipulate, and that also +# therefore means that it is reserved for developers and experienced +# professionals having in-depth computer knowledge. Users are therefore +# encouraged to load and test the software's suitability as regards their +# requirements in conditions enabling the security of their systems and/or +# data to be ensured and, more generally, to use and operate it in the +# same conditions as regards security. +# +# The fact that you are presently reading this means that you have had +# knowledge of the CeCILL license and that you accept its terms. + + diff --git a/libreauth/password.py b/libreauth/password.py new file mode 100644 index 0000000..ddfc72a --- /dev/null +++ b/libreauth/password.py @@ -0,0 +1,85 @@ +# Copyright Rodolphe Breard (2017) +# Author: Rodolphe Breard (2017) +# +# This software is a computer library whose purpose is to offer a +# collection of tools for user authentication. +# +# This software is governed by the CeCILL license under French law and +# abiding by the rules of distribution of free software. You can use, +# modify and/ or redistribute the software under the terms of the CeCILL +# license as circulated by CEA, CNRS and INRIA at the following URL +# "http://www.cecill.info". +# +# As a counterpart to the access to the source code and rights to copy, +# modify and redistribute granted by the license, users are provided only +# with a limited warranty and the software's author, the holder of the +# economic rights, and the successive licensors have only limited +# liability. +# +# In this respect, the user's attention is drawn to the risks associated +# with loading, using, modifying and/or developing or reproducing the +# software by the user in light of its specific status of free software, +# that may mean that it is complicated to manipulate, and that also +# therefore means that it is reserved for developers and experienced +# professionals having in-depth computer knowledge. Users are therefore +# encouraged to load and test the software's suitability as regards their +# requirements in conditions enabling the security of their systems and/or +# data to be ensured and, more generally, to use and operate it in the +# same conditions as regards security. +# +# The fact that you are presently reading this means that you have had +# knowledge of the CeCILL license and that you accept its terms. + +from ctypes import * +from . import lib + +PASSWORD_MIN_LEN = 8 +PASSWORD_MAX_LEN = 128 +PASSWORD_STORAGE_LEN = 1024 + +NOSTANDARD = 0 +NIST80063B = 1 + + +class LibreAuthPassError(Exception): + """Exception raised for errors in the password authentication module. + + Attributes: + code -- error code + message -- explanation of the error + """ + + def __init__(self, code): + valid_codes = { + 0: 'success', + 1: 'password is too short', + 2: 'password is too long', + 10: 'invalid password format', + 20: 'not enough space', + } + self.code = code + if code in valid_codes: + self.message = valid_codes[code] + else: + self.message = 'unknown error' + +def password_hash(password): + return password_hash_standard(password, NOSTANDARD) + +def password_hash_standard(password, standard): + pass_len = len(password) + if pass_len < PASSWORD_MIN_LEN: + raise LibreAuthPassError(1) + if pass_len > PASSWORD_MAX_LEN: + raise LibreAuthPassError(2) + buff = create_string_buffer(b'\000' * PASSWORD_STORAGE_LEN) + c_pass = create_string_buffer(password) + err = lib.libreauth_password_hash(c_pass, buff, PASSWORD_STORAGE_LEN, standard) + if err != 0: + raise LibreAuthPassError(err) + return str(buff.value, encoding="utf-8") + +def is_valid(password, reference): + c_pass = create_string_buffer(password) + c_ref = create_string_buffer(bytes(reference, encoding='utf-8')) + return bool(lib.libreauth_password_is_valid(c_pass, c_ref)) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..628f29f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[bdist_wheel] +# See PEP 425 +# https://www.python.org/dev/peps/pep-0425/ +python-tag = py3 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fc02be3 --- /dev/null +++ b/setup.py @@ -0,0 +1,31 @@ +from setuptools import setup, find_packages +from codecs import open +from os import path + +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the README file +with open(path.join(here, 'README.rst'), encoding='utf-8') as f: + long_description = f.read() + +setup( + name='libreauth', + version='0.1.0.dev2', + description='Python bindings to the LibreAuth library.', + long_description=long_description, + url='https://github.com/breard-r/py-libreauth', + author='Rodolphe Bréard', + author_email='rodolphe@breard.tf', + license='CeCILL', + classifiers=[ + 'Development Status :: 1 - Planning', + 'License :: OSI Approved :: CEA CNRS Inria Logiciel Libre License, version 2.1 (CeCILL-2.1)', + 'Programming Language :: Python :: 3 :: Only', + 'Topic :: Security', + ], + keywords='authentication password oath hotp totp', + packages=find_packages(exclude=['contrib', 'docs', 'tests']), + data_files=[ + ('license', ['LICENSE-EN.txt', 'LICENSE-FR.txt']), + ], +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_password.py b/tests/test_password.py new file mode 100644 index 0000000..c79bb79 --- /dev/null +++ b/tests/test_password.py @@ -0,0 +1,64 @@ +# Copyright Rodolphe Breard (2017) +# Author: Rodolphe Breard (2017) +# +# This software is a computer library whose purpose is to offer a +# collection of tools for user authentication. +# +# This software is governed by the CeCILL license under French law and +# abiding by the rules of distribution of free software. You can use, +# modify and/ or redistribute the software under the terms of the CeCILL +# license as circulated by CEA, CNRS and INRIA at the following URL +# "http://www.cecill.info". +# +# As a counterpart to the access to the source code and rights to copy, +# modify and redistribute granted by the license, users are provided only +# with a limited warranty and the software's author, the holder of the +# economic rights, and the successive licensors have only limited +# liability. +# +# In this respect, the user's attention is drawn to the risks associated +# with loading, using, modifying and/or developing or reproducing the +# software by the user in light of its specific status of free software, +# that may mean that it is complicated to manipulate, and that also +# therefore means that it is reserved for developers and experienced +# professionals having in-depth computer knowledge. Users are therefore +# encouraged to load and test the software's suitability as regards their +# requirements in conditions enabling the security of their systems and/or +# data to be ensured and, more generally, to use and operate it in the +# same conditions as regards security. +# +# The fact that you are presently reading this means that you have had +# knowledge of the CeCILL license and that you accept its terms. + +from libreauth.password import * +import unittest + +class PasswordTestCase(unittest.TestCase): + def test_default(self): + p = b'my super password' + h = password_hash(p) + self.assertTrue(h.startswith('$')) + self.assertEqual(len(h.split('$')), 5) + self.assertTrue(is_valid(p, h)) + self.assertFalse(is_valid(b'bad password', h)) + + def test_std(self): + p = b'my super password' + for std in (NOSTANDARD, NIST80063B, ): + h = password_hash_standard(p, NIST80063B) + self.assertTrue(h.startswith('$')) + self.assertEqual(len(h.split('$')), 5) + self.assertTrue(is_valid(p, h)) + self.assertFalse(is_valid(b'bad password', h)) + + def test_invalid_pass_len(self): + for p in (b'', b'a', b'1234567'): + with self.assertRaises(LibreAuthPassError) as cm: + password_hash(p) + e = cm.exception + self.assertEqual(e.code, 1) + for p in (b'a' * 129, b'1' * 256): + with self.assertRaises(LibreAuthPassError) as cm: + password_hash(p) + e = cm.exception + self.assertEqual(e.code, 2)