Skip to content

Commit

Permalink
Initialize project
Browse files Browse the repository at this point in the history
  • Loading branch information
jteppinette committed Mar 30, 2022
0 parents commit f1357c9
Show file tree
Hide file tree
Showing 21 changed files with 375 additions and 0 deletions.
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
layout pyenv $(cat runtimes.txt)
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
max-line-length = 88
14 changes: 14 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: pre-commit

on:
pull_request:
push:
branches: [main]

jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: pre-commit/[email protected]
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: test

on:
pull_request:
push:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
name: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- run: |
pip install -r requirements/test.txt
pip install -e .
pytest
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
*.egg-info
.direnv
.tox
__pycache__
dist
venv

# Add the following files to the allowlist
# under various searching tools.
!.envrc
!.flake8
!.github
!.gitignore
!.pre-commit-config.yaml
49 changes: 49 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: check-added-large-files # Prevent giant files from being committed
- id: check-ast # Simply check whether the files parse as valid python.
- id: check-builtin-literals # Require literal syntax when initializing empty or zero Python builtin types.
- id: check-byte-order-marker # forbid files which have a UTF-8 byte-order marker
- id: check-case-conflict # Check for files that would conflict in case-insensitive filesystems
- id: check-docstring-first # Checks a common error of defining a docstring after code.
- id: check-executables-have-shebangs # Ensures that (non-binary) executables have a shebang.
- id: check-json # This hook checks json files for parseable syntax.
- id: check-merge-conflict # Check for files that contain merge conflict strings.
- id: check-shebang-scripts-are-executable # Ensures that (non-binary) files with a shebang are executable.
- id: check-symlinks # Checks for symlinks which do not point to anything.
- id: check-toml # This hook checks toml files for parseable syntax.
- id: check-vcs-permalinks # Ensures that links to vcs websites are permalinks.
- id: check-xml # This hook checks xml files for parseable syntax.
- id: check-yaml # This hook checks yaml files for parseable syntax.
- id: debug-statements # Check for debugger imports and py37+ `breakpoint()` calls in python source.
- id: destroyed-symlinks # Detects symlinks which are changed to regular files with a content of a path which that symlink was pointing to.
- id: detect-private-key # Detects the presence of private keys
- id: end-of-file-fixer # Ensures that a file is either empty, or ends with one newline.
- id: fix-byte-order-marker # Removes UTF-8 byte order marker
- id: forbid-new-submodules # Prevent addition of new git submodules
- id: mixed-line-ending # Replaces or checks mixed line ending
- id: requirements-txt-fixer # Sorts entries in requirements.txt
- id: sort-simple-yaml # Sorts simple YAML files which consist only of top-level keys, preserving comments and blocks.
- id: trailing-whitespace # This hook trims trailing whitespace.

- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black

- repo: https://github.com/timothycrosley/isort
rev: 5.10.1
hooks:
- id: isort

- repo: https://gitlab.com/pycqa/flake8
rev: 4.0.1
hooks:
- id: flake8

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.931
hooks:
- id: mypy
Empty file added HISTORY.md
Empty file.
21 changes: 21 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2022 Joshua Taylor Eppinette

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.
104 changes: 104 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Python SSSD LDAP Auth

_A Python package which supports deobfuscating LDAP passwords
contained in (System Security Services Daemon) sssd.conf files._

## Inspiration

- [Michael Ludvig](https://github.com/mludvig)'s [sss_deobfuscate](https://github.com/mludvig/sss_deobfuscate) script.
- [SSSD](https://github.com/SSSD/sssd)'s [/src/util/crypto/libcrypto/crypto_obfuscate.c](https://github.com/SSSD/sssd/blob/master/src/util/crypto/libcrypto/crypto_obfuscate.c) source file.

## Features

- Type Hints / Editor Completion
- Readable
- Fully Tested
- Python 3.6 - 3.10 Support

## Install

```sh
$ pip install sssdldapauth
```

## Usage

```python
from sssdldapauth import deobfuscate

password = deobfuscate("<obfuscated_password>")
```

## Development

### Required Software

Refer to the links provided below to install these development dependencies:

- [direnv](https://direnv.net)
- [git](https://git-scm.com/)
- [pyenv](https://github.com/pyenv/pyenv#installation)

### Getting Started

**Setup**

```sh
$ <runtimes.txt xargs -n 1 pyenv install -s
$ direnv allow
$ pip install -r requirements/dev.txt
$ pre-commit install
$ pip install -e .
```

**Tests**

_Run the test suite against the active python environment._

```sh
$ pytest
```

_Run the test suite against the active python environment and watch the codebase
for any changes._

```sh
$ ptw
```

_Run the test suite against all supported python versions._

```sh
$ tox
```

### Publishing

**Create**

1. Update the version number in `sssdldapauth/__init__.py`.

2. Add an entry in `HISTORY.md`.

3. Commit the changes, tag the commit, and push the tags:

```sh
$ git commit -am "v<major>.<minor>.<patch>"
$ git tag v<major>.<minor>.<patch>
$ git push origin main --tags
```

4. Convert the tag to a release in GitHub with the history entry as the
description.

**Build**

```sh
$ python -m build
```

**Upload**

```
$ twine upload dist/*
```
10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[tool.black]
target-version = ["py310"]

[tool.isort]
profile = "black"
sections = "FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER"
9 changes: 9 additions & 0 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-r ./publish.txt
-r ./test.txt
black==22.3.0
flake8==4.0.1
isort==5.10.1
mypy==0.931
pre-commit==2.17.0
pytest-watch==4.2.0
tox==3.24.5
2 changes: 2 additions & 0 deletions requirements/publish.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build==0.7.0
twine==3.8.0
1 change: 1 addition & 0 deletions requirements/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pytest==7.0.0
1 change: 1 addition & 0 deletions runtimes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.6.15 3.7.12 3.8.11 3.9.10 3.10.1
20 changes: 20 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[metadata]
name = sssdldapauth
version = attr: sssdldapauth.__version__
description = Supports deobfuscating LDAP passwords contained in (System Security Services Daemon) sssd.conf files.
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/jteppinette/python-sssd-ldap-auth
author = Joshua Taylor Eppinette
author_email = [email protected]
license = MIT

[options]
packages = find:
python_requires = >= 3.6
include_package_data = True
install_requires=
cryptography

[options.package_data]
sssdldapauth = py.typed
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from setuptools import setup

setup()
5 changes: 5 additions & 0 deletions sssdldapauth/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# flake8: noqa

from sssdldapauth.deobfuscate import deobfuscate

__version__ = "0.0.0"
71 changes: 71 additions & 0 deletions sssdldapauth/deobfuscate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import base64
import struct
from operator import itemgetter
from typing import Optional, Tuple, Union

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

METHODS = [
{
"algo": algorithms.AES,
"mode": modes.CBC,
"key_len": 32,
"iv_len": 16,
}
]

PopResult = Tuple[bytes, Union[bytes, Tuple]]


def pop(binary: bytes, length: int, unpack: Optional[str] = None) -> PopResult:
"""
Return the remainding binary data and the requested binary segment.
If the unpack keyword arguent is provided, then the requested segment
will be unpacked accordingly. i.e. The unpack argument will be used as
the unpack format. When using the unpack functionality, the second
tuple index will be a tuple of unpacked items.
"""
if len(binary) < length:
raise Exception("requested segment too large")

segment = binary[:length]
remainder = binary[length:]

if unpack:
return remainder, struct.unpack(unpack, segment)

return remainder, segment


def deobfuscate(token: str) -> str:
"""
Consume an obfuscated token and return the unobfuscated password.
"""
binary = base64.b64decode(token)

binary, (method, ciphertext_len) = pop(binary, 4, "HH")

try:
properties = METHODS[method]
except IndexError:
raise Exception("unsupported method")

algo, mode, key_len, iv_len = itemgetter("algo", "mode", "key_len", "iv_len")(
properties
)

binary, encryption_key = pop(binary, key_len)
binary, iv = pop(binary, iv_len)
binary, ciphertext = pop(binary, ciphertext_len)

cipher = Cipher(algo(encryption_key), mode(iv), backend=default_backend())
decryptor = cipher.decryptor()
decrypted = decryptor.update(ciphertext) + decryptor.finalize()

# Anything after \x00 can be thrown away.
password_binary = decrypted.split(b"\x00")[0]

# UTF-8 passwords are supported (and any subset encodings e.g. ASCII).
return password_binary.decode("utf-8")
Empty file added sssdldapauth/py.typed
Empty file.
19 changes: 19 additions & 0 deletions tests/test_deobfuscate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import pytest

from sssdldapauth.deobfuscate import deobfuscate

OBFUSCATED = (
"AAAQABagVAjf9KgUyIxTw3A+HUfbig7N1+L0qtY4xAULt2GY"
"HFc1B3CBWGAE9ArooklBkpxQtROiyCGDQH+VzLHYmiIAAQID"
)
DEOBFUSCATED = "Passw0rd"


def test_valid():
assert deobfuscate(OBFUSCATED) == DEOBFUSCATED


@pytest.mark.parametrize("token", ["invalid", "", None])
def test_invalid(token):
with pytest.raises(Exception):
deobfuscate(token)
6 changes: 6 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[tox]
envlist = py36,py37,py38,py39,py310

[testenv]
deps = pytest
commands = pytest {posargs}

0 comments on commit f1357c9

Please sign in to comment.