refactored identity verification

public key authentication
This commit is contained in:
Brendan Taylor 2007-08-17 08:39:47 +00:00
parent 4bfe14d3cf
commit 8675731f8d
2 changed files with 131 additions and 61 deletions

View File

@ -16,9 +16,12 @@ import xmpp.c14n
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA256
from Crypto.PublicKey import RSA
import base64
XmlDsig = 'http://www.w3.org/2000/09/xmldsig#'
class StanzaSession(object):
def __init__(self, conn, jid, thread_id, type):
self.conn = conn
@ -189,6 +192,11 @@ class EncryptedStanzaSession(StanzaSession):
self.c_o = (self.c_o + 1) % (2 ** self.n)
return self.encode_mpi_with_padding(self.c_o)
def sign(self, string):
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
hash = self.sha256(string)
return self.encode_mpi(gajim.interface.pubkey.sign(hash, '')[0])
def encrypt_stanza(self, stanza):
encryptable = filter(lambda x: x.getName() not in ('error', 'amp', 'thread'), stanza.getChildren())
@ -317,84 +325,117 @@ class EncryptedStanzaSession(StanzaSession):
macable = filter(lambda x: x.getVar() not in ('mac', 'identity'), kids)
return ''.join(map(lambda el: xmpp.c14n.c14n(el), macable))
def verify_alices_identity(self, form, e):
m_a = base64.b64decode(form['mac'])
id_a = base64.b64decode(form['identity'])
def verify_identity(self, form, dh_i, sigmai, i_o):
m_o = base64.b64decode(form['mac'])
id_o = base64.b64decode(form['identity'])
m_a_calculated = self.hmac(self.km_o, self.encode_mpi(self.c_o) + id_a)
m_o_calculated = self.hmac(self.km_o, self.encode_mpi(self.c_o) + id_o)
if m_a_calculated != m_a:
raise exceptions.NegotiationError, 'calculated m_a differs from received m_a'
if m_o_calculated != m_o:
raise exceptions.NegotiationError, 'calculated m_%s differs from received m_%s' % (i_o, i_o)
# check for a retained secret
# if none exists, prompt the user with the SAS
if self.sas_algs == 'sas28x5':
self.sas = self.sas_28x5(m_a, self.form_b)
if i_o == 'a' and self.sas_algs == 'sas28x5':
# XXX not necessary if there's a verified retained secret
self.sas = self.sas_28x5(m_o, self.form_s)
mac_a = self.decrypt(id_a)
if self.negotiated['recv_pubkey']:
plaintext = self.decrypt(id_o)
parsed = xmpp.Node(node='<node>' + plaintext + '</node>')
form_a2 = self.c7lize_mac_id(form)
if self.negotiated['recv_pubkey'] == 'hash':
fingerprint = parsed.getTagData('fingerprint')
mac_a_calculated = self.hmac(self.ks_o, self.n_s + self.n_o + self.encode_mpi(e) + self.form_a + form_a2)
# XXX find stored pubkey or terminate session
raise 'unimplemented'
else:
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
keyvalue = parsed.getTag(name='RSAKeyValue', namespace=XmlDsig)
if mac_a_calculated != mac_a:
raise exceptions.NegotiationError, 'calculated mac_a differs from received mac_a'
n, e = map(lambda x: self.decode_mpi(base64.b64decode(keyvalue.getTagData(x))), ('Modulus', 'Exponent'))
eir_pubkey = RSA.construct((n,long(e)))
def verify_bobs_identity(self, form, sigmai):
m_b = base64.b64decode(form['mac'])
id_b = base64.b64decode(form['identity'])
pubkey_o = xmpp.c14n.c14n(keyvalue)
else:
# XXX DSA, etc.
raise 'unimplemented'
m_b_calculated = self.hmac(self.km_o, self.encode_mpi(self.c_o) + id_b)
if m_b_calculated != m_b:
raise exceptions.NegotiationError, 'calculated m_b differs from received m_b'
mac_b = self.decrypt(id_b)
pubkey_b = ''
enc_sig = parsed.getTag(name='SignatureValue', namespace=XmlDsig).getData()
signature = (self.decode_mpi(base64.b64decode(enc_sig)),)
else:
mac_o = self.decrypt(id_o)
pubkey_o = ''
c7l_form = self.c7lize_mac_id(form)
content = self.n_s + self.n_o + self.encode_mpi(self.d) + pubkey_b
content = self.n_s + self.n_o + self.encode_mpi(dh_i) + pubkey_o
if sigmai:
self.form_b = c7l_form
content += self.form_b
self.form_o = c7l_form
content += self.form_o
else:
form_b2 = c7l_form
content += self.form_b + form_b2
form_o2 = c7l_form
content += self.form_o + form_o2
mac_b_calculated = self.hmac(self.ks_o, content)
mac_o_calculated = self.hmac(self.ks_o, content)
if self.negotiated['recv_pubkey']:
hash = self.sha256(mac_o_calculated)
if mac_b_calculated != mac_b:
raise exceptions.NegotiationError, 'calculated mac_b differs from received mac_b'
if not eir_pubkey.verify(hash, signature):
raise exceptions.NegotiationError, 'public key signature verification failed!'
elif mac_o_calculated != mac_o:
raise exceptions.NegotiationError, 'calculated mac_%s differs from received mac_%s' % (i_o, i_o)
def make_alices_identity(self, form, e):
form_a2 = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
if self.negotiated['send_pubkey']:
if self.negotiated['sign_algs'] == (XmlDsig + 'rsa-sha256'):
fields = (gajim.interface.pubkey.n, gajim.interface.pubkey.e)
cb_fields = map(lambda f: base64.b64encode(self.encode_mpi(f)), fields)
pubkey_s = '<RSAKeyValue xmlns="http://www.w3.org/2000/09/xmldsig#"><Modulus>%s</Modulus><Exponent>%s</Exponent></RSAKeyValue>' % tuple(cb_fields)
else:
pubkey_s = ''
form_s2 = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
old_c_s = self.c_s
content = self.n_o + self.n_s + self.encode_mpi(e) + self.form_a + form_a2
content = self.n_o + self.n_s + self.encode_mpi(e) + pubkey_s + self.form_s + form_s2
mac_a = self.hmac(self.ks_s, content)
id_a = self.encrypt(mac_a)
if self.negotiated['send_pubkey']:
signature = self.sign(mac_a)
sign_s = '<SignatureValue xmlns="http://www.w3.org/2000/09/xmldsig#">%s</SignatureValue>' % base64.b64encode(signature)
if self.negotiated['send_pubkey'] == 'hash':
b64ed = base64.b64encode(self.hash(pubkey_s))
pubkey_s = '<fingerprint>%s</fingerprint>' % b64ed
id_a = self.encrypt(pubkey_s + sign_s)
else:
id_a = self.encrypt(mac_a)
m_a = self.hmac(self.km_s, self.encode_mpi(old_c_s) + id_a)
# check for a retained secret
# if none exists, prompt the user with the SAS
if self.sas_algs == 'sas28x5':
self.sas = self.sas_28x5(m_a, self.form_b)
self.sas = self.sas_28x5(m_a, self.form_o)
if self.sigmai:
self.check_identity()
return (xmpp.DataField(name='identity', value=base64.b64encode(id_a)), \
xmpp.DataField(name='mac', value=base64.b64encode(m_a)))
def make_bobs_identity(self, form, d):
pubkey_b = ''
form_b2 = ''.join(map(lambda el: c14n.c14n(el), form.getChildren()))
content = self.n_o + self.n_s + self.encode_mpi(d) + pubkey_b + self.form_b + form_b2
form_s2 = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
content = self.n_o + self.n_s + self.encode_mpi(d) + pubkey_b + self.form_s + form_s2
old_c_s = self.c_s
mac_b = self.hmac(self.ks_s, content)
@ -430,13 +471,17 @@ class EncryptedStanzaSession(StanzaSession):
# unsupported options: 'iq', 'presence'
x.addChild(node=xmpp.DataField(name='stanzas', typ='list-multi', options=['message']))
x.addChild(node=xmpp.DataField(name='init_pubkey', value='none', typ='hidden'))
x.addChild(node=xmpp.DataField(name='resp_pubkey', value='none', typ='hidden'))
x.addChild(node=xmpp.DataField(name='init_pubkey', options=['none', 'key', 'hash'], typ='list-single'))
# XXX store key, use hash
x.addChild(node=xmpp.DataField(name='resp_pubkey', options=['none', 'key'], typ='list-single'))
x.addChild(node=xmpp.DataField(name='ver', value='1.0', typ='hidden'))
x.addChild(node=xmpp.DataField(name='rekey_freq', value='4294967295', typ='hidden'))
x.addChild(node=xmpp.DataField(name='sas_algs', value='sas28x5', typ='hidden'))
x.addChild(node=xmpp.DataField(name='sign_algs', value='http://www.w3.org/2000/09/xmldsig#rsa-sha256', typ='hidden'))
self.n_s = self.generate_nonce()
@ -449,7 +494,7 @@ class EncryptedStanzaSession(StanzaSession):
x.addChild(node=self.make_dhfield(modp_options, sigmai))
self.sigmai = sigmai
self.form_a = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
self.form_s = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
feature.addChild(node=x)
@ -459,7 +504,7 @@ class EncryptedStanzaSession(StanzaSession):
# 4.3 esession response (bob)
def verify_options_bob(self, form):
negotiated = {}
negotiated = {'recv_pubkey': None, 'send_pubkey': None}
not_acceptable = []
ask_user = {}
@ -511,6 +556,19 @@ class EncryptedStanzaSession(StanzaSession):
if not 'logging' in ask_user:
not_acceptable.append(name)
elif name == 'init_pubkey':
for x in ('key'):
if x in options:
negotiated['recv_pubkey'] = x
break
elif name == 'resp_pubkey':
for x in ('hash', 'key'):
if x in options:
negotiated['send_pubkey'] = x
break
elif name == 'sign_algs':
if (XmlDsig + 'rsa-sha256') in options:
negotiated['sign_algs'] = XmlDsig + 'rsa-sha256'
else:
# XXX some things are handled elsewhere, some things are not-implemented
pass
@ -565,8 +623,8 @@ class EncryptedStanzaSession(StanzaSession):
b64ed = base64.b64encode(to_add[name])
x.addChild(node=xmpp.DataField(name=name, value=b64ed))
self.form_a = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
self.form_b = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
self.form_o = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
self.form_s = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
self.status = 'responded-e2e'
@ -597,6 +655,18 @@ class EncryptedStanzaSession(StanzaSession):
else:
negotiated['logging'] = self.logging_preference()[0]
for r,a in (('recv_pubkey', 'resp_pubkey'), ('send_pubkey', 'init_pubkey')):
negotiated[r] = None
if a in form.asDict() and form[a] in ('key', 'hash'):
negotiated[r] = form[a]
if 'sign_algs' in form.asDict():
if form['sign_algs'] in (XmlDsig + 'rsa-sha256',):
negotiated['sign_algs'] = form['sign_algs']
else:
not_acceptable.append(form['sign_algs'])
return (negotiated, not_acceptable, ask_user)
# 'Alice Accepts', continued
@ -637,7 +707,7 @@ class EncryptedStanzaSession(StanzaSession):
if self.sigmai:
self.kc_o, self.km_o, self.ks_o = self.generate_responder_keys(self.k)
self.verify_bobs_identity(form, True)
self.verify_identity(form, self.d, True, 'b')
else:
secrets = gajim.interface.list_secrets(self.conn.name, self.jid.getStripped())
rshashes = [self.hmac(self.n_s, rs) for rs in secrets]
@ -649,7 +719,7 @@ class EncryptedStanzaSession(StanzaSession):
result.addChild(node=xmpp.DataField(name='rshashes', value=rshashes))
result.addChild(node=xmpp.DataField(name='dhkeys', value=base64.b64encode(self.encode_mpi(e))))
self.form_b = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
self.form_o = ''.join(map(lambda el: xmpp.c14n.c14n(el), form.getChildren()))
# MUST securely destroy K unless it will be used later to generate the final shared secret
@ -682,13 +752,15 @@ class EncryptedStanzaSession(StanzaSession):
e = self.decode_mpi(base64.b64decode(form['dhkeys']))
p = dh.primes[self.modp]
# XXX return <feature-not-implemented> if hash(e) != He
k = self.get_shared_secret(e, self.y, p)
self.kc_o, self.km_o, self.ks_o = self.generate_initiator_keys(k)
# 4.5.2 verifying alice's identity
self.verify_alices_identity(form)
self.verify_identity(form, e, False, 'a')
# 4.5.4 generating bob's final session keys
@ -721,17 +793,8 @@ class EncryptedStanzaSession(StanzaSession):
x.addChild(node=xmpp.DataField(name='nonce', value=base64.b64encode(self.n_o)))
x.addChild(node=xmpp.DataField(name='srshash', value=base64.b64encode(srshash)))
form_b2 = ''.join(map(lambda el: xmpp.c14n.c14n(el), x.getChildren()))
old_c_s = self.c_s
mac_b = self.hmac(self.ks_s, self.n_o + self.n_s + self.encode_mpi(self.d) + self.form_b + form_b2)
id_b = self.encrypt(mac_b)
m_b = self.hmac(self.km_s, self.encode_mpi(old_c_s) + id_b)
x.addChild(node=xmpp.DataField(name='identity', value=base64.b64encode(id_b)))
x.addChild(node=xmpp.DataField(name='mac', value=base64.b64encode(m_b)))
for datafield in self.make_bobs_identity(x, self.d):
x.addChild(node=datafield)
init.addChild(node=x)
@ -769,7 +832,7 @@ class EncryptedStanzaSession(StanzaSession):
# 4.6.2 Verifying Bob's Identity
self.verify_bobs_identity(form, False)
self.verify_identity(form, self.d, False, 'b')
# Note: If Alice discovers an error then she SHOULD ignore any encrypted content she received in the stanza.
if self.negotiated['logging'] == 'mustnot':

View File

@ -104,6 +104,7 @@ from chat_control import ChatControlBase
from atom_window import AtomWindow
import negotiation
import Crypto.PublicKey.RSA
from common import exceptions
from common.zeroconf import connection_zeroconf
@ -1393,6 +1394,7 @@ class Interface:
if os.path.isfile(path_to_original_file):
os.remove(path_to_original_file)
# list the retained secrets we have for a local account and a remote jid
def list_secrets(self, account, jid):
f = open(secrets_filename)
@ -1404,6 +1406,7 @@ class Interface:
f.close()
return s
# save a new retained secret
def save_new_secret(self, account, jid, secret):
f = open(secrets_filename, 'r')
secrets = pickle.load(f)
@ -2559,6 +2562,10 @@ class Interface:
gobject.timeout_add(200, self.process_connections)
gobject.timeout_add(500, self.read_sleepy)
# public key for XEP-0116
# XXX os.urandom is not a cryptographic PRNG
self.pubkey = Crypto.PublicKey.RSA.generate(384, os.urandom)
if __name__ == '__main__':
def sigint_cb(num, stack):
sys.exit(5)