From ef0a2c1cb19cf4df3d42d7fb295c07082bd5b347 Mon Sep 17 00:00:00 2001 From: Max Teufel Date: Sat, 27 Dec 2014 18:39:38 +0100 Subject: [PATCH] irclib: add support for SASL ECDSA-NIST256P-CHALLENGE Closes #911 --- requirements.txt | 1 + src/conf.py | 4 ++++ src/irclib.py | 34 +++++++++++++++++++++++++++++++--- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index a23bd4938..3fa312f09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ feedparser sqlalchemy SocksiPy-branch mock +ecdsa diff --git a/src/conf.py b/src/conf.py index 3f1bcf386..793749828 100644 --- a/src/conf.py +++ b/src/conf.py @@ -344,6 +344,10 @@ def registerNetwork(name, password='', ssl=False, sasl_username='', registerGlobalValue(sasl, 'password', registry.String(sasl_password, _("""Determines what SASL password will be used on %s.""") \ % name, private=True)) + registerGlobalValue(sasl, 'ecdsa_key', registry.String('', + _("""Determines what SASL ECDSA key (if any) will be used on %s. + The public key must be registered with NickServ for SASL + ECDSA-NIST256P-CHALLENGE to work.""") % name, private=False)) registerGlobalValue(network, 'socksproxy', registry.String('', _("""If not empty, determines the hostname of the socks proxy that will be used to connect to this network."""))) diff --git a/src/irclib.py b/src/irclib.py index 7a1bc6e92..007774a54 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -33,6 +33,12 @@ import time import random import base64 +try: + from ecdsa import SigningKey, BadDigestError + ecdsa = True +except ImportError: + ecdsa = False + from . import conf, ircdb, ircmsgs, ircutils, log, utils, world from .utils.str import rsplit from .utils.iter import imap, chain, cycle @@ -924,6 +930,11 @@ class Irc(IrcCommandDispatcher): # TODO Find a better way to fix this if hasattr(self.sasl_password, 'decode'): self.sasl_password = self.sasl_password.decode('utf-8') + self.sasl_ecdsa_key = \ + conf.supybot.networks.get(self.network).sasl.ecdsa_key() + # TODO Find a better way to fix this + if hasattr(self.sasl_ecdsa_key, 'decode'): + self.sasl_ecdsa_key = self.sasl_ecdsa_key.decode('utf-8') self.prefix = '%s!%s@%s' % (self.nick, self.ident, 'unset.domain') # The rest. self.lastTake = 0 @@ -943,7 +954,9 @@ class Irc(IrcCommandDispatcher): self.sasl = None - if (conf.supybot.networks.get(self.network).certfile() or + if ecdsa and self.sasl_username and self.sasl_ecdsa_key: + self.sasl = 'ecdsa-nist256p-challenge' + elif (conf.supybot.networks.get(self.network).certfile() or conf.supybot.protocols.irc.certfile()): self.sasl = 'external' elif self.sasl_username and self.sasl_password: @@ -980,6 +993,9 @@ class Irc(IrcCommandDispatcher): if self.sasl == 'external': authstring = '+' + elif self.sasl == 'ecdsa-nist256p-challenge': + authstring = base64.b64encode( + self.sasl_username.encode('utf-8')).decode('utf-8') elif self.sasl == 'plain': authstring = base64.b64encode('\0'.join([ self.sasl_username, @@ -988,6 +1004,17 @@ class Irc(IrcCommandDispatcher): ]).encode('utf-8')).decode('utf-8') self.queueMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=(authstring,))) + elif (len(msg.args) == 1 and msg.args[0] != '+' and + self.sasl == 'ecdsa-nist256p-challenge'): + try: + private_key = SigningKey.from_pem(open(self.sasl_ecdsa_key). + read()) + authstring = base64.b64encode( + private_key.sign(base64.b64decode(msg.args[0]))).decode('utf-8') + except (BadDigestError, OSError, ValueError) as e: + authstring = "*" + + self.queueMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=(authstring,))) def doCap(self, msg): if len(msg.args) == 3: @@ -1009,9 +1036,10 @@ class Irc(IrcCommandDispatcher): self.queueMsg(ircmsgs.IrcMsg(command='CAP', args=('END',))) def do904(self, msg): - if (self.sasl == 'external' and self.sasl_username and + if (self.sasl != 'plain' and self.sasl_username and self.sasl_password): - log.info('%s: SASL EXTERNAL failed, trying PLAIN.', self.network) + log.info('%s: SASL %s failed, trying PLAIN.', self.network, + self.sasl.upper()) self.sasl = 'plain'