Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SASL SCRAM-SHA-256 #41

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
author_email='[email protected]',
license='GNU',
packages=find_packages(),
install_requires=['six'],
install_requires=['six', 'pyxmpp2_scram>=2.0.2'],
extras_require={
'pysocks': ['pysocks'],
'pysocks:sys_platform=="win32" and python_version == "2.7"': ['win_inet_pton']
Expand Down
1 change: 1 addition & 0 deletions zirc/ext/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import sasl
27 changes: 23 additions & 4 deletions zirc/ext/sasl.py → zirc/ext/sasl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# SASL authentication for zirc

import base64
from ..errors import SASLError
from . import scram
from ...errors import SASLError

class Sasl(object):
name = "sasl"
Expand All @@ -11,18 +12,19 @@ def __init__(self, username, password, method="plain"):
self.password = password
self.method = method
self.retries = 0
self.sasl_scram_state = {'step': 'uninitialized'}

def run(self, bot, args=None):
if args is None:
mechanisms = ["EXTERNAL", "PLAIN"]
mechanisms = ["SCRAM-SHA256-PLUS", "SCRAM-SHA256", "EXTERNAL", "PLAIN"]
else:
mechanisms = args
self.bot = bot
bot.listen(self.on_authenticate, "authenticate")
bot.listen(self.on_saslfailed, "saslfailed")
bot.listen(self.on_saslsuccess, "saslsuccess")
if self.method.upper() in mechanisms:
if self.method in ["plain", "external"]:
if self.method in ["scram-sha256-plus", "scram-sha256", "plain", "external"]:
bot.send("AUTHENTICATE " + self.method.upper())
else:
raise SASLError("Not implemented yet")
Expand All @@ -35,12 +37,29 @@ def on_authenticate(self, event):
password = base64.b64encode("{0}\x00{0}\x00{1}".format(self.username, self.password).encode("UTF-8")).decode("UTF-8")
elif self.method == 'external':
password = "+"
elif self.method.startswith('scram-sha'):
scram.doAuthenticateScramFirst(self, self.method)
self.bot.send("AUTHENTICATE {0}".format(password))
else:
step = self.sasl_scram_state['step']
string = event.arguments[0]
try:
if step == 'first-sent':
scram.doAuthenticateScramChallenge(self, string)
elif step == 'final-sent':
scram.doAuthenticateScramFinish(self, string)
elif step == "authenticated":
self.bot.send("AUTHENTICATE +")
else:
assert False
except scram.ScramException:
bot.send('AUTHENTICATE *')
self.retries += 2

def on_saslfailed(self, event):
self.retries += 1
if self.method == 'external':
if self.retries == 2:
if self.retries >= 2:
self.retries = 1
self.method = 'plain'
self.bot.send("AUTHENTICATE PLAIN")
Expand Down
33 changes: 33 additions & 0 deletions zirc/ext/sasl/scram.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pyxmpp2_scram as scram

def doAuthenticateScramFirst(self, mechanism):
"""Handle sending the client-first message of SCRAM auth."""
hash_name = mechanism[len('scram-'):]
if hash_name.endswith('-plus'):
hash_name = hash_name[:-len('-plus')]
hash_name = hash_name.upper()
if hash_name not in scram.HASH_FACTORIES:
self.retries += 2
return
authenticator = scram.SCRAMClientAuthenticator(hash_name, channel_binding=False)
self.sasl_scram_state['authenticator'] = authenticator
client_first = authenticator.start({
'username': self.sasl_username,
'password': self.sasl_password,
})
self.bot.send("AUTHENTICATE {0}".format(client_first))
self.sasl_scram_state['step'] = 'first-sent'

def doAuthenticateScramChallenge(self, challenge):
client_final = self.sasl_scram_state['authenticator'].challenge(challenge)
self.bot.send("AUTHENTICATE {0}".format(client_final))
self.sasl_scram_state['step'] = 'final-sent'

def doAuthenticateScramFinish(self, data):
try:
res = self.sasl_scram_state['authenticator'].finish(data)
except scram.BadSuccessException:
self.retries += 2
else:
self.sasl_scram_state['step'] = 'authenticated'
self.bot.send("AUTHENTICATE +")