aboutsummaryrefslogtreecommitdiff
path: root/misc/send-receive.py
diff options
context:
space:
mode:
Diffstat (limited to 'misc/send-receive.py')
-rw-r--r--misc/send-receive.py141
1 files changed, 93 insertions, 48 deletions
diff --git a/misc/send-receive.py b/misc/send-receive.py
index 7b47798..c102508 100644
--- a/misc/send-receive.py
+++ b/misc/send-receive.py
@@ -5,10 +5,7 @@ import os
import logging
import json
import sqlalchemy as sa
-
-from nacl.signing import VerifyKey
-from nacl.encoding import Base64Encoder
-from nacl.exceptions import BadSignatureError
+import patatt
DB_VERSION = 1
@@ -34,19 +31,13 @@ class SendReceiveListener(object):
auth = sa.Table('auth', md,
sa.Column('auth_id', sa.Integer(), primary_key=True),
sa.Column('created', sa.DateTime(), nullable=False, server_default=sa.sql.func.now()),
- sa.Column('email', sa.Text(), nullable=False),
- sa.Column('name', sa.Text(), nullable=False),
+ sa.Column('identity', sa.Text(), nullable=False),
+ sa.Column('selector', sa.Text(), nullable=False),
sa.Column('pubkey', sa.Text(), nullable=False),
+ sa.Column('challenge', sa.Text(), nullable=True),
+ sa.Column('verified', sa.Integer(), nullable=False),
)
- sa.Index('idx_email_pubkey', auth.c.pubkey, auth.c.email, unique=True)
- challenge = sa.Table('challenge', md,
- sa.Column('challenge_id', sa.Integer(), primary_key=True),
- sa.Column('created', sa.DateTime(), nullable=False, server_default=sa.sql.func.now()),
- sa.Column('pubkey', sa.Text(), nullable=False),
- sa.Column('email', sa.Text(), nullable=False),
- sa.Column('challenge', sa.Text(), nullable=False),
- )
- sa.Index('idx_uniq_challenge', challenge.c.pubkey, challenge.c.email, challenge.c.challenge, unique=True)
+ sa.Index('idx_identity_selector', auth.c.identity, auth.c.selector, unique=True)
md.create_all(self._engine)
q = sa.insert(meta).values(version=DB_VERSION)
conn.execute(q)
@@ -68,56 +59,107 @@ class SendReceiveListener(object):
# Is it already authorized?
conn = self._engine.connect()
md = sa.MetaData()
+ identity = jdata.get('identity')
+ selector = jdata.get('selector')
+ pubkey = jdata.get('pubkey')
t_auth = sa.Table('auth', md, autoload=True, autoload_with=self._engine)
- email = jdata.get('email')
- pubkey = jdata.get('key')
- q = sa.select([t_auth.c.auth_id]).where(t_auth.c.email == email, t_auth.c.pubkey == pubkey)
+ q = sa.select([t_auth.c.auth_id]).where(t_auth.c.identity == identity, t_auth.c.selector == selector,
+ t_auth.c.verified == 1)
rp = conn.execute(q)
if len(rp.fetchall()):
- self.send_error(resp, message='%s:%s is already authorized' % (email, pubkey))
+ self.send_error(resp, message='i=%s;s=%s is already authorized' % (identity, selector))
return
# delete any existing challenges for this and create a new one
- t_challenge = sa.Table('challenge', md, autoload=True, autoload_with=self._engine)
- q = sa.delete(t_challenge).where(t_challenge.c.email == email, t_challenge.c.pubkey == pubkey)
+ q = sa.delete(t_auth).where(t_auth.c.identity == identity, t_auth.c.selector == selector,
+ t_auth.c.verified == 0)
conn.execute(q)
# create new challenge
import uuid
cstr = str(uuid.uuid4())
- q = sa.insert(t_challenge).values(pubkey=pubkey, email=email, challenge=cstr)
+ q = sa.insert(t_auth).values(identity=identity, selector=selector, pubkey=pubkey, challenge=cstr,
+ verified=0)
conn.execute(q)
# TODO: Actual mail sending
logger.info('Challenge: %s', cstr)
self.send_success(resp, message='Challenge generated')
+ def validate_message(self, conn, t_auth, bdata, verified=1):
+ # Returns auth_id of the matching record
+ pm = patatt.PatattMessage(bdata)
+ if not pm.signed:
+ return None
+ auth_id = identity = pubkey = None
+ for ds in pm.get_sigs():
+ selector = 'default'
+ identity = ''
+ i = ds.get_field('i')
+ if i:
+ identity = i.decode()
+ s = ds.get_field('s')
+ if s:
+ selector = s.decode()
+ logger.debug('i=%s; s=%s', identity, selector)
+ q = sa.select([t_auth.c.auth_id, t_auth.c.pubkey]).where(t_auth.c.identity == identity,
+ t_auth.c.selector == selector,
+ t_auth.c.verified == verified)
+ rp = conn.execute(q)
+ res = rp.fetchall()
+ if res:
+ auth_id, pubkey = res[0]
+ break
+
+ logger.debug('auth_id=%s', auth_id)
+ if not auth_id:
+ return None
+ try:
+ pm.validate(identity, pubkey.encode())
+ except Exception as ex:
+ logger.debug('Validation failed: %s', ex)
+ return None
+
+ return auth_id
+
def auth_verify(self, jdata, resp):
- # Do we have a record for this email/challenge?
+ msg = jdata.get('msg')
+ if msg.find('\nverify:') < 0:
+ self.send_error(resp, message='Invalid verification message')
+ return
conn = self._engine.connect()
md = sa.MetaData()
- t_challenge = sa.Table('challenge', md, autoload=True, autoload_with=self._engine)
- email = jdata.get('email', '')
- challenge = jdata.get('challenge', '')
- sigdata = jdata.get('sigdata', '')
- q = sa.select([t_challenge.c.pubkey]).where(t_challenge.c.email == email, t_challenge.c.challenge == challenge)
+ t_auth = sa.Table('auth', md, autoload=True, autoload_with=self._engine)
+ bdata = msg.encode()
+ auth_id = self.validate_message(conn, t_auth, bdata, verified=0)
+ if auth_id is None:
+ self.send_error(resp, message='Signature validation failed')
+ return
+ # Now compare the challenge to what we received
+ q = sa.select([t_auth.c.challenge]).where(t_auth.c.auth_id == auth_id)
rp = conn.execute(q)
- qres = rp.fetchall()
- if not len(qres):
- self.send_error(resp, message='No such challenge for %s' % email)
+ res = rp.fetchall()
+ challenge = res[0][0]
+ if msg.find(f'\nverify:{challenge}') < 0:
+ self.send_error(resp, message='Invalid verification string')
return
- pubkey = qres[0][0]
- vk = VerifyKey(pubkey.encode(), encoder=Base64Encoder)
- try:
- vk.verify(sigdata.encode(), encoder=Base64Encoder)
- except BadSignatureError:
- self.send_error(resp, message='Could not validate signature for %s' % email)
+ q = sa.update(t_auth).where(t_auth.c.auth_id == auth_id).values(challenge=None, verified=1)
+ conn.execute(q)
+ self.send_success(resp, message='Challenge verified')
+
+ def auth_delete(self, jdata, resp):
+ msg = jdata.get('msg')
+ if msg.find('\nauth-delete') < 0:
+ self.send_error(resp, message='Invalid key delete message')
return
- # validated at this point, so record this as valid auth
- name = jdata.get('name')
+ conn = self._engine.connect()
+ md = sa.MetaData()
t_auth = sa.Table('auth', md, autoload=True, autoload_with=self._engine)
- q = sa.insert(t_auth).values(pubkey=pubkey, name=name, email=email)
- conn.execute(q)
- q = sa.delete(t_challenge).where(t_challenge.c.email == email, t_challenge.c.challenge == challenge)
+ bdata = msg.encode()
+ auth_id = self.validate_message(conn, t_auth, bdata)
+ if auth_id is None:
+ self.send_error(resp, message='Signature validation failed')
+ return
+ q = sa.delete(t_auth).where(t_auth.c.auth_id == auth_id)
conn.execute(q)
- self.send_success(resp, message='Challenge verified')
+ self.send_success(resp, message='Authentication deleted')
def on_post(self, req, resp):
if not req.content_length:
@@ -133,17 +175,20 @@ class SendReceiveListener(object):
resp.content_type = falcon.MEDIA_TEXT
resp.text = 'Failed to parse the request\n'
return
- logger.info(jdata)
action = jdata.get('action')
if action == 'auth-new':
self.auth_new(jdata, resp)
+ return
if action == 'auth-verify':
self.auth_verify(jdata, resp)
- else:
- resp.status = falcon.HTTP_500
- resp.content_type = falcon.MEDIA_TEXT
- resp.text = 'Unknown action: %s\n' % action
return
+ if action == 'auth-delete':
+ self.auth_delete(jdata, resp)
+ return
+
+ resp.status = falcon.HTTP_500
+ resp.content_type = falcon.MEDIA_TEXT
+ resp.text = 'Unknown action: %s\n' % action
app = falcon.App()