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.3.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.3-py3-none-any.whl (12.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ecp_lib-1.0.3.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.3.tar.gz
Algorithm Hash digest
SHA256 1b64b499d0b9ad2527c280437a264e71cb24785a71580aa250c238368d967380
MD5 99ae96bdbb27c04f13aed07cd4c660ea
BLAKE2b-256 781ef63c2a836ddc857f6aab69a632362c03d1764e033ac5ada589116c6e4694

See more details on using hashes here.

Provenance

The following attestation bundles were made for ecp_lib-1.0.3.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.3-py3-none-any.whl.

File metadata

  • Download URL: ecp_lib-1.0.3-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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 dd8ea89ff3e89ecf6f100f4a72c4b9ed518577c307c3b89f6708626d5f8540c7
MD5 7b1952d9c427aafe98cffa3f0e2d4fc9
BLAKE2b-256 28d384998b396f5f259a309f774bdf677c45707ea31b26eda3a531ac541db383

See more details on using hashes here.

Provenance

The following attestation bundles were made for ecp_lib-1.0.3-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