Skip to main content

Django RSA private-key authentication helpers and middleware

Project description

ecp-lib

ecp-lib is a Django library for authentication using username + password + private.pem. It generates RSA keys, stores the user’s public_key, and provides middleware that verifies the uploaded private key matches the key in the database.

What the library can do

  • generate a private_key/public_key pair
  • store the public_key in the ECPKey model
  • read private.pem from upload
  • validate the username/password/private_key combination
  • reject invalid login requests at the middleware level
  • log whether the request reached the middleware at all and at which step it failed

What the library does not do

  • does not provide ready-made views or urls
  • does not log in the user by itself
  • does not store private.pem on the server
  • does not implement a challenge-response protocol

Installation

pip install ecp-lib
pip install "ecp-lib[django]"

Public API

from ecp_lib import (
    ECPKey,
    ECPMiddleware,
    authenticate_with_private_key,
    create_challenge,
    create_user_keys,
    generate_keys,
    read_private_key,
    sanitize,
    sign,
    validate_public_key,
    verify,
)

The main Django-flow uses:

Main Django flow

Main flow

1. Registration

After creating a user, call create_user_keys(user). The function:

  1. generates a new RSA key pair
  2. stores the public_key in ECPKey
  3. returns the private_key as a PEM string

Typical approach: provide this PEM to the user as a private.pem file.

from django.http import HttpResponse

from ecp_lib.auth import create_user_keys


def registration_success(request, user):
    private_key = create_user_keys(user)

    response = HttpResponse(private_key, content_type="application/x-pem-file")
    response["Content-Disposition"] = 'attachment; filename="private.pem"'
    return response

2. Login

The login form submits:

  • username
  • password
  • a private_key or private_key_file

In the view, you can read the PEM and verify it using the helper:

from django.contrib.auth import login
from django.shortcuts import redirect, render

from ecp_lib.auth import authenticate_with_private_key, read_private_key


def login_view(request):
    if request.method == "POST":
        private_key = read_private_key(request.FILES["private_key_file"])

        user, error = authenticate_with_private_key(
            request=request,
            username=request.POST["username"],
            password=request.POST["password"],
            private_key=private_key,
        )

        if error:
            return render(request, "login.html", {"error": error}, status=400)

        login(request, user)
        return redirect("dashboard")

    return render(request, "login.html")

authenticate_with_private_key(...):

  1. validates the input data
  2. checks username/password using Django’s authenticate()
  3. retrieves the user’s public_key from ECPKey
  4. creates an internal payload
  5. signs it with the provided private_key
  6. verifies the signature using the stored public_key

Returns:

  • (user, None) on success
  • (None, "error text") on failure

Middleware

ecp_lib/middleware.py acts as an early guard for POST requests.

The middleware triggers when it detects:

  • username
  • password
  • private_key as a text field or a private_key file
  • or a private_key_file

Supported content types:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • application/json

What it does:

  1. checks that the request is a POST
  2. reads username, password, and the private key
  3. retrieves the user’s public_key from the database
  4. generates a signature using the provided private key
  5. verifies the signature with the public_key
  6. returns 403 on failure
  7. allows the request to proceed to the view on success

Important:

  • the middleware does not create a user session
  • the middleware does not replace django.contrib.auth.login
  • the middleware only blocks invalid requests before they reach the view

Django integration

In settings.py:

INSTALLED_APPS = [
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "ecp_lib",
]

MIDDLEWARE = [
    "...",
    "ecp_lib.middleware.ECPMiddleware",
]

After that, run the migrations:

python manage.py migrate

Middleware Logging

To see if the middleware is triggered, add a logger in settings.py:

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
    },
    "loggers": {
        "ecp_lib.middleware": {
            "handlers": ["console"],
            "level": "DEBUG",
            "propagate": False,
        },
    },
}

The logs will show:

  • that the request reached the middleware
  • why the middleware allowed the request
  • why the middleware rejected the request
  • whether the verification succeeded

Cryptographic Helpers

generate_keys()

from ecp_lib.crypto import generate_keys

private_key, public_key = generate_keys()
  • returns PEM strings
  • generates only RSA keys
  • minimum key length: 2048

sign() & verify()

from ecp_lib.crypto import sign, verify

signature = sign(private_key, "hello")
is_valid = verify(public_key, "hello", signature)

The library uses RSA-PSS with SHA-256.

create_challenge() & verify_challenge()

These are auxiliary helpers for testing or local verification of key pairs. They are not part of the main login flow via the HTML form.

Validation

ecp_lib/validators.py contains:

  • sanitize(value)
  • validate_username(username)
  • validate_public_key(public_key)

It validates:

  • the type and non-emptiness of the value
  • absence of dangerous control characters
  • PEM format of the public_key
  • RSA key type
  • minimum key length of 2048

Model

ECPKey stores:

  • user as OneToOneField;
  • public_key as TextField;
  • created_at as DateTimeField(auto_now_add=True).

Only the public_key is stored on the server.

Tests

Run:

pytest -q

Coverage:

  • key generation
  • signing and verification
  • storing the public_key
  • reading private.pem
  • helpers from auth.py
  • middleware for form POST
  • middleware for JSON POST

Security

  • do not store private.pem in the database
  • provide the private.pem to the user only once after registration
  • ensure only a valid public_key is stored in the database
  • use HTTPS, since the password and key file are sent to the server
  • place the middleware as an early barrier, but not as a replacement for verification in the view

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ecp_lib-1.0.2.tar.gz (15.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ecp_lib-1.0.2-py3-none-any.whl (12.2 kB view details)

Uploaded Python 3

File details

Details for the file ecp_lib-1.0.2.tar.gz.

File metadata

  • Download URL: ecp_lib-1.0.2.tar.gz
  • Upload date:
  • Size: 15.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for ecp_lib-1.0.2.tar.gz
Algorithm Hash digest
SHA256 57557d954c2ca8aaa65de8429848e2ff002dfc7f153ff6f1d5a38ee3e1211802
MD5 cf0e62a8fe7da026005ce7bc07f4340f
BLAKE2b-256 1109db023b831b1bbd0c1bf4d40cff5c28a33fff8245c8e6b9aae709af9b171b

See more details on using hashes here.

Provenance

The following attestation bundles were made for ecp_lib-1.0.2.tar.gz:

Publisher: publish-pypi.yml on T0ks1k24/django-pub-sub

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ecp_lib-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: ecp_lib-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 12.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for ecp_lib-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0c04b92d86bf723ae9c6a8e0a1669e5dc0b92657b416e880080118b335b0a53b
MD5 0aef8a64929f6196a7d37aaaf9720036
BLAKE2b-256 28dbe49a7582d9af3cdab9f6863c154d480fbb79de5edd44f098f38ff775572f

See more details on using hashes here.

Provenance

The following attestation bundles were made for ecp_lib-1.0.2-py3-none-any.whl:

Publisher: publish-pypi.yml on T0ks1k24/django-pub-sub

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page