Skip to main content

Pure Python SASL client and server library.

Project description

pysasl

Pure Python SASL client and server library. The design of the library is intended to be agnostic of the protocol or network library.

The library currently offers PLAIN and LOGIN mechanisms by default. The CRAM-MD5, EXTERNAL, and XOAUTH2 mechanisms are also available for special circumstances.

There are currently no plans to implement security layer negotiation support.

build PyPI PyPI platforms PyPI

API Documentation

Installation

Available in PyPi:

$ pip install pysasl

Running Tests

First, install Hatch. To run the build checks:

$ hatch run check

$ hatch run all:check  # to run against all supported Python versions

Usage

Server-side

Server-side SASL has three basic requirements:

  • Must advertise supported mechanisms,
  • Must issue authentication challenges to the client and read responses,
  • Must determine if credentials are considered valid.

Advertising Mechanisms

Implementations may decide on any sub-set of mechanisms to advertise. Make this choice when instantiating the SASLAuth object:

from pysasl import SASLAuth

auth1 = SASLAuth.defaults()  # equivalent to...
auth2 = SASLAuth.named([b'PLAIN', b'LOGIN'])

To get the names of all available mechanisms:

mechanisms = [mech.name for mech in auth1.server_mechanisms]
mech = auth1.get_server(b'PLAIN')

Issuing Challenges

Once a mechanism has been chosen by the client, enter a loop of issuing challenges to the client:

def server_side_authentication(sock, mech):
    challenges = []
    while True:
        try:
            creds, _ = mech.server_attempt(challenges)
            return creds
        except ServerChallenge as chal:
            sock.send(chal.data + b'\r\n')
            resp = sock.recv(1024).rstrip(b'\r\n')
            challenges.append(ChallengeResponse(chal.data, resp))

It's worth noting that implementations are not quite that simple. Most will expect all transmissions to base64-encoded, often with a prefix before the server challenges such as 334 or +. See the appropriate RFC for your protocol, such as RFC 4954 for SMTP or RFC 3501 for IMAP.

Checking Credentials

Once the challenge-response loop has been completed and we are left with the a ServerCredentials object, we can access information from the attempt:

from pysasl.creds.server import ServerCredentials
from pysasl.identity import ClearIdentity, HashedIdentity

result: ServerCredentials = ...

print('Authenticating as:', result.authcid)
print('Authorizing as:', result.authzid)

# To compare to a known cleartext password...
identity = ClearIdentity('myuser', 's3kr3t')
assert result.verify(identity)

# Or to compare hashes...
from pysasl.hashing import BuiltinHash
identity = HashedIdentity('myuser, '$pbkdf2-sha256$500000$...', hash=BuiltinHash())
assert result.verify(identity)

# Or use passlib hashing...
from passlib.apps import custom_app_context
identity = HashedIdentity('myuser', '$6$rounds=656000$...', hash=custom_app_context)
assert result.verify(identity)

Client-side

The goal of client-side authentication is to respond to server challenges until the authentication attempt either succeeds or fails.

Choosing a Mechanism

The first step is to pick a SASL mechanism. The protocol should allow the server to advertise to the client which mechanisms are available to it:

from pysasl import SASLAuth

auth = SASLAuth.named(advertised_mechanism_names)
mech = auth.client_mechanisms[0]

Any mechanism name that is not recognized will be ignored.

Issuing Responses

Once a mechanism is chosen, we enter of a loop of responding to server challenges:

from pysasl.creds.client import ClientCredentials

def client_side_authentication(sock, mech, username, password):
    creds = ClientCredentials(username, password)
    challenges = []
    while True:
        resp = mech.client_attempt(creds, challenges)
        sock.send(resp + b'\r\n')
        data = sock.recv(1024).rstrip(b'\r\n')
        if data == 'SUCCESS':
            return True
        elif data == 'FAILURE':
            return False
        challenges.append(ServerChallenge(data))

As you might expect, a real protocol probably won't return SUCCESS or FAILURE, that will depend entirely on the details of the protocol.

Supporting Initial Responses

Some protocols (e.g. SMTP) support the client ability to send an initial response before the first server challenge, for mechanisms that support it. A perfectly valid authentication can then have no challenges at all:

AUTH PLAIN AHVzZXJuYW1lAHBhc3N3b3Jk
235 2.7.0 Authentication successful

In this case, both client-side and server-side authentication should be handled a bit differently. For example for server-side:

challenges = []
if initial_response:
    challenges.append(ChallengeResponse(b'', initial_response))

And for client-side, just call resp = mech.client_attempt(creds, []) to get the initial response before starting the transmission. All mechanisms should either return an initial response or an empty string when given an empty list for the second argument.

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

pysasl-1.2.0.tar.gz (16.7 kB view details)

Uploaded Source

Built Distribution

pysasl-1.2.0-py3-none-any.whl (21.5 kB view details)

Uploaded Python 3

File details

Details for the file pysasl-1.2.0.tar.gz.

File metadata

  • Download URL: pysasl-1.2.0.tar.gz
  • Upload date:
  • Size: 16.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-httpx/0.24.1

File hashes

Hashes for pysasl-1.2.0.tar.gz
Algorithm Hash digest
SHA256 5141cd8cfa0b3667ec114aded60fb17313496ba10603ffdbd7feeac7146ae7a1
MD5 48e02b241eb635d8d7bbe043c2e32516
BLAKE2b-256 a2810f67bed0f17a2ce439b0cd9daad856791f471134a93deef4677095e98368

See more details on using hashes here.

File details

Details for the file pysasl-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: pysasl-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 21.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-httpx/0.24.1

File hashes

Hashes for pysasl-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4bd65b0342f2a04343501d3d192faf20a09a39b3412f3d383fd6437d62f96fe4
MD5 be26d12813396203171b9c11e2f55f57
BLAKE2b-256 82fcd523b85287bac9e86d9134107b831e6a0a5317e3e92bb52c56e3900f32af

See more details on using hashes here.

Supported by

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