irclib: add support for SASL ECDSA-NIST256P-CHALLENGE

Closes #911
This commit is contained in:
Max Teufel 2014-12-27 18:39:38 +01:00
parent 7843fecbeb
commit ef0a2c1cb1
3 changed files with 36 additions and 3 deletions

View File

@ -6,3 +6,4 @@ feedparser
sqlalchemy sqlalchemy
SocksiPy-branch SocksiPy-branch
mock mock
ecdsa

View File

@ -344,6 +344,10 @@ def registerNetwork(name, password='', ssl=False, sasl_username='',
registerGlobalValue(sasl, 'password', registry.String(sasl_password, registerGlobalValue(sasl, 'password', registry.String(sasl_password,
_("""Determines what SASL password will be used on %s.""") \ _("""Determines what SASL password will be used on %s.""") \
% name, private=True)) % 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('', registerGlobalValue(network, 'socksproxy', registry.String('',
_("""If not empty, determines the hostname of the socks proxy that _("""If not empty, determines the hostname of the socks proxy that
will be used to connect to this network."""))) will be used to connect to this network.""")))

View File

@ -33,6 +33,12 @@ import time
import random import random
import base64 import base64
try:
from ecdsa import SigningKey, BadDigestError
ecdsa = True
except ImportError:
ecdsa = False
from . import conf, ircdb, ircmsgs, ircutils, log, utils, world from . import conf, ircdb, ircmsgs, ircutils, log, utils, world
from .utils.str import rsplit from .utils.str import rsplit
from .utils.iter import imap, chain, cycle from .utils.iter import imap, chain, cycle
@ -924,6 +930,11 @@ class Irc(IrcCommandDispatcher):
# TODO Find a better way to fix this # TODO Find a better way to fix this
if hasattr(self.sasl_password, 'decode'): if hasattr(self.sasl_password, 'decode'):
self.sasl_password = self.sasl_password.decode('utf-8') 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') self.prefix = '%s!%s@%s' % (self.nick, self.ident, 'unset.domain')
# The rest. # The rest.
self.lastTake = 0 self.lastTake = 0
@ -943,7 +954,9 @@ class Irc(IrcCommandDispatcher):
self.sasl = None 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()): conf.supybot.protocols.irc.certfile()):
self.sasl = 'external' self.sasl = 'external'
elif self.sasl_username and self.sasl_password: elif self.sasl_username and self.sasl_password:
@ -980,6 +993,9 @@ class Irc(IrcCommandDispatcher):
if self.sasl == 'external': if self.sasl == 'external':
authstring = '+' authstring = '+'
elif self.sasl == 'ecdsa-nist256p-challenge':
authstring = base64.b64encode(
self.sasl_username.encode('utf-8')).decode('utf-8')
elif self.sasl == 'plain': elif self.sasl == 'plain':
authstring = base64.b64encode('\0'.join([ authstring = base64.b64encode('\0'.join([
self.sasl_username, self.sasl_username,
@ -988,6 +1004,17 @@ class Irc(IrcCommandDispatcher):
]).encode('utf-8')).decode('utf-8') ]).encode('utf-8')).decode('utf-8')
self.queueMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=(authstring,))) 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): def doCap(self, msg):
if len(msg.args) == 3: if len(msg.args) == 3:
@ -1009,9 +1036,10 @@ class Irc(IrcCommandDispatcher):
self.queueMsg(ircmsgs.IrcMsg(command='CAP', args=('END',))) self.queueMsg(ircmsgs.IrcMsg(command='CAP', args=('END',)))
def do904(self, msg): 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): 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' self.sasl = 'plain'