jborean93 / pyspnego

Python SPNEGO authentication library
MIT License
52 stars 11 forks source link

SMTP GSSAPI Kerberos Auth - any hints? #88

Closed epruesse closed 1 month ago

epruesse commented 2 months ago

I'm trying to Kerberos auth to an MS Exchange email server. The auth seems to stop before the other side is truly happy. See below. I create the first token, send it over and feed it back into auth.step(). At this point, the pyspnego client object is "complete" and happy. The other end is not. I can send an empty line, to which it responds with a wrapped message containing 4 bytes, expecting something to be done next. It's not asking it to accept an email, that will lead to "auth failed", so the auth hasn't really actually completed yet.

Any insights much appreciated.

server = smtplib.SMTP(hostname, 587)
server.set_debuglevel(1)
server.ehlo()

creds = spnego.KerberosCCache(ccache="FILE:/tmp/krb5cc_1000")
auth = spnego.client(creds, service="SMTP", protocol="kerberos", hostname=hostname)
token = auth.step()
status, resp = server.docmd("AUTH", "GSSAPI")
if status != 334:
    raise  smtplib.SMTPException("Auth failed")
status, resp = server.docmd("", base64.b64encode(token).decode())
if status != 334:
    raise  smtplib.SMTPException("Auth failed")
token = auth.step(base64.b64decode(resp))

print(f"last token: {token}") ## None
print(f"auth complete: {auth.complete}") ## True
print(f"protocol: {auth.negotiated_protocol}") ## Kerberos

status, resp = server.docmd("", "")

answer = auth.unwrap(base64.b64decode(resp))
# answer.data is always b'\x01\x00\xbb\x80' - but it's unclear what this means. 
jborean93 commented 2 months ago

SMTP uses the GSSAPI SASL mech for authentication which is GSSAPI auth but with some extra work on top of the authentication process. Those 4 bytes you see from the server is the signing/sealing negotiation settings done after authentication. You can see the documentation for GSSAPI over SASL in RFC 2222. These bytes are also wrapped/encrypted by the security session hence the need to call auth.unwrap.

Those 4 bytes are stated under

the server then constructs 4 octets of data, with the first octet containing a bit-mask specifying the security layers supported by the server and the second through fourth octets containing in network byte order the maximum size output_token the server is able to receive

Essentially once the auth is complete the server will reply with the wrapped 4 bytes being

Upon receiving the SASL security negotation token the client is meant to reply with it's own 4 bytes with what it supports. In your case if you don't offer encryption support (no idea if SMTP even offers this) then you need to do something like

if answer[0] & 1 == 0:
    raise Exception("Server does not offer no security layer")

response = answer
response[0] = 1  # Unset any other flag and don't offer any security
response_token = base64.b64encode(auth.wrap(response).data)

# send response_token back to the server

While this is not in Python here is an example of SMTP authentication I had working in a C#/PowerShell application https://github.com/EvotecIT/Mailozaurr/pull/43/files.

Here is an example of SASL auth as used in LDAP https://github.com/jborean93/sansldap/blob/b1a20c7ae17240cc747c8fe49228284ad47473d5/tests/examples/sasl.py#L205-L241. It does a lot more work as it supports signing and encryption so I'm validating the result that way. Hopefully that helps.

epruesse commented 2 months ago

Thank you @jborean93! That's exactly what I was looking for. I'd seen that RFC, but skipped it since it mentioned IMAP specifically, but no reference to SMTP. Your help is greatly appreciated!

jborean93 commented 2 months ago

Glad it worked, was there any other issues?

epruesse commented 1 month ago

No, that was all. Thank you again.