This commit is contained in:
Hynek Schlawack 2021-11-27 10:15:45 +01:00
commit dd0b48a459
19 changed files with 1286 additions and 0 deletions

133
.github/CODE_OF_CONDUCT.md vendored Normal file
View File

@ -0,0 +1,133 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<mailto:hs@ox.cx>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

178
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,178 @@
# How To Contribute
First off, thank you for considering contributing to *argon2-cffi-bindings*!
It's people like *you* who make it such a great tool for everyone.
This document intends to make contribution more accessible by codifying tribal knowledge and expectations.
Don't be afraid to open half-finished PRs, and ask questions if something is unclear!
Please note that this project is released with a Contributor [Code of Conduct](https://github.com/hynek/argon2-cffi-bindings/blob/main/.github/CODE_OF_CONDUCT.md).
By participating in this project you agree to abide by its terms.
Please report any harm to [Hynek Schlawack] in any way you find appropriate.
## Workflow
- No contribution is too small!
Please submit as many fixes for typos and grammar bloopers as you can!
- Try to limit each pull request to *one* change only.
- Since we squash on merge, it's up to you how you handle updates to the main branch.
Whether you prefer to rebase on main or merge main into your branch, do whatever is more comfortable for you.
- *Always* add tests and docs for your code.
This is a hard rule; patches with missing tests or documentation can't be merged.
- Make sure your changes pass our [CI].
You won't get any feedback until it's green unless you ask for it.
- Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done.
- Dont break backwards compatibility.
## Local Development Environment
You can (and should) run our test suite using [*tox*].
However, youll probably want a more traditional environment as well.
We highly recommend to develop using the latest Python release because we try to take advantage of modern features whenever possible.
First create a [virtual environment](https://virtualenv.pypa.io/) so you don't break your system-wide Python installation.
Its out of scope for this document to list all the ways to manage virtual environments in Python, but if you dont already have a pet way, take some time to look at tools like [*direnv*](https://hynek.me/til/python-project-local-venvs/), [*virtualfish*](https://virtualfish.readthedocs.io/), and [*virtualenvwrapper*](https://virtualenvwrapper.readthedocs.io/).
Next, get an up to date checkout of the *argon2-cffi-bindings* repository:
```console
$ git clone git@github.com:hynek/argon2-cffi-bindings.git
```
or if you want to use git via `https`:
```console
$ git clone https://github.com/hynek/argon2-cffi-bindings.git
```
Change into the newly created directory and **activate your virtual environment**
First you have to make sure that our *Argon2* *git* submodule is up-to-date:
```console
$ cd argon2-cffi-bindings
$ git submodule init # initialize git submodule mechanics
$ git submodule update # update the vendored Argon2 C library to the version we are packaging
```
Now an editable version of *argon2-cffi-bindings* along with its test requirements can be installed as usual:
```console
$ python -m pip install --upgrade pip setuptools cffi # PLEASE don't skip this step
$ python setup.py build # build the vendored C code
$ python -m pip install -e '.[dev]'
```
At this point,
```console
$ python -m pytest
```
should work and pass.
To avoid committing code that violates our style guide, we strongly advise you to install [*pre-commit*] [^dev] hooks:
```console
$ pre-commit install
```
You can also run them anytime (as our tox does) using:
```console
$ pre-commit run --all-files
```
[^dev]: *pre-commit* should have been installed into your virtualenv automatically when you ran `pip install -e '.[dev]'` above.
If *pre-commit* is missing, your probably need to run `pip install -e '.[dev]'` again.
## Code
- Obey [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/).
We use the `"""`-on-separate-lines style for docstrings:
```python
def func(x):
"""
Do something.
:param str x: A very important parameter.
:rtype: str
"""
```
- If you add or change public APIs, tag the docstring using `.. versionadded:: 16.0.0 WHAT` or `.. versionchanged:: 16.2.0 WHAT`.
- We use [*isort*](https://github.com/PyCQA/isort) to sort our imports, and we use [*Black*](https://github.com/psf/black) with line length of 79 characters to format our code.
As long as you run our full [*tox*] suite before committing, or install our [*pre-commit*] hooks (ideally you'll do both see [*Local Development Environment*](#local-development-environment) below), you won't have to spend any time on formatting your code at all.
If you don't, [CI] will catch it for you but that seems like a waste of your time!
## Tests
- Write your asserts as `expected == actual` to line them up nicely:
```python
x = f()
assert 42 == x.some_attribute
assert "foo" == x._a_private_attribute
```
- To run the test suite, all you need is a recent [*tox*].
It will ensure the test suite runs with all dependencies against all Python versions just as it will in our [CI].
If you lack some Python versions, you can can always limit the environments like `tox -e py38,py39`, or make it a non-failure using `tox --skip-missing-interpreters`.
In that case you should look into [*asdf*](https://asdf-vm.com) or [*pyenv*](https://github.com/pyenv/pyenv), which make it very easy to install many different Python versions in parallel.
- Write [good test docstrings](https://jml.io/pages/test-docstrings.html).
## Documentation
- Use [semantic newlines] in [Markdown*] files (files ending in `.md`):
```markdown
This is a sentence.
This is another sentence.
```
### Changelog
If your change is noteworthy, there needs to be a changelog entry in `CHANGELOG.md`.
- As with other docs, please use [semantic newlines] in the changelog.
- Wrap symbols like modules, functions, or classes into backticks so they are rendered in a `monospace font`.
- Wrap arguments into asterisks like in docstrings:
`Added new argument *an_argument*.`
- If you mention functions or other callables, add parentheses at the end of their names:
`_argon2_cffi_bindings.func()` or `_argon2_cffi_bindings.Class.method()`.
This makes the changelog a lot more readable.
- Prefer simple past tense or constructions with "now".
For example:
+ Added `_argon2_cffi_bindings.func()`.
+ `_argon2_cffi_bindings.func()` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument.
Example entries:
```markdown
Added `_argon2_cffi_bindings.func()`.
The feature really *is* awesome.
```
or:
```markdown
`_argon2_cffi_bindings.func()` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument.
The bug really *was* nasty.
```
[CI]: https://github.com/hynek/argon2-cffi-bindings/actions
[Hynek Schlawack]: https://hynek.me/about/
[*pre-commit*]: https://pre-commit.com/
[*tox*]: https://https://tox.wiki/
[semantic newlines]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
---
github: hynek
ko_fi: the_hynek

104
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,104 @@
---
name: CI
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
workflow_dispatch:
env:
FORCE_COLOR: "1" # Make tools pretty.
TOX_TESTENV_PASSENV: "FORCE_COLOR"
PYTHON_LATEST: "3.10"
jobs:
tests:
name: "tox on ${{ matrix.python-version }}"
runs-on: "ubuntu-latest"
strategy:
matrix:
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "pypy-3.7"]
steps:
- uses: "actions/checkout@v2"
with:
submodules: "recursive"
- uses: "actions/setup-python@v2"
with:
python-version: "${{ matrix.python-version }}"
- name: "Install dependencies"
run: |
python -VV
python -m site
python -m pip install --upgrade pip setuptools wheel
python -m pip install --upgrade virtualenv tox tox-gh-actions
- name: "Run tox targets for ${{ matrix.python-version }}"
run: "python -m tox"
system-package:
runs-on: "ubuntu-latest"
name: "Install and test with system package of Argon2."
steps:
- uses: "actions/checkout@v2"
- uses: "actions/setup-python@v2"
with:
python-version: ${{env.PYTHON_LATEST}}
- name: "Install dependencies"
run: |
sudo apt-get install libargon2-0 libargon2-0-dev
# Ensure we cannot use our own Argon2 by accident.
rm -rf extras
python -VV
python -m site
python -m pip install --upgrade pip setuptools wheel
python -m pip install --upgrade virtualenv tox
- run: "python -m tox -e system-argon2"
package:
name: "Build & verify package"
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
with:
submodules: "recursive"
- uses: "actions/setup-python@v2"
with:
python-version: ${{env.PYTHON_LATEST}}
- run: "python -m pip install build twine check-wheel-contents"
- run: "python -m build --sdist --wheel ."
- run: "ls -l dist"
- run: "check-wheel-contents dist/*.whl"
- name: "Check long_description"
run: "python -m twine check dist/*"
install-dev:
name: "Verify dev env"
runs-on: "${{ matrix.os }}"
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-latest", "macos-latest"]
steps:
- uses: "actions/checkout@v2"
with:
submodules: "recursive"
- uses: "actions/setup-python@v2"
with:
python-version: ${{env.PYTHON_LATEST}}
- run: python -m pip install --upgrade pip
- run: python setup.py build
- run: python -m pip install -e .[dev]
- name: "Import package"
run: "python -c 'from _argon2_cffi_bindings import ffi, lib; print(lib.ARGON2_VERSION_NUMBER)'"

45
.github/workflows/wheels.yml vendored Normal file
View File

@ -0,0 +1,45 @@
---
name: Build Wheels
on:
push:
tags:
- '*'
workflow_dispatch:
jobs:
wheels:
name: Building for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v2
with:
submodules: "recursive"
- name: Set up QEMU
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@v1
with:
platforms: arm64
- uses: pypa/cibuildwheel@v2.3.0
env:
# Only build CPython 3.6, because we have portable abi3 wheels.
# Windows arm64 is only available on 3.9 and later.
CIBW_BUILD: "cp36-* pp37-* pp38-* cp39-win_arm64"
CIBW_ARCHS_LINUX: "auto aarch64"
CIBW_ARCHS_MACOS: "auto"
# Unfortunately, Argon2 currently doesn't compile on Apple Silicon.
# CIBW_ARCHS_MACOS: auto universal2
- uses: actions/upload-artifact@v2
with:
path: wheelhouse/*.whl
...

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.tox
*.egg-info
*.so
build
dist
__pycache__

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "libargon2"]
path = extras/libargon2
url = https://github.com/P-H-C/phc-winner-argon2.git

30
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,30 @@
---
ci:
autoupdate_schedule: monthly
repos:
- repo: https://github.com/psf/black
rev: 21.11b1
hooks:
- id: black
language_version: python3.10
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
hooks:
- id: isort
additional_dependencies: [toml]
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
hooks:
- id: flake8
language_version: python3.10
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: debug-statements
- id: check-toml

12
CHANGELOG.md Normal file
View File

@ -0,0 +1,12 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Calendar Versioning](https://calver.org/).
## Unreleased
### Added
- Initial import from [*argon2-cffi*](https://github.com/hynek/argon2-cffi).

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2021 Hynek Schlawack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
include *.md *.txt *.ini LICENSE .pre-commit-config.yaml pyproject.toml
exclude src/argon2/_ffi.py .gitmodules extras/libargon2/.git *.yml
graft tests
graft .github
recursive-exclude tests *.pyc
graft extras

84
README.md Normal file
View File

@ -0,0 +1,84 @@
# Python CFFI Bindings for Argon2
*argon2-cffi-bindings* provides low-level [*CFFI*](https://cffi.readthedocs.io/) bindings to the [*Argon2*] password hashing algorithm including a vendored version of them.
<!-- Extract commit ID; refresh using `tox -e cog`
[[[cog
import subprocess
cp = subprocess.run(["git", "submodule"], capture_output=True)
id = cp.stdout[1:].decode().split(" ", 1)[0]
link = f'[**`{id[:7]}`**](https://github.com/P-H-C/phc-winner-argon2/commit/{id})'
print(f"The currently vendored *Argon2* commit ID is {link}.")
]]] -->
The currently vendored *Argon2* commit ID is [**`f57e61e`**](https://github.com/P-H-C/phc-winner-argon2/commit/f57e61e19229e23c4445b85494dbf7c07de721cb).
<!-- [[[end]]] -->
> If you want to hash passwords in an application, this package is **not** for you.
> Have a look at [*argon2-cffi*] with its high-level abstractions!
These bindings have been extracted from [*argon2-cffi*] and it remains its main consumer.
However, they may be used by other packages that want to use *Argon2* library without dealing with C-related complexities.
## Usage
The provided *CFFI* bindings are compiled in API mode.
Best effort is given to provide binary wheels for as many platforms as possible.
---
A copy of [*Argon2*] is vendored and used by default, but can be disabled if *argon2-cffi-bindings* is installed using:
```console
$ env ARGON2_CFFI_USE_SYSTEM=1 \
python -m pip install --no-binary=argon2-cffi-bindings argon2-cffi-bindings
```
### Python API
Since this package is intended to be an implementation detail, it uses a private module name.
Therefore you have to import the symbols from `_argon2_cffi_bindings`:
```python
from _argon2_cffi_bindings import ffi, lib
```
Please refer to [*cffi* documentation](https://cffi.readthedocs.io/en/latest/using.html) on how to use the `ffi` and `lib` objects.
The list of symbols that are provided can be found in the [`_ffi_build.py` file](https://github.com/hynek/argon2-cffi-bindings/blob/main/src/_argon2_cffi_bindings/_ffi_build.py).
[*Argon2*]: https://github.com/p-h-c/phc-winner-argon2
[*argon2-cffi*]: https://argon2-cffi.readthedocs.io/
## Project Information
*argon2-cffi-bindings* is available under the MIT license, available from [PyPI](https://pypi.org/project/argon2-cffi-bindings/), the source code and documentation can be found on [GitHub](https://github.com/hynek/argon2-cffi-bindings).
*argon2-cffi-bindings* targets Python 3.6 and later, including PyPy3.
### Credits & License
*argon2-cffi-bindings* is written and maintained by [Hynek Schlawack](https://hynek.me/about/).
It is released under the [MIT license](https://github.com/hynek/argon2-cffi/blob/main/LICENSE>).
The development is kindly supported by [Variomedia AG](https://www.variomedia.de/).
The authors of *Argon2* were very helpful to get the library to compile on ancient versions of Visual Studio for ancient versions of Python.
The documentation quotes frequently in verbatim from the *Argon2* [paper](https://www.password-hashing.net/argon2-specs.pdf) to avoid mistakes by rephrasing.
#### Vendored Code
The original *Argon2* repo can be found at <https://github.com/P-H-C/phc-winner-argon2/>.
Except for the components listed below, the *Argon2* code in this repository is copyright (c) 2015 Daniel Dinu, Dmitry Khovratovich (main authors), Jean-Philippe Aumasson and Samuel Neves, and under [CC0] license.
The string encoding routines in src/encoding.c are copyright (c) 2015 Thomas Pornin, and under [CC0] license.
The [*BLAKE2*](https://www.blake2.net) code in ``src/blake2/`` is copyright (c) Samuel Neves, 2013-2015, and under [CC0] license.
[CC0]: https://creativecommons.org/publicdomain/zero/1.0/

1
extras/libargon2 Submodule

@ -0,0 +1 @@
Subproject commit f57e61e19229e23c4445b85494dbf7c07de721cb

22
pyproject.toml Normal file
View File

@ -0,0 +1,22 @@
[build-system]
requires = ["setuptools>=45", "setuptools_scm>=6.2", "wheel", "cffi>=1.0"]
build-backend = "setuptools.build_meta"
[tool.setuptools_scm]
[tool.pytest.ini_options]
addopts = "-ra --strict-markers --capture=no"
xfail_strict = true
testpaths = "tests"
filterwarnings = [
"once::Warning",
]
[tool.black]
line-length = 79
[tool.isort]
profile = "attrs"

357
setup.py Normal file
View File

@ -0,0 +1,357 @@
# SPDX-License-Identifier: MIT
import os
import platform
import re
import sys
from distutils.command.build import build
from distutils.command.build_clib import build_clib
from distutils.errors import DistutilsSetupError
from pathlib import Path
from setuptools import find_packages, setup
from setuptools.command.install import install
###############################################################################
NAME = "argon2-cffi-bindings"
DESCRIPTION = "Low-level CFFI bindings for Argon2"
URL = "https://github.com/hynek/argon2-cffi-bindings"
PACKAGES = find_packages(where="src")
LICENSE = "MIT"
AUTHOR = "Hynek Schlawack"
EMAIL = "hs@ox.cx"
HERE = Path(__file__).parent.resolve()
use_sse2 = os.environ.get("ARGON2_CFFI_USE_SSE2", None)
if use_sse2 == "1":
optimized = True
elif use_sse2 == "0":
optimized = False
else:
# Optimized version requires SSE2 extensions. They have been around since
# 2001 so we try to compile it on every recent-ish x86.
optimized = platform.machine() in ("i686", "x86", "x86_64", "AMD64")
CFFI_MODULES = ["src/_argon2_cffi_bindings/_ffi_build.py:ffi"]
lib_base = Path("extras") / "libargon2" / "src"
include_dirs = [
str(lib_base / path)
for path in (
Path("..") / "include",
"blake2",
)
]
sources = [
str(lib_base / path)
for path in (
"argon2.c",
Path("blake2") / "blake2b.c",
"core.c",
"encoding.c",
"opt.c" if optimized else "ref.c",
"thread.c",
)
]
# This is the definition of the vendored libargon2.
LIBRARIES = [("argon2", {"include_dirs": include_dirs, "sources": sources})]
windows = "win32" in str(sys.platform).lower()
META_PATH = HERE / "src" / "_argon2_cffi_bindings" / "__init__.py"
KEYWORDS = ["password", "hash", "hashing", "security", "bindings", "cffi"]
PROJECT_URLS = {
"Source Code": "https://github.com/hynek/argon2-cffi-bindings",
"Funding": "https://github.com/sponsors/hynek",
"Tidelift": "https://tidelift.com/subscription/pkg/pypi-argon2-cffi?"
"utm_source=pypi-argon2-cffi&utm_medium=pypi",
"Ko-fi": "https://ko-fi.com/the_hynek",
}
CLASSIFIERS = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX",
"Operating System :: Unix",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Programming Language :: Python",
"Topic :: Security :: Cryptography",
"Topic :: Security",
"Topic :: Software Development :: Libraries :: Python Modules",
]
PYTHON_REQUIRES = ">=3.6"
SETUP_REQUIRES = ["cffi>=1.0.0", "setuptools_scm>=6.2"]
INSTALL_REQUIRES = ["cffi>=1.0.0"]
EXTRAS_REQUIRE = {"tests": ["pytest"]}
EXTRAS_REQUIRE["dev"] = EXTRAS_REQUIRE["tests"] + [
"cogapp",
"pre-commit",
"wheel",
]
###############################################################################
def keywords_with_side_effects(argv):
"""
Get a dictionary with setup keywords that (can) have side effects.
:param argv: A list of strings with command line arguments.
:returns: A dictionary with keyword arguments for the ``setup()`` function.
This setup.py script uses the setuptools 'setup_requires' feature
because this is required by the cffi package to compile extension
modules. The purpose of ``keywords_with_side_effects()`` is to avoid
triggering the cffi build process as a result of setup.py invocations
that don't need the cffi module to be built (setup.py serves the dual
purpose of exposing package metadata).
Stolen from pyca/cryptography.
"""
no_setup_requires_arguments = (
"-h",
"--help",
"-n",
"--dry-run",
"-q",
"--quiet",
"-v",
"--verbose",
"-V",
"--version",
"--author",
"--author-email",
"--classifiers",
"--contact",
"--contact-email",
"--description",
"--egg-base",
"--fullname",
"--help-commands",
"--keywords",
"--licence",
"--license",
"--long-description",
"--maintainer",
"--maintainer-email",
"--name",
"--no-user-cfg",
"--obsoletes",
"--platforms",
"--provides",
"--requires",
"--url",
"clean",
"egg_info",
"register",
"sdist",
"upload",
)
def is_short_option(argument):
"""Check whether a command line argument is a short option."""
return len(argument) >= 2 and argument[0] == "-" and argument[1] != "-"
def expand_short_options(argument):
"""Expand combined short options into canonical short options."""
return ("-" + char for char in argument[1:])
def argument_without_setup_requirements(argv, i):
"""Check whether a command line argument needs setup requirements."""
if argv[i] in no_setup_requires_arguments:
# Simple case: An argument which is either an option or a command
# which doesn't need setup requirements.
return True
elif is_short_option(argv[i]) and all(
option in no_setup_requires_arguments
for option in expand_short_options(argv[i])
):
# Not so simple case: Combined short options none of which need
# setup requirements.
return True
elif argv[i - 1 : i] == ["--egg-base"]:
# Tricky case: --egg-info takes an argument which should not make
# us use setup_requires (defeating the purpose of this code).
return True
else:
return False
if all(
argument_without_setup_requirements(argv, i)
for i in range(1, len(argv))
):
return {"cmdclass": {"build": DummyBuild, "install": DummyInstall}}
else:
global LIBRARIES
use_system_argon2 = (
os.environ.get("ARGON2_CFFI_USE_SYSTEM", "0") == "1"
)
if use_system_argon2:
disable_subcommand(build, "build_clib")
LIBRARIES = []
cmdclass = {"build_clib": BuildCLibWithCompilerFlags}
if BDistWheel is not None:
cmdclass["bdist_wheel"] = BDistWheel
return {
"setup_requires": SETUP_REQUIRES,
"cffi_modules": CFFI_MODULES,
"libraries": LIBRARIES,
"cmdclass": cmdclass,
}
def disable_subcommand(command, subcommand_name):
for name, method in command.sub_commands:
if name == subcommand_name:
command.sub_commands.remove((subcommand_name, method))
break
setup_requires_error = (
"Requested setup command that needs 'setup_requires' while command line "
"arguments implied a side effect free command or option."
)
class DummyBuild(build):
"""
This class makes it very obvious when ``keywords_with_side_effects()`` has
incorrectly interpreted the command line arguments to ``setup.py build`` as
one of the 'side effect free' commands or options.
"""
def run(self):
raise RuntimeError(setup_requires_error)
class DummyInstall(install):
"""
This class makes it very obvious when ``keywords_with_side_effects()`` has
incorrectly interpreted the command line arguments to ``setup.py install``
as one of the 'side effect free' commands or options.
"""
def run(self):
raise RuntimeError(setup_requires_error)
META_FILE = META_PATH.read_text()
def find_meta(meta):
"""
Extract __*meta*__ from META_FILE.
"""
meta_match = re.search(
fr"^__{meta}__ = ['\"]([^'\"]*)['\"]", META_FILE, re.M
)
if meta_match:
return meta_match.group(1)
raise RuntimeError(f"Unable to find __{meta}__ string.")
LONG = (HERE / "README.md").read_text()
class BuildCLibWithCompilerFlags(build_clib):
"""
We need to pass ``-msse2`` for the optimized build.
"""
def build_libraries(self, libraries):
"""
Mostly copy pasta from ``distutils.command.build_clib``.
"""
for (lib_name, build_info) in libraries:
sources = build_info.get("sources")
if sources is None or not isinstance(sources, (list, tuple)):
raise DistutilsSetupError(
"in 'libraries' option (library '%s'), "
"'sources' must be present and must be "
"a list of source filenames" % lib_name
)
sources = list(sources)
print(f"building '{lib_name}' library")
# First, compile the source code to object files in the library
# directory. (This should probably change to putting object
# files in a temporary build directory.)
macros = build_info.get("macros")
include_dirs = build_info.get("include_dirs")
objects = self.compiler.compile(
sources,
extra_preargs=["-msse2"] if optimized and not windows else [],
output_dir=self.build_temp,
macros=macros,
include_dirs=include_dirs,
debug=self.debug,
)
# Now "link" the object files together into a static library.
# (On Unix at least, this isn't really linking -- it just
# builds an archive. Whatever.)
self.compiler.create_static_lib(
objects, lib_name, output_dir=self.build_clib, debug=self.debug
)
if sys.version_info > (3,) and platform.python_implementation() == "CPython":
try:
import wheel.bdist_wheel
except ImportError:
BDistWheel = None
else:
class BDistWheel(wheel.bdist_wheel.bdist_wheel):
def finalize_options(self):
self.py_limited_api = f"cp3{sys.version_info[1]}"
wheel.bdist_wheel.bdist_wheel.finalize_options(self)
else:
BDistWheel = None
if __name__ == "__main__":
setup(
name=NAME,
description=DESCRIPTION,
license=LICENSE,
url=URL,
project_urls=PROJECT_URLS,
use_scm_version=True, # setuptools_scm
author=AUTHOR,
author_email=EMAIL,
maintainer=AUTHOR,
maintainer_email=EMAIL,
long_description=LONG,
long_description_content_type="text/markdown",
keywords=KEYWORDS,
packages=PACKAGES,
package_dir={"": "src"},
classifiers=CLASSIFIERS,
python_requires=PYTHON_REQUIRES,
install_requires=INSTALL_REQUIRES,
extras_require=EXTRAS_REQUIRE,
# CFFI
zip_safe=False,
ext_package="_argon2_cffi_bindings",
**keywords_with_side_effects(sys.argv),
)

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: MIT
from ._ffi import ffi, lib
__all__ = ["ffi", "lib"]

View File

@ -0,0 +1,177 @@
# SPDX-License-Identifier: MIT
import os
from cffi import FFI
include_dirs = [os.path.join("extras", "libargon2", "include")]
use_system_argon2 = os.environ.get("ARGON2_CFFI_USE_SYSTEM", "0") == "1"
if use_system_argon2:
include_dirs = []
ffi = FFI()
ffi.set_source(
"_ffi",
"#include <argon2.h>",
include_dirs=include_dirs,
libraries=["argon2"],
)
ffi.cdef(
"""\
typedef enum Argon2_type {
Argon2_d = ...,
Argon2_i = ...,
Argon2_id = ...,
} argon2_type;
typedef enum Argon2_version {
ARGON2_VERSION_10 = ...,
ARGON2_VERSION_13 = ...,
ARGON2_VERSION_NUMBER = ...
} argon2_version;
int argon2_hash(const uint32_t t_cost, const uint32_t m_cost,
const uint32_t parallelism, const void *pwd,
const size_t pwdlen, const void *salt,
const size_t saltlen, void *hash,
const size_t hashlen, char *encoded,
const size_t encodedlen, argon2_type type,
const uint32_t version);
int argon2_verify(const char *encoded, const void *pwd,
const size_t pwdlen, argon2_type type);
const char *argon2_error_message(int error_code);
typedef int (*allocate_fptr)(uint8_t **memory, size_t bytes_to_allocate);
typedef void (*deallocate_fptr)(uint8_t *memory, size_t bytes_to_allocate);
typedef struct Argon2_Context {
uint8_t *out; /* output array */
uint32_t outlen; /* digest length */
uint8_t *pwd; /* password array */
uint32_t pwdlen; /* password length */
uint8_t *salt; /* salt array */
uint32_t saltlen; /* salt length */
uint8_t *secret; /* key array */
uint32_t secretlen; /* key length */
uint8_t *ad; /* associated data array */
uint32_t adlen; /* associated data length */
uint32_t t_cost; /* number of passes */
uint32_t m_cost; /* amount of memory requested (KB) */
uint32_t lanes; /* number of lanes */
uint32_t threads; /* maximum number of threads */
uint32_t version; /* version number */
allocate_fptr allocate_cbk; /* pointer to memory allocator */
deallocate_fptr free_cbk; /* pointer to memory deallocator */
uint32_t flags; /* array of bool options */
} argon2_context;
int argon2_ctx(argon2_context *context, argon2_type type);
/* Error codes */
typedef enum Argon2_ErrorCodes {
ARGON2_OK = ...,
ARGON2_OUTPUT_PTR_NULL = ...,
ARGON2_OUTPUT_TOO_SHORT = ...,
ARGON2_OUTPUT_TOO_LONG = ...,
ARGON2_PWD_TOO_SHORT = ...,
ARGON2_PWD_TOO_LONG = ...,
ARGON2_SALT_TOO_SHORT = ...,
ARGON2_SALT_TOO_LONG = ...,
ARGON2_AD_TOO_SHORT = ...,
ARGON2_AD_TOO_LONG = ...,
ARGON2_SECRET_TOO_SHORT = ...,
ARGON2_SECRET_TOO_LONG = ...,
ARGON2_TIME_TOO_SMALL = ...,
ARGON2_TIME_TOO_LARGE = ...,
ARGON2_MEMORY_TOO_LITTLE = ...,
ARGON2_MEMORY_TOO_MUCH = ...,
ARGON2_LANES_TOO_FEW = ...,
ARGON2_LANES_TOO_MANY = ...,
ARGON2_PWD_PTR_MISMATCH = ..., /* NULL ptr with non-zero length */
ARGON2_SALT_PTR_MISMATCH = ..., /* NULL ptr with non-zero length */
ARGON2_SECRET_PTR_MISMATCH = ..., /* NULL ptr with non-zero length */
ARGON2_AD_PTR_MISMATCH = ..., /* NULL ptr with non-zero length */
ARGON2_MEMORY_ALLOCATION_ERROR = ...,
ARGON2_FREE_MEMORY_CBK_NULL = ...,
ARGON2_ALLOCATE_MEMORY_CBK_NULL = ...,
ARGON2_INCORRECT_PARAMETER = ...,
ARGON2_INCORRECT_TYPE = ...,
ARGON2_OUT_PTR_MISMATCH = ...,
ARGON2_THREADS_TOO_FEW = ...,
ARGON2_THREADS_TOO_MANY = ...,
ARGON2_MISSING_ARGS = ...,
ARGON2_ENCODING_FAIL = ...,
ARGON2_DECODING_FAIL = ...,
ARGON2_THREAD_FAIL = ...,
ARGON2_DECODING_LENGTH_FAIL= ...,
ARGON2_VERIFY_MISMATCH = ...,
} argon2_error_codes;
#define ARGON2_FLAG_CLEAR_PASSWORD ...
#define ARGON2_FLAG_CLEAR_SECRET ...
#define ARGON2_DEFAULT_FLAGS ...
#define ARGON2_MIN_LANES ...
#define ARGON2_MAX_LANES ...
#define ARGON2_MIN_THREADS ...
#define ARGON2_MAX_THREADS ...
#define ARGON2_SYNC_POINTS ...
#define ARGON2_MIN_OUTLEN ...
#define ARGON2_MAX_OUTLEN ...
#define ARGON2_MIN_MEMORY ...
#define ARGON2_MAX_MEMORY_BITS ...
#define ARGON2_MAX_MEMORY ...
#define ARGON2_MIN_TIME ...
#define ARGON2_MAX_TIME ...
#define ARGON2_MIN_PWD_LENGTH ...
#define ARGON2_MAX_PWD_LENGTH ...
#define ARGON2_MIN_AD_LENGTH ...
#define ARGON2_MAX_AD_LENGTH ...
#define ARGON2_MIN_SALT_LENGTH ...
#define ARGON2_MAX_SALT_LENGTH ...
#define ARGON2_MIN_SECRET ...
#define ARGON2_MAX_SECRET ...
uint32_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost,
uint32_t parallelism, uint32_t saltlen,
uint32_t hashlen, argon2_type type);
"""
)
if __name__ == "__main__":
ffi.compile()

19
tests/test_smoke.py Normal file
View File

@ -0,0 +1,19 @@
# SPDX-License-Identifier: MIT
"""
Since this package doesn't do anything beyond providing bindings, all we can
do is trying to ensure that those bindings are functional.
"""
def test_smoke():
"""
lib and ffi can be imported and looks OK.
"""
from _argon2_cffi_bindings import ffi, lib
assert repr(ffi).startswith("<_cffi_backend.FFI object at")
assert repr(lib).startswith("<Lib object for")
assert 19 == lib.ARGON2_VERSION_NUMBER
assert 42 == lib.argon2_encodedlen(1, 2, 3, 4, 5, lib.Argon2_id)

79
tox.ini Normal file
View File

@ -0,0 +1,79 @@
[flake8]
exclude = src/_argon2_cffi_bindings/_ffi.py
ignore:
# Black vs flake8 conflict
E203
# We don't run pre-commit in CI, because we use pre-commit.ci.
[gh-actions]
python =
3.6: py36
3.7: py37
3.8: py38
3.9: py39, manifest
3.10: py310, cogCheck
pypy-3: pypy3
[tox]
envlist = pre-commit,cogCheck,cog,py36,py37,py38,py39,py310,pypy3,system-argon2,manifest,pypi-description
isolated_build = true
[testenv:pre-commit]
description = Run all pre-commit hooks.
basepython = python3.10
skip_install = true
deps = pre-commit
passenv = HOMEPATH # needed on Windows
commands = pre-commit run --all-files
[testenv:cog]
description = "Update README"
skip_install = true
deps =
cogapp>=3.3.0
commands = python -m cogapp -rP README.md
[testenv:cogCheck]
description = "Ensure README.md is up to date"
skip_install = true
deps = {[testenv:cog]deps}
commands = python -m cogapp --check -P README.md
[testenv]
description = Run tests.
extras = tests
commands =
python -m pytest {posargs}
[testenv:system-argon2]
description = Run tests against bindings that use a system installation of Argon2.
setenv = ARGON2_CFFI_USE_SYSTEM=1
extras = tests
install_command = pip install {opts} --no-binary=argon2-cffi-bindings {packages}
commands =
python -m pytest {posargs}
[testenv:manifest]
description = Ensure MANIFEST.in is up to date.
deps = check-manifest
skip_install = true
commands = check-manifest
[testenv:pypi-description]
description = Ensure README.rst renders on PyPI.
skip_install = true
deps =
twine
pip >= 18.0.0
commands =
pip wheel -w {envtmpdir}/build --no-deps .
twine check {envtmpdir}/build/*