mirror of
https://github.com/Mikaela/Limnoria.git
synced 2025-01-11 20:52:42 +01:00
Merge branch 'sasl-scram' into testing
This commit is contained in:
commit
260a511942
@ -312,7 +312,8 @@ class SpaceSeparatedSetOfChannels(registry.SpaceSeparatedListOf):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
class ValidSaslMechanism(registry.OnlySomeStrings):
|
class ValidSaslMechanism(registry.OnlySomeStrings):
|
||||||
validStrings = ('ecdsa-nist256p-challenge', 'external', 'plain')
|
validStrings = ('ecdsa-nist256p-challenge', 'external', 'plain',
|
||||||
|
'scram-sha-256')
|
||||||
|
|
||||||
class SpaceSeparatedListOfSaslMechanisms(registry.SpaceSeparatedListOf):
|
class SpaceSeparatedListOfSaslMechanisms(registry.SpaceSeparatedListOf):
|
||||||
Value = ValidSaslMechanism
|
Value = ValidSaslMechanism
|
||||||
|
@ -35,10 +35,14 @@ import base64
|
|||||||
import collections
|
import collections
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ecdsa import SigningKey, BadDigestError
|
import ecdsa
|
||||||
ecdsa = True
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
ecdsa = False
|
ecdsa = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pyxmpp2_scram as scram
|
||||||
|
except ImportError:
|
||||||
|
scram = None
|
||||||
|
|
||||||
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
|
||||||
@ -994,6 +998,7 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
|
|||||||
self.sasl_username = network_config.sasl.username()
|
self.sasl_username = network_config.sasl.username()
|
||||||
self.sasl_password = network_config.sasl.password()
|
self.sasl_password = network_config.sasl.password()
|
||||||
self.sasl_ecdsa_key = network_config.sasl.ecdsa_key()
|
self.sasl_ecdsa_key = network_config.sasl.ecdsa_key()
|
||||||
|
self.sasl_scram_state = {'step': 'uninitialized'}
|
||||||
self.authenticate_decoder = None
|
self.authenticate_decoder = None
|
||||||
self.sasl_next_mechanisms = []
|
self.sasl_next_mechanisms = []
|
||||||
self.sasl_current_mechanism = None
|
self.sasl_current_mechanism = None
|
||||||
@ -1006,6 +1011,9 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
|
|||||||
network_config.certfile() or
|
network_config.certfile() or
|
||||||
conf.supybot.protocols.irc.certfile()):
|
conf.supybot.protocols.irc.certfile()):
|
||||||
self.sasl_next_mechanisms.append(mechanism)
|
self.sasl_next_mechanisms.append(mechanism)
|
||||||
|
elif mechanism.startswith('scram-') and scram and \
|
||||||
|
self.sasl_username and self.sasl_password:
|
||||||
|
self.sasl_next_mechanisms.append(mechanism)
|
||||||
elif mechanism == 'plain' and \
|
elif mechanism == 'plain' and \
|
||||||
self.sasl_username and self.sasl_password:
|
self.sasl_username and self.sasl_password:
|
||||||
self.sasl_next_mechanisms.append(mechanism)
|
self.sasl_next_mechanisms.append(mechanism)
|
||||||
@ -1101,20 +1109,30 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
|
|||||||
|
|
||||||
mechanism = self.sasl_current_mechanism
|
mechanism = self.sasl_current_mechanism
|
||||||
if mechanism == 'ecdsa-nist256p-challenge':
|
if mechanism == 'ecdsa-nist256p-challenge':
|
||||||
if string == b'':
|
self.doAuthenticateEcdsa(string)
|
||||||
self.sendSaslString(self.sasl_username.encode('utf-8'))
|
elif mechanism == 'external':
|
||||||
return
|
self.sendSaslString(b'')
|
||||||
|
elif mechanism.startswith('scram-'):
|
||||||
|
step = self.sasl_scram_state['step']
|
||||||
try:
|
try:
|
||||||
with open(self.sasl_ecdsa_key) as fd:
|
if step == 'uninitialized':
|
||||||
private_key = SigningKey.from_pem(fd.read())
|
log.debug('%s: starting SCRAM.',
|
||||||
authstring = private_key.sign(base64.b64decode(msg.args[0].encode()))
|
self.network)
|
||||||
self.sendSaslString(authstring)
|
self.doAuthenticateScramFirst(mechanism)
|
||||||
except (BadDigestError, OSError, ValueError):
|
elif step == 'first-sent':
|
||||||
|
log.debug('%s: received SCRAM challenge.',
|
||||||
|
self.network)
|
||||||
|
self.doAuthenticateScramChallenge(string)
|
||||||
|
elif step == 'final-sent':
|
||||||
|
log.debug('%s: finishing SCRAM.',
|
||||||
|
self.network)
|
||||||
|
self.doAuthenticateScramFinish(string)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
except scram.ScramException:
|
||||||
self.sendMsg(ircmsgs.IrcMsg(command='AUTHENTICATE',
|
self.sendMsg(ircmsgs.IrcMsg(command='AUTHENTICATE',
|
||||||
args=('*',)))
|
args=('*',)))
|
||||||
self.tryNextSaslMechanism()
|
self.tryNextSaslMechanism()
|
||||||
elif mechanism == 'external':
|
|
||||||
self.sendSaslString(b'')
|
|
||||||
elif mechanism == 'plain':
|
elif mechanism == 'plain':
|
||||||
authstring = b'\0'.join([
|
authstring = b'\0'.join([
|
||||||
self.sasl_username.encode('utf-8'),
|
self.sasl_username.encode('utf-8'),
|
||||||
@ -1123,6 +1141,58 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
|
|||||||
])
|
])
|
||||||
self.sendSaslString(authstring)
|
self.sendSaslString(authstring)
|
||||||
|
|
||||||
|
def doAuthenticateEcdsa(self, string):
|
||||||
|
if string == b'':
|
||||||
|
self.sendSaslString(self.sasl_username.encode('utf-8'))
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
with open(self.sasl_ecdsa_key) as fd:
|
||||||
|
private_key = ecdsa.SigningKey.from_pem(fd.read())
|
||||||
|
authstring = private_key.sign(string)
|
||||||
|
self.sendSaslString(authstring)
|
||||||
|
except (ecdsa.BadDigestError, OSError, ValueError):
|
||||||
|
self.sendMsg(ircmsgs.IrcMsg(command='AUTHENTICATE',
|
||||||
|
args=('*',)))
|
||||||
|
self.tryNextSaslMechanism()
|
||||||
|
|
||||||
|
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:
|
||||||
|
log.debug('%s: SCRAM hash %r not supported, aborting.',
|
||||||
|
self.network, hash_name)
|
||||||
|
self.tryNextSaslMechanism()
|
||||||
|
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.sendSaslString(client_first)
|
||||||
|
self.sasl_scram_state['step'] = 'first-sent'
|
||||||
|
|
||||||
|
def doAuthenticateScramChallenge(self, challenge):
|
||||||
|
client_final = self.sasl_scram_state['authenticator'] \
|
||||||
|
.challenge(challenge)
|
||||||
|
self.sendSaslString(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 as e:
|
||||||
|
log.warning('%s: SASL authentication failed with SCRAM error: %e',
|
||||||
|
self.network, e)
|
||||||
|
self.tryNextSaslMechanism()
|
||||||
|
else:
|
||||||
|
self.sasl_scram_state['step'] = 'authenticated'
|
||||||
|
|
||||||
def do903(self, msg):
|
def do903(self, msg):
|
||||||
log.info('%s: SASL authentication successful', self.network)
|
log.info('%s: SASL authentication successful', self.network)
|
||||||
self.sasl_authenticated = True
|
self.sasl_authenticated = True
|
||||||
|
@ -508,7 +508,7 @@ class Probability(Float):
|
|||||||
|
|
||||||
class String(Value):
|
class String(Value):
|
||||||
"""Value is not a valid Python string."""
|
"""Value is not a valid Python string."""
|
||||||
errormsg = _('Value is not a valid Python string, not %r.')
|
errormsg = _('Value should be a valid Python string, not %r.')
|
||||||
def set(self, s):
|
def set(self, s):
|
||||||
v = s
|
v = s
|
||||||
if not v:
|
if not v:
|
||||||
|
Loading…
Reference in New Issue
Block a user