• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

pysasl/H07-May-2022-985720

pysasl.egg-info/H03-May-2022-248185

test/H29-Nov-2021-400325

LICENSE.mdH A D29-Nov-20211.1 KiB2317

MANIFEST.inH A D29-Nov-202145 21

PKG-INFOH A D29-Nov-20217.7 KiB248185

README.mdH A D29-Nov-20215.9 KiB201146

setup.cfgH A D29-Nov-2021170 1511

setup.pyH A D29-Nov-20212.6 KiB6035

README.md

1pysasl
2======
3
4Pure Python SASL client and server library. The design of the library is
5intended to be agnostic of the protocol or network library.
6
7The library currently offers `PLAIN`, `LOGIN`, and `CRAM-MD5` mechanisms by
8default. The `EXTERNAL` and `XOAUTH2` mechanisms are also available for special
9circumstances.
10
11There are currently no plans to implement security layer negotiation support.
12
13[![build](https://github.com/icgood/pysasl/actions/workflows/python-package.yml/badge.svg)](https://github.com/icgood/pysasl/actions/workflows/python-package.yml)
14[![Coverage Status](https://coveralls.io/repos/icgood/pysasl/badge.svg?branch=main)](https://coveralls.io/r/icgood/pysasl?branch=main)
15[![PyPI](https://img.shields.io/pypi/v/pysasl.svg)](https://pypi.python.org/pypi/pysasl)
16[![PyPI](https://img.shields.io/pypi/pyversions/pysasl.svg)](https://pypi.python.org/pypi/pysasl)
17[![PyPI](https://img.shields.io/pypi/l/pysasl.svg)](https://pypi.python.org/pypi/pysasl)
18
19#### [API Documentation](https://icgood.github.io/pysasl/)
20
21Installation
22============
23
24Available in [PyPi](https://pypi.python.org/):
25
26```
27pip install pysasl
28```
29
30### Running Tests
31
32Install into a virtual environment:
33
34```
35python3 -m venv .venv
36source .venv/bin/activate
37
38pip install -r test/requirements.txt
39pip install -e .
40```
41
42Run the tests and report coverage metrics:
43
44```
45py.test --cov=pysasl
46```
47
48Usage
49=====
50
51## Server-side
52
53Server-side SASL has three basic requirements:
54
55* Must advertise supported mechanisms,
56* Must issue authentication challenges to the client and read responses,
57* Must determine if credentials are considered valid.
58
59#### Advertising Mechanisms
60
61Implementations may decide on any sub-set of mechanisms to advertise. Make this
62choice when instantiating the [`SASLAuth`][1] object:
63
64```python
65from pysasl import SASLAuth
66
67auth1 = SASLAuth.defaults()  # equivalent to...
68auth2 = SASLAuth.named([b'PLAIN', b'LOGIN'])
69```
70
71To get the names of all available mechanisms:
72
73```python
74mechanisms = [mech.name for mech in auth1.server_mechanisms]
75mech = auth1.get(b'PLAIN')
76```
77
78#### Issuing Challenges
79
80Once a mechanism has been chosen by the client, enter a loop of issuing
81challenges to the client:
82
83```python
84def server_side_authentication(sock, mech):
85    challenges = []
86    while True:
87        try:
88            creds, _ = mech.server_attempt(challenges)
89            return creds
90        except ServerChallenge as chal:
91            sock.send(chal.data + b'\r\n')
92            resp = sock.recv(1024).rstrip(b'\r\n')
93            challenges.append(ChallengeResponse(chal.data, resp))
94```
95
96It's worth noting that implementations are not quite that simple. Most will
97expect all transmissions to base64-encoded, often with a prefix before the
98server challenges such as `334` or `+`. See the appropriate RFC for your
99protocol, such as [RFC 4954 for SMTP][3] or [RFC 3501 for IMAP][4].
100
101#### Checking Credentials
102
103Once the challenge-response loop has been completed and we are left with the
104a [`AuthenticationCredentials`][2] object, we can access information from the
105attempt:
106
107```python
108from pysasl.creds import StoredSecret
109
110print('Authenticated as:', result.authcid)
111print('Authorization ID:', result.authzid)
112print('Assumed identity:', result.identity)
113
114# To compare to a known password...
115assert result.check_secret(StoredSecret('s3kr3t'))
116
117# Or to compare hashes...
118from pysasl.hashing import BuiltinHash
119secret = StoredSecret('1baa33d03d0...', hash=BuiltinHash())
120assert result.check_secret(secret)
121
122# Or use passlib hashing...
123from passlib.apps import custom_app_context
124secret = StoredSecret('$6$rounds=656000$...', hash=custom_app_context)
125assert result.check_secret(secret)
126```
127
128## Client-side
129
130The goal of client-side authentication is to respond to server challenges until
131the authentication attempt either succeeds or fails.
132
133#### Choosing a Mechanism
134
135The first step is to pick a SASL mechanism. The protocol should allow the server
136to advertise to the client which mechanisms are available to it:
137
138```python
139from pysasl import SASLAuth
140
141auth = SASLAuth.named(advertised_mechanism_names)
142mech = auth.client_mechanisms[0]
143```
144
145Any mechanism name that is not recognized will be ignored.
146
147#### Issuing Responses
148
149Once a mechanism is chosen, we enter of a loop of responding to server
150challenges:
151
152```python
153from pysasl.creds import AuthenticationCredentials
154
155def client_side_authentication(sock, mech, username, password):
156    creds = AuthenticationCredentials(username, password)
157    challenges = []
158    while True:
159        resp = mech.client_attempt(creds, challenges)
160        sock.send(resp + b'\r\n')
161        data = sock.recv(1024).rstrip(b'\r\n')
162        if data == 'SUCCESS':
163            return True
164        elif data == 'FAILURE':
165            return False
166        challenges.append(ServerChallenge(data))
167```
168
169As you might expect, a real protocol probably won't return `SUCCESS` or
170`FAILURE`, that will depend entirely on the details of the protocol.
171
172## Supporting Initial Responses
173
174Some protocols (e.g. SMTP) support the client ability to send an initial
175response before the first server challenge, for mechanisms that support it.
176A perfectly valid authentication can then have no challenges at all:
177
178```
179AUTH PLAIN AHVzZXJuYW1lAHBhc3N3b3Jk
180235 2.7.0 Authentication successful
181```
182
183In this case, both client-side and server-side authentication should be
184handled a bit differently. For example for server-side:
185
186```python
187challenges = []
188if initial_response:
189    challenges.append(ChallengeResponse(b'', initial_response))
190```
191
192And for client-side, just call `resp = mech.client_attempt(creds, [])`
193to get the initial response before starting the transmission. All
194mechanisms should either return an initial response or an empty string
195when given an empty list for the second argument.
196
197[1]: https://icgood.github.io/pysasl/pysasl.html#pysasl.SASLAuth
198[2]: https://icgood.github.io/pysasl/pysasl.html#pysasl.creds.AuthenticationCredentials
199[3]: https://tools.ietf.org/html/rfc4954
200[4]: https://tools.ietf.org/html/rfc3501#section-6.2.2
201