diff options
Diffstat (limited to 'server')
| -rw-r--r-- | server/README | 7 | ||||
| -rw-r--r-- | server/__init__.py | 3 | ||||
| -rw-r--r-- | server/config.ini.sample | 6 | ||||
| -rw-r--r-- | server/config.py | 15 | ||||
| -rw-r--r-- | server/crypto.py | 28 | ||||
| -rw-r--r-- | server/object.py | 211 | ||||
| -rw-r--r-- | server/permission.py | 6 | ||||
| -rw-r--r-- | server/plugin.py | 61 | ||||
| -rwxr-xr-x | server/server.py | 97 | ||||
| -rw-r--r-- | server/ssh_rsa_key_util.py | 209 | ||||
| -rw-r--r-- | server/user.py | 32 | ||||
| -rw-r--r-- | server/version.py | 5 |
12 files changed, 680 insertions, 0 deletions
diff --git a/server/README b/server/README new file mode 100644 index 0000000..81ee68b --- /dev/null +++ b/server/README @@ -0,0 +1,7 @@ +This is Alias server written in python. It depends on SleekXMPP library : + + git clone git://github.com/fritzy/SleekXMPP.git + cd SleekXMPP/ + sudo python setup.py install + +Make sure sleekxmpp is in your pythonpath before starting the server. diff --git a/server/__init__.py b/server/__init__.py new file mode 100644 index 0000000..faaaf79 --- /dev/null +++ b/server/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + + diff --git a/server/config.ini.sample b/server/config.ini.sample new file mode 100644 index 0000000..b25b281 --- /dev/null +++ b/server/config.ini.sample @@ -0,0 +1,6 @@ +[component] +name = object.alias.fr.nf +secret = xxxx +host = alias.fr.nf +port = 5347 +root = users/ diff --git a/server/config.py b/server/config.py new file mode 100644 index 0000000..d50d4d7 --- /dev/null +++ b/server/config.py @@ -0,0 +1,15 @@ +import ConfigParser + +filename = None + +class AliasConfigParser(ConfigParser.RawConfigParser): + + def read(self, filenames): + ConfigParser.RawConfigParser.read(self, filenames) + self.name = self.get("component", "name") + self.root = self.get("component", "root") + self.host = self.get("component", "host") + self.secret = self.get("component", "secret") + self.port = self.getint("component", "port") + +config = AliasConfigParser() diff --git a/server/crypto.py b/server/crypto.py new file mode 100644 index 0000000..f99001f --- /dev/null +++ b/server/crypto.py @@ -0,0 +1,28 @@ +from keyczar import keys +import ssh_rsa_key_util + +if __name__ == '__main__' : + #load asymmetric keys for 2 users. user1 key is generated, + #while user2 key is loaded from disk + keyuser1 = keys.RsaPrivateKey.Generate() + keyuser1public = keyuser1.public_key + ssh_keys_directory = '/home/guillaume/.ssh/' + keyuser2 = ssh_rsa_key_util.SshRsaPrivateKey.Read(ssh_keys_directory + 'id_rsa') + keyuser2public = keyuser2.public_key + + #symmetric key to encrypt the data + key = keys.AesKey.Generate() + #encrypt symmetric key for user 1 and 2 + key1 = keyuser1public.Encrypt(key.key_string) + key2 = keyuser2public.Encrypt(key.key_string) + + msg = 'Guillaume is a genius!' + secretmsg = key.Encrypt(msg) + + #msg decoded by user 1 + newkey1 = keys.AesKey(keyuser1.Decrypt(key1), key.hmac_key) + print newkey1.Decrypt(secretmsg) + + #msg decoded by user 2 + newkey2 = keys.AesKey(keyuser1.Decrypt(key1), key.hmac_key) + print newkey2.Decrypt(secretmsg) diff --git a/server/object.py b/server/object.py new file mode 100644 index 0000000..866f7bb --- /dev/null +++ b/server/object.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +import StringIO +import hashlib +import sys +import os +import os.path +import zlib +import datetime +import fileinput +import logging + +from permission import * +from config import config + +class Object: + + def __get_path(self): + return self.name[:2] + '/' + self.name[2:] + + def __create_dir(self): + if (not os.path.exists(self.name[:2])): + os.mkdir(self.name[:2]) + + if (not os.path.exists(self.path)): + os.mkdir(self.path) + + ## + # Save the object on the disk. + def save(self): + self.__create_dir() + + header = "author " + self.data['author'] + "\n" + header += "parent " + self.data['parent'] + "\n" + header += "created " + self.data['created'] + "\n" + header += "#\n" #end of header + store = header + self.data['content'] + + file = open(self.path + "/object", 'w') + file.write(zlib.compress(store)) + file.close() + os.mknod(self.path + "/childs") + os.mknod(self.path + "/permissions") + self.saved = True + + ## + # Class constructor. + def __init__(self, init): + + if isinstance(init, dict): + if 'author' not in init: + init['author'] = 'None' + if 'parent' not in init: + init['parent'] = 'None' + if 'content' not in init: + init['content'] = 'None' + + self.data = init + self.data['created'] = str(datetime.datetime.now()) + self.name = hashlib.sha1(str(self.data)).hexdigest() + self.path = self.__get_path() + self.saved = False + + elif isinstance(init, str): + self.name = init + self.path = self.__get_path() + if (os.path.exists(self.path)): + file = open(self.path + '/object', 'r') + contentStream = StringIO.StringIO(zlib.decompress(file.read())) + data = {} + + for line in contentStream: + if (line == "#\n"): + data['content'] = contentStream.read() + break + else: + key, sep, value = line.rstrip('\n').partition(' ') + data[key] = value + + self.data = data + file.close() + else: + logging.error("Object {} root doesn't exist".format(init)) + + def append_to(self, father): + father.append_child(self) + + def append_child(self, child): + if not self.saved: + self.save() + + file = open(self.path + "/childs", 'a') + file.write(child.name + "\n") + file.close() + + @staticmethod + def get_permission_by_hash(hash, user): + path = hash[:2] + '/' + hash[2:] + try: + file = open(path + '/permissions', 'r') + except IOError: + print 'cannot open', path + else: + for line in file: + name, sep, perm = line.rstrip('\n').partition(' ') + if name == user: + return perm + + return 0 + + def get_permission(self, user): + file = open(self.path + '/permissions', 'r') + for line in file: + name, sep, perm = line.rstrip('\n').partition(' ') + if name == user: + return perm + + return 0 + + def set_permission(self, user, permission): + if not self.saved: + self.save() + + sentinel = False + for line in fileinput.input(self.path + "/permissions", inplace = 1): + name, sep, perm = line.rstrip('\n').partition(' ') + if name == user: + sys.stdout.write(name + ' ' + str(permission) + '\n') + sentinel = True + else: + sys.stdout.write(line) + + if not sentinel: + file = open(self.path + '/permissions', 'a') + file.write(user + ' ' + str(permission) + '\n') + file.close() + +class ObjectHandler: + + def __init__(self, user): + self.user = user + self.root_object = hashlib.sha1(user).hexdigest() + + if not os.path.exists(self.user): + logging.error("User {} root doesn't exist".format(self.user)) + + def get_object_directory(self, hash): + directory = self.user + '/' + hash[:2] + '/' + hash[2:] + if (os.path.exists(directory)): + return directory + else: + return None + + def get_object(self, hash): + os.chdir(self.user) + temp = Object(hash) + os.chdir('..') + return temp + + #return a list of hash,permission pairs + def get_child_list(self, hash, user): + directory = self.get_object_directory(hash) + if directory: + result = [] + file = open(directory + "/childs", 'r') + for line in file : + name = line.rstrip('\n') + result.append(name) + file.close() + return result + else : + return None + + def create_home_node(self): + pass + + def get_home_node(self): + return self.root_object + +if __name__ == '__main__': + config.root = os.path.abspath('object_tests') + if not(os.path.exists(config.root)): + os.mkdir(config.root) + os.chdir(config.root) + if not(os.path.exists('guillaume')): + os.mkdir('guillaume') + + guillaume_stuff = ObjectHandler('guillaume') + os.chdir('guillaume') + x = Object({'parent' : 'toto', 'author' : 'Zaran'}) + x.set_permission("test", READ) + x.set_permission("toto", READ | MODIFY) + x.set_permission("toto", READ | MODIFY | APPEND) + x.set_permission("toto2", READ | MODIFY | APPEND) + print x.get_permission("toto") + print Object.get_permission_by_hash(x.name, "toto") + child = Object({'parent' : x.name, 'author' : 'Zaran'}) + child.save() + + x.append_child(child) + os.chdir(config.root) + + y = guillaume_stuff.get_object(x.name) + pomme = guillaume_stuff.get_child_list(x.name, None) + print pomme + print y.data['author'] + print y.data['parent'] + print y.data['content'] + print y.data['created'] + + + diff --git a/server/permission.py b/server/permission.py new file mode 100644 index 0000000..fe93928 --- /dev/null +++ b/server/permission.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +READ = 1 << 0 +MODIFY = 1 << 1 +APPEND = 1 << 2 +
\ No newline at end of file diff --git a/server/plugin.py b/server/plugin.py new file mode 100644 index 0000000..df05908 --- /dev/null +++ b/server/plugin.py @@ -0,0 +1,61 @@ +import logging +import base64 +from xml.etree import cElementTree as ET + +from sleekxmpp.xmlstream.stanzabase import ElementBase, register_stanza_plugin +from sleekxmpp.plugins import base +from sleekxmpp.xmlstream.handler.callback import Callback +from sleekxmpp.xmlstream.matcher.xpath import MatchXPath +from sleekxmpp.stanza.iq import Iq + +import object + +class AliasQuery(ElementBase): + namespace = 'alias:query' + name = 'query' + plugin_attrib = 'alias' + interfaces = set(('node', 'type', 'items', 'object')) + sub_interfaces = set(('items')) + + def addItem(self, node, permission = None): + item = AliasItem(None, self) + item['node'] = node + if permission is not None: + item['permission'] = permission + +class AliasItem(ElementBase): + namespace = 'alias:query' + name = 'item' + plugin_attrib = 'item' + interfaces = set(('node', 'permission')) + +class AliasPlugin(base.base_plugin): + + def plugin_init(self): + self.description = 'Plugin to handle alias queries' + register_stanza_plugin(Iq, AliasQuery) + query_parser = MatchXPath('{{{}}}iq/{{{}}}query'.format(self.xmpp.default_ns, + AliasQuery.namespace)) + self.xmpp.register_handler(Callback('Alias queries', query_parser, + self.handle_alias_query)) + + def post_init(self): + base.base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature("alias:query") + + def handle_alias_query(self, iq): + callee = base64.b64decode(iq['to'].user) + caller = iq['alias']['from'] + handler = object.ObjectHandler(callee) + node = iq['alias']['node'] + if not node: + node = handler.get_home_node() + if iq['alias']['type'] == 'items': + logging.debug('childs of {} requested'.format(node)) + childs = handler.get_child_list(node, caller) + reply = AliasQuery() + reply['node'] = node + reply['type'] = 'items' + for child in childs: + reply.addItem(child, "test") + iq.reply().set_payload(reply).send()
\ No newline at end of file diff --git a/server/server.py b/server/server.py new file mode 100755 index 0000000..d78552d --- /dev/null +++ b/server/server.py @@ -0,0 +1,97 @@ +#!/usr/bin/python2 +import logging +from argparse import ArgumentParser + +from sleekxmpp.componentxmpp import ComponentXMPP +from sleekxmpp.xmlstream.xmlstream import XMLStream + +from user import UserHandler +from config import filename, config + +class ObjectComponent(ComponentXMPP): + + def __init__(self, jid, secret, server, port, root): + ComponentXMPP.__init__(self, jid, secret, server, port) + self.register_plugin('xep_0030') + self.register_plugin("AliasPlugin", module = "plugin") + self.add_event_handler("session_start", self.start) + self.add_event_handler("presence_probe", self.presence_probe) + self.add_event_handler("message", self.message) + self.add_event_handler("changed_subscription", self.presence_subscription) + self.userHandler = UserHandler(root) + + def start(self, event): + for user in self.userHandler.get_user_list(): + self.send_presence(pto = user) + + def disconnect(self, reconnect = False): + for user in self.userHandler.get_user_list(): + self.send_presence(pto = user, ptype = "unavailable") + XMLStream.disconnect(self, reconnect) + logging.info('Component {} disconnected'.format(self.boundjid.bare)) + + def message(self, msg): + msg.reply("Thanks for sending\n{[body]}".format(msg)).send() + + def presence_subscription(self, subscription): + if subscription["type"] == "subscribe": + userJID = subscription["from"].full + self.userHandler.register(userJID) + subscription.reply().send() + self.send_presence(pto = userJID) + self.send_presence_subscription(pto = userJID, ptype = "subscribe") + if subscription["type"] == "unsubscribe": + userJID = subscription["from"].full + self.userHandler.unregister(userJID) + + def presence_probe(self, event): + self.send_presence(pto = event["from"].full) + + +if __name__ == '__main__': + commandline = ArgumentParser(description = 'Connect the alias \ + component to a given server') + commandline.add_argument('-p', '--port', + help = 'Port to connect to', + type = int) + commandline.add_argument('-s', '--secret', + help = 'password') + commandline.add_argument('-n', '--name', + help = 'Name the component will have') + commandline.add_argument('-r', '--root', + help = 'Root directory of the user files') + commandline.add_argument('-c', '--config', + help = 'Name of the config file to use') + commandline.add_argument('-d', '--debug', + help = 'Set log level to DEBUG', + action = 'store_const', + const = logging.DEBUG, + default = logging.INFO) + commandline.add_argument('-o', '--host', + help = 'Host to connect to') + + args = commandline.parse_args() + + if args.config is None: + logging.basicConfig(level = args.debug) + config.name = args.name + config.port = args.port + config.secret = args.secret + config.root = args.root + config.host = args.host + else: + filename = args.config + logging.basicConfig(level = args.debug) + config.read(filename) + + component = ObjectComponent(config.name, config.secret, + config.host, config.port, + config.root) + + if component.connect(): + logging.info('Component {} connected'.format(component.boundjid)) + component.process(False) + else : + print "Couldn't connect" + + diff --git a/server/ssh_rsa_key_util.py b/server/ssh_rsa_key_util.py new file mode 100644 index 0000000..c25a112 --- /dev/null +++ b/server/ssh_rsa_key_util.py @@ -0,0 +1,209 @@ +import base64 +import struct +import filecmp +from keyczar import util, keys +from Crypto.PublicKey import RSA + +# need pyasn for DER parsing and generating +from pyasn1.type import univ +from pyasn1.codec.der import decoder, encoder + +def read_int(buffer, i): + "Read 32bit integer from buffer." + + (l,) = struct.unpack('!I', buffer[i:i + 4]) + i += 4 + return (l, i) + +def read_chunk(buffer, i): + "Read chunk from buffer." + + # first grab length of chunk + (l, i) = read_int(buffer, i) + if l > 1000000: + # just in case... if this happens, then something is way off + raise Exception("got chunk length of %d, that's certainly too long" % l) + + # read chunk of length l + (s,) = struct.unpack('!{0:d}s'.format(l), buffer[i:i + l]) + i += l + return (s, i) + +def read_rsa_pub(filename): + """Read RSA public key file. Structure: + + ssh-rsa base64data user@host + + base64data: [7]ssh-rsa[len][e-data][len][n-data] + """ + + [prefix, data, host] = file(filename, 'r').read().split() + raw = base64.b64decode(data) + + # read type string + i = 0 + (s, i) = read_chunk(raw, i) + if s != 'ssh-rsa': + raise Exception("expected string 'ssh-rsa' but got '%s'" % s) + + # grab e + (s, i) = read_chunk(raw, i) + e = util.BytesToLong(s) + # grab n + (s, i) = read_chunk(raw, i) + n = util.BytesToLong(s) + return (n, e, host) + +def write_rsa_pub(filename, n, e, host): + """Write RSA public key file. Structure: + + ssh-rsa base64data user@host + + base64data: [7]ssh-rsa[len][e-data][len][n-data] + """ + e_str = util.BigIntToBytes(e) + n_str = util.BigIntToBytes(n) + + # pack e and n properly into the raw data + raw = struct.pack('!I7sI{0:d}sI{1:d}s'.format(len(e_str), len(n_str)), 7, 'ssh-rsa', + len(e_str), e_str, len(n_str), n_str) + # assemble file content and save it + content = "ssh-rsa {0!s} {1!s}\n".format(base64.b64encode(raw), host) + file(filename, 'w').write(content) + +def read_rsa_pri(filename): + """Read RSA private key file. Structure: + + -----BEGIN RSA PRIVATE KEY----- + base64data + -----END RSA PRIVATE KEY----- + + base64data DER structure: + + RSAPrivateKey ::= SEQUENCE { + version Version, + modulus INTEGER, -- n + publicExponent INTEGER, -- e + privateExponent INTEGER, -- d + prime1 INTEGER, -- p + prime2 INTEGER, -- q + exponent1 INTEGER, -- d mod (p - 1) + exponent2 INTEGER, -- d mod (q - 1) + coefficient INTEGER -- q^-1 mod p + } + """ + + # grab only the lines between the --- * --- lines, glue them together + data = ''.join(filter(lambda x: x and x[0] != '-', + file(filename, 'r').read().split('\n'))) + # decode from base64 + raw = base64.b64decode(data) + # parse DER structure + der = decoder.decode(raw) + (version, n, e, d, p, q, e1, e2, c) = (int(x) for x in der[0]) + + return (n, e, d, p, q, e1, e2, c) + +def write_rsa_pri(filename, n, e, d, p, q, e1, e2, c): + """Write RSA private key file. Structure: + + -----BEGIN RSA PRIVATE KEY----- + base64data + -----END RSA PRIVATE KEY----- + + base64data DER structure: + + RSAPrivateKey ::= SEQUENCE { + version Version, + modulus INTEGER, -- n + publicExponent INTEGER, -- e + privateExponent INTEGER, -- d + prime1 INTEGER, -- p + prime2 INTEGER, -- q + exponent1 INTEGER, -- d mod (p - 1) + exponent2 INTEGER, -- d mod (q - 1) + coefficient INTEGER -- q^-1 mod p + } + """ + + seq = ( + univ.Integer(0), + univ.Integer(n), + univ.Integer(e), + univ.Integer(d), + univ.Integer(p), + univ.Integer(q), + univ.Integer(e1), + univ.Integer(e2), + univ.Integer(c), + ) + struct = univ.Sequence() + for i in xrange(len(seq)): + struct.setComponentByPosition(i, seq[i]) + + # build DER structure + raw = encoder.encode(struct) + # encode to base64 + data = base64.b64encode(raw) + + # chop data up into lines of certain width + width = 64 + chopped = [data[i:i + width] for i in xrange(0, len(data), width)] + # assemble file content + content = """-----BEGIN RSA PRIVATE KEY----- +{0} +-----END RSA PRIVATE KEY----- +""".format('\n'.join(chopped)) + file(filename, 'w').write(content) + + +class SshRsaPublicKey(keys.RsaPublicKey): + @staticmethod + def Read(keyfile): + (n, e, host) = read_rsa_pub(keyfile) + params = {'modulus' : util.PadBytes(util.BigIntToBytes(n), 1), + 'publicExponent' : util.PadBytes(util.BigIntToBytes(e), 1)} + pubkey = RSA.construct((util.BytesToLong(params['modulus']), + util.BytesToLong(params['publicExponent']))) + return keys.RsaPublicKey(params, pubkey) + +class SshRsaPrivateKey(keys.RsaPrivateKey): + @staticmethod + def Read(keyfile): + (n, e, d, p, q, e1, e2, c) = read_rsa_pri(keyfile) + params = {'modulus' : util.PadBytes(util.BigIntToBytes(n), 1), + 'publicExponent' : util.PadBytes(util.BigIntToBytes(e), 1)} + pubkey = RSA.construct((util.BytesToLong(params['modulus']), + util.BytesToLong(params['publicExponent']))) + pub = keys.RsaPublicKey(params,pubkey) + params = {'privateExponent': util.PadBytes(util.BigIntToBytes(d),1), + 'primeP': util.PadBytes(util.BigIntToBytes(p),1), + 'primeQ': util.PadBytes(util.BigIntToBytes(q),1), + 'primeExponentP': util.PadBytes(util.BigIntToBytes(e1),1), + 'primeExponentQ': util.PadBytes(util.BigIntToBytes(e2),1), + 'crtCoefficient': util.PadBytes(util.BigIntToBytes(c),1), + } + key = RSA.construct((util.BytesToLong(pub.params['modulus']), + util.BytesToLong(pub.params['publicExponent']), + util.BytesToLong(params['privateExponent']), + util.BytesToLong(params['primeQ']), + util.BytesToLong(params['primeP']), + util.BytesToLong(params['crtCoefficient']))) + return keys.RsaPrivateKey(params, pub, key) + +if __name__ == '__main__' : + ssh_keys_directory='/home/guillaume/.ssh/' + print 'Testing public key reading...' + (n,e,host)=read_rsa_pub(ssh_keys_directory + 'id_rsa.pub') + test = SshRsaPublicKey.Read(ssh_keys_directory + 'id_rsa.pub') + write_rsa_pub(ssh_keys_directory + 'id_rsa_test.pub',n,e,host) + if filecmp.cmp(ssh_keys_directory + 'id_rsa.pub',ssh_keys_directory + 'id_rsa_test.pub'): + print 'test succesful' + + print 'Testing private key reading...' + (n, e, d, p, q, e1, e2, c)=read_rsa_pri('/home/guillaume/.ssh/id_rsa') + write_rsa_pri('/home/guillaume/.ssh/id_rsa_test',n, e, d, p, q, e1, e2, c) + if filecmp.cmp(ssh_keys_directory + 'id_rsa',ssh_keys_directory + 'id_rsa_test'): + print 'test succesful' + +
\ No newline at end of file diff --git a/server/user.py b/server/user.py new file mode 100644 index 0000000..411ada6 --- /dev/null +++ b/server/user.py @@ -0,0 +1,32 @@ +import os +import os.path +import shutil +import object +import hashlib + +class UserHandler: + + def __init__(self, root): + self.root = root + + def register(self, name): + userDir = self.root + '/' + name + if not os.path.exists(userDir) : + os.mkdir(userDir) + handler = object.ObjectHandler(name) + handler.create_home_node() + + def registered(self, name): + return os.path.exists(self.root + '/' + name) + + def unregister(self, name): + shutil.rmtree(self.root + '/' + name) + + def get_user_list(self): + return os.listdir(self.root) + +class User: + + def __init__(self, name): + self.name = name + self.rootObject = hashlib.sha1(name).hexdigest() diff --git a/server/version.py b/server/version.py new file mode 100644 index 0000000..306cebb --- /dev/null +++ b/server/version.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +MAJOR=0 +MINOR=1 +TYPE='dev' +VERSION = str(MAJOR)+'.'+str(MINOR)+TYPE #TODO: add the commit number ? |
