Commit f2e11d86 authored by Julien Muchembled's avatar Julien Muchembled

INCOMPATIBLE: change registry protocol

- authenticated communications with registered clients
- XML-RPC is dropped
- multi-threaded server
parent 746b369c
......@@ -278,8 +278,9 @@ if len(sys.argv) > 1:
elif self.path == '/tunnel.html':
other = 'route'
gv = registry.Popen(('python', '-c', r"""if 1:
import math, xmlrpclib
g = xmlrpclib.ServerProxy('http://localhost/').topology()
import math
from re6st.registry import RegistryClient
g = eval(RegistryClient('http://localhost/').topology())
print 'digraph {'
a = 2 * math.pi / len(g)
z = 4
......@@ -293,7 +294,7 @@ if len(sys.argv) > 1:
for p in p or ():
print '"%s" -> "%s";' % (n, title(p))
print '}'
"""), stdout=subprocess.PIPE).communicate()[0]
"""), stdout=subprocess.PIPE, cwd="..").communicate()[0]
if gv:
svg = subprocess.Popen(('neato', '-Tsvg'),
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
......
#!/usr/bin/python
import argparse, atexit, errno, os, subprocess, sqlite3, sys, xmlrpclib
import argparse, atexit, errno, os, subprocess, sqlite3, sys
from OpenSSL import crypto
from re6st import utils
from re6st import registry, utils
def create(path, text=None, mode=0666):
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode)
......@@ -10,6 +10,9 @@ def create(path, text=None, mode=0666):
finally:
os.close(fd)
def loadCert(pem):
return crypto.load_certificate(crypto.FILETYPE_PEM, pem)
def main():
parser = argparse.ArgumentParser(
description="Setup script for re6stnet.",
......@@ -47,11 +50,11 @@ def main():
dh_path = 'dh2048.pem'
# Establish connection with server
s = xmlrpclib.ServerProxy(config.registry, allow_none=True)
s = registry.RegistryClient(config.registry)
# Get CA
ca = s.getCa()
network = utils.networkFromCa(ca)
network = utils.networkFromCa(loadCert(ca))
if config.is_needed:
route, err = subprocess.Popen(('ip', '-6', '-o', 'route', 'get',
utils.ipFromBin(network)),
......@@ -72,7 +75,7 @@ def main():
req = crypto.X509Req()
try:
with open(cert_path) as f:
cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
cert = loadCert(f.read())
components = dict(cert.get_subject().get_components())
components.pop('CN', None)
except IOError, e:
......@@ -157,7 +160,7 @@ dh %s
""" % (config.registry, ca_path, cert_path, key_path, dh_path))
print "Sample configuration file created."
cn = utils.subnetFromCert(cert)
cn = utils.subnetFromCert(loadCert(cert))
subnet = network + utils.binFromSubnet(cn)
print "Your subnet: %s/%u (CN=%s)" \
% (utils.ipFromBin(subnet), len(subnet), cn)
......
#!/usr/bin/python
import errno, logging, mailbox, os, random, select
import smtplib, socket, sqlite3, string, subprocess, sys
import threading, time, traceback, xmlrpclib
from collections import deque
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
from email.mime.text import MIMEText
from OpenSSL import crypto
from re6st import tunnel, utils
import errno, httplib, logging, select, socket
from BaseHTTPServer import BaseHTTPRequestHandler
from SocketServer import ThreadingTCPServer
from urlparse import parse_qsl
from re6st import registry, utils
# To generate server ca and key with serial for 2001:db8:42::/48
# openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 365 -out ca.crt
# openssl req -nodes -new -x509 -key ca.key -set_serial 0x120010db80042 -days 3650 -out ca.crt
# TODO: There is currently no mechanism so that client automatically get
# a renewed CA certificate.
IPV6_V6ONLY = 26
SOL_IPV6 = 41
class RequestHandler(SimpleXMLRPCRequestHandler):
class RequestHandler(BaseHTTPRequestHandler):
def address_string(self):
# Workaround for http://bugs.python.org/issue6085
return self.client_address[0]
def _dispatch(self, method, params):
logging.debug('%s%r', method, params)
return self.server._dispatch(method, (self,) + params)
def do_GET(self):
try:
try:
path, query = self.path.split('?', 1)
except ValueError:
path = self.path
query = {}
else:
query = dict(parse_qsl(query, keep_blank_values=1,
strict_parsing=1))
_, path = path.split('/')
if not _ and path[0] != '_':
return self.server._handle_request(self, path, query)
except Exception:
logging.info(self.requestline, exc_info=1)
self.send_error(httplib.BAD_REQUEST)
def log_error(*args):
pass
class SimpleXMLRPCServer4(SimpleXMLRPCServer):
class HTTPServer4(ThreadingTCPServer):
allow_reuse_address = True
daemon_threads = True
class SimpleXMLRPCServer6(SimpleXMLRPCServer4):
class HTTPServer6(HTTPServer4):
address_family = socket.AF_INET6
def server_bind(self):
self.socket.setsockopt(SOL_IPV6, IPV6_V6ONLY, 1)
SimpleXMLRPCServer4.server_bind(self)
class main(object):
def __init__(self):
self.cert_duration = 365 * 86400
self.time_out = 45000
self.refresh_interval = 600
self.last_refresh = time.time()
# Command line parsing
parser = utils.ArgParser(fromfile_prefix_chars='@',
description="re6stnet registry used to bootstrap nodes"
" and deliver certificates.")
_ = parser.add_argument
_('--port', type=int, default=80,
help="Port on which the server will listen.")
_('-4', dest='bind4', default='0.0.0.0',
help="Bind server to this IPv4.")
_('-6', dest='bind6', default='::',
help="Bind server to this IPv6.")
_('--db', default='/var/lib/re6stnet/registry.db',
help="Path to SQLite database file. It is automatically initialized"
" if the file does not exist.")
_('--ca', required=True, help=parser._ca_help)
_('--key', required=True,
help="CA private key in .pem format.")
_('--mailhost', required=True,
help="SMTP host to send confirmation emails. For debugging"
" purpose, it can also be an absolute or existing path to"
" a mailbox file")
_('--private',
help="re6stnet IP of the node on which runs the registry."
" Required for normal operation.")
_('--prefix-length', default=16, type=int,
help="Default length of allocated prefixes.")
_('--anonymous-prefix-length', type=int,
help="Length of allocated anonymous prefixes."
" If 0 or unset, registration by email is required")
_('-l', '--logfile', default='/var/log/re6stnet/registry.log',
help="Path to logging file.")
_('-v', '--verbose', default=1, type=int,
help="Log level. 0 disables logging."
" Use SIGUSR1 to reopen log.")
self.config = parser.parse_args()
utils.setupLog(self.config.verbose, self.config.logfile)
if self.config.private:
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
else:
logging.warning('You have declared no private address'
', either this is the first start, or you should'
'check you configuration')
# Database initializing
utils.makedirs(os.path.dirname(self.config.db))
self.db = sqlite3.connect(self.config.db, isolation_level=None)
self.db.execute("""CREATE TABLE IF NOT EXISTS token (
token text primary key not null,
email text not null,
prefix_len integer not null,
date integer not null)""")
try:
self.db.execute("""CREATE TABLE cert (
prefix text primary key not null,
email text,
cert text)""")
except sqlite3.OperationalError, e:
if e.args[0] != 'table cert already exists':
raise RuntimeError
else:
self.db.execute("INSERT INTO cert VALUES ('',null,null)")
# Loading certificates
with open(self.config.ca) as f:
self.ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
with open(self.config.key) as f:
self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
# Get vpn network prefix
self.network = bin(self.ca.get_serial_number())[3:]
logging.info("Network: %s/%u", utils.ipFromBin(self.network),
len(self.network))
self._email = self.ca.get_subject().emailAddress
# Starting server
server_list = []
if self.config.bind4:
server4 = SimpleXMLRPCServer4((self.config.bind4, self.config.port),
requestHandler=RequestHandler, allow_none=True)
server4.register_instance(self)
server_list.append(server4)
if self.config.bind6:
server6 = SimpleXMLRPCServer6((self.config.bind6, self.config.port),
requestHandler=RequestHandler, allow_none=True)
server6.register_instance(self)
server_list.append(server6)
if len(server_list) == 1:
server_list[0].serve_forever()
else:
while True:
try:
r = select.select(server_list[:], [], [])[0]
except select.error as e:
if e.args[0] != errno.EINTR:
raise
else:
for r in r:
r._handle_request_noblock()
def requestToken(self, handler, email):
HTTPServer4.server_bind(self)
def main():
parser = utils.ArgParser(fromfile_prefix_chars='@',
description="re6stnet registry used to bootstrap nodes"
" and deliver certificates.")
_ = parser.add_argument
_('--port', type=int, default=80,
help="Port on which the server will listen.")
_('-4', dest='bind4', default='0.0.0.0',
help="Bind server to this IPv4.")
_('-6', dest='bind6', default='::',
help="Bind server to this IPv6.")
_('--db', default='/var/lib/re6stnet/registry.db',
help="Path to SQLite database file. It is automatically initialized"
" if the file does not exist.")
_('--ca', required=True, help=parser._ca_help)
_('--key', required=True,
help="CA private key in .pem format.")
_('--mailhost', required=True,
help="SMTP host to send confirmation emails. For debugging"
" purpose, it can also be an absolute or existing path to"
" a mailbox file")
_('--private',
help="re6stnet IP of the node on which runs the registry."
" Required for normal operation.")
_('--prefix-length', default=16, type=int,
help="Default length of allocated prefixes.")
_('--anonymous-prefix-length', type=int,
help="Length of allocated anonymous prefixes."
" If 0 or unset, registration by email is required")
_('-l', '--logfile', default='/var/log/re6stnet/registry.log',
help="Path to logging file.")
_('-v', '--verbose', default=1, type=int,
help="Log level. 0 disables logging."
" Use SIGUSR1 to reopen log.")
config = parser.parse_args()
utils.setupLog(config.verbose, config.logfile)
server = registry.RegistryServer(config)
def requestHandler(request, client_address, _):
RequestHandler(request, client_address, server)
server_list = []
if config.bind4:
server_list.append(HTTPServer4((config.bind4, config.port),
requestHandler))
if config.bind6:
server_list.append(HTTPServer6((config.bind6, config.port),
requestHandler))
if server_list:
empty_list = []
while True:
# Generating token
token = ''.join(random.sample(string.ascii_lowercase, 8))
args = token, email, self.config.prefix_length, int(time.time())
# Updating database
try:
self.db.execute("INSERT INTO token VALUES (?,?,?,?)", args)
break
except sqlite3.IntegrityError:
pass
# Creating and sending email
msg = MIMEText('Hello, your token to join re6st network is: %s\n'
% token)
msg['Subject'] = '[re6stnet] Token Request'
if self._email:
msg['From'] = self._email
msg['To'] = email
if os.path.isabs(self.config.mailhost) or \
os.path.isfile(self.config.mailhost):
m = mailbox.mbox(self.config.mailhost)
try:
m.add(msg)
finally:
m.close()
else:
s = smtplib.SMTP(self.config.mailhost)
s.sendmail(self._email, email, msg.as_string())
s.quit()
def _getPrefix(self, prefix_len):
max_len = 128 - len(self.network)
assert 0 < prefix_len <= max_len
try:
prefix, = self.db.execute("""SELECT prefix FROM cert WHERE length(prefix) <= ? AND cert is null
ORDER BY length(prefix) DESC""", (prefix_len,)).next()
except StopIteration:
logging.error('No more free /%u prefix available', prefix_len)
raise
while len(prefix) < prefix_len:
self.db.execute("UPDATE cert SET prefix = ? WHERE prefix = ?", (prefix + '1', prefix))
prefix += '0'
self.db.execute("INSERT INTO cert VALUES (?,null,null)", (prefix,))
if len(prefix) < max_len or '1' in prefix:
return prefix
self.db.execute("UPDATE cert SET cert = 'reserved' WHERE prefix = ?", (prefix,))
return self._getPrefix(prefix_len)
def requestCertificate(self, handler, token, cert_req):
try:
req = crypto.load_certificate_request(crypto.FILETYPE_PEM, cert_req)
with self.db:
if token is None:
prefix_len = self.config.anonymous_prefix_length
if not prefix_len:
return
email = None
else:
try:
token, email, prefix_len, _ = self.db.execute(
"SELECT * FROM token WHERE token = ?",
(token,)).next()
except StopIteration:
return
self.db.execute("DELETE FROM token WHERE token = ?", (token,))
# Get a new prefix
prefix = self._getPrefix(prefix_len)
# Create certificate
cert = crypto.X509()
cert.set_serial_number(0) # required for libssl < 1.0
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(self.cert_duration)
cert.set_issuer(self.ca.get_subject())
subject = req.get_subject()
subject.CN = "%u/%u" % (int(prefix, 2), prefix_len)
cert.set_subject(subject)
cert.set_pubkey(req.get_pubkey())
cert.sign(self.key, 'sha1')
cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
# Insert certificate into db
self.db.execute("UPDATE cert SET email = ?, cert = ? WHERE prefix = ?", (email, cert, prefix))
return cert
except Exception:
f = traceback.format_exception(*sys.exc_info())
logging.error('%s%s', f.pop(), ''.join(f))
raise
def getCa(self, handler):
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.ca)
def getPrivateAddress(self, handler):
return self.config.private
def getBootstrapPeer(self, handler, client_prefix):
cert, = self.db.execute("SELECT cert FROM cert WHERE prefix = ?",
(client_prefix,)).next()
address = self.config.private, tunnel.PORT
self.sock.sendto('\2', address)
peer = None
while select.select([self.sock], [], [], peer is None)[0]:
msg = self.sock.recv(1<<16)
if msg[0] == '\1':
try:
peer = msg[1:].split('\n')[-2]
except IndexError:
peer = ''
if peer is None:
raise EnvironmentError("Timeout while querying [%s]:%u" % address)
if not peer or peer.split()[0] == client_prefix:
raise LookupError("No bootstrap peer found")
logging.info("Sending bootstrap peer: %s", peer)
r, w = os.pipe()
try:
threading.Thread(target=os.write, args=(w, cert)).start()
p = subprocess.Popen(('openssl', 'rsautl', '-encrypt', '-certin', '-inkey', '/proc/self/fd/%u' % r),
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
return xmlrpclib.Binary(p.communicate(peer)[0])
finally:
os.close(r)
os.close(w)
r = select.select(server_list[:], empty_list, empty_list)[0]
except select.error as e:
if e.args[0] != errno.EINTR:
raise
else:
for r in r:
r._handle_request_noblock()
def topology(self, handler):
if handler.client_address[0] in ('127.0.0.1', '::'):
is_registry = utils.binFromIp(self.config.private
)[len(self.network):].startswith
peers = deque('%u/%u' % (int(x, 2), len(x))
for x, in self.db.execute("SELECT prefix FROM cert")
if is_registry(x))
assert len(peers) == 1
cookie = hex(random.randint(0, 1<<32))[2:]
graph = dict.fromkeys(peers)
asked = 0
while True:
r, w, _ = select.select([self.sock],
[self.sock] if peers else [], [], 1)
if r:
answer = self.sock.recv(1<<16)
if answer[0] == '\xfe':
answer = answer[1:].split('\n')[:-1]
if len(answer) >= 3 and answer[0] == cookie:
x = answer[3:]
assert answer[1] not in x, (answer, graph)
graph[answer[1]] = x[:int(answer[2])]
x = set(x).difference(graph)
peers += x
graph.update(dict.fromkeys(x))
if w:
x = utils.binFromSubnet(peers.popleft())
x = utils.ipFromBin(self.network + x)
try:
self.sock.sendto('\xff%s\n' % cookie, (x, tunnel.PORT))
except socket.error:
pass
elif not r:
break
return graph
if __name__ == "__main__":
main()
import logging, sqlite3, socket, subprocess, xmlrpclib, time
from urllib import splittype, splithost, splitport
import utils
import logging, sqlite3, socket, subprocess, time
from . import utils
class PeerDB(object):
......@@ -10,7 +9,7 @@ class PeerDB(object):
self._prefix = prefix
self._db_size = db_size
self._key_path = key_path
self._proxy = xmlrpclib.ServerProxy(registry)
self._registry = registry
logging.info('Initialize cache ...')
self._db = sqlite3.connect(db_path, isolation_level=None)
......@@ -37,7 +36,7 @@ class PeerDB(object):
retry = 1
while True:
try:
a = self._proxy.getPrivateAddress()
a = self._registry.getPrivateAddress(self._prefix)
break
except socket.error, e:
logging.warning(e)
......@@ -83,16 +82,9 @@ class PeerDB(object):
def getBootstrapPeer(self):
logging.info('Getting Boot peer...')
try:
bootpeer = self._proxy.getBootstrapPeer(self._prefix).data
p = subprocess.Popen(
('openssl', 'rsautl', '-decrypt', '-inkey', self._key_path),
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out, err = p.communicate(bootpeer)
if p.returncode:
raise subprocess.CalledProcessError(p.returncode, err)
prefix, address = out.split()
except (socket.error, xmlrpclib.Fault, subprocess.CalledProcessError,
ValueError), e:
bootpeer = self._registry.getBootstrapPeer(self._prefix)
prefix, address = utils.decrypt(self._key_path, bootpeer).split()
except (socket.error, subprocess.CalledProcessError, ValueError), e:
logging.warning('Failed to bootstrap (%s)', e)
else:
if prefix != self._prefix:
......
import base64, hmac, hashlib, httplib, inspect, logging, mailbox, os, random
import select, smtplib, socket, sqlite3, string, struct, threading, time
from collections import deque
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from email.mime.text import MIMEText
from OpenSSL import crypto
from urllib import splittype, splithost, splitport, urlencode
from . import tunnel, utils
HMAC_HEADER = "Re6stHMAC"
class getcallargs(type):
def __init__(cls, name, bases, d):
type.__init__(cls, name, bases, d)
for n, f in d.iteritems():
if n[0] == '_':
continue
try:
args, varargs, varkw, defaults = inspect.getargspec(f)
except TypeError:
continue
if varargs or varkw or defaults:
continue
f.getcallargs = eval("lambda %s: locals()" % ','.join(args[1:]))
class RegistryServer(object):
__metaclass__ = getcallargs
def __init__(self, config):
self.config = config
self.cert_duration = 365 * 86400
self.lock = threading.Lock()
self.sessions = {}
if self.config.private:
self.sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
else:
logging.warning('You have declared no private address'
', either this is the first start, or you should'
'check you configuration')
# Database initializing
utils.makedirs(os.path.dirname(self.config.db))
self.db = sqlite3.connect(self.config.db, isolation_level=None,
check_same_thread=False)
self.db.execute("""CREATE TABLE IF NOT EXISTS token (
token text primary key not null,
email text not null,
prefix_len integer not null,
date integer not null)""")
try:
self.db.execute("""CREATE TABLE cert (
prefix text primary key not null,
email text,
cert text)""")
except sqlite3.OperationalError, e:
if e.args[0] != 'table cert already exists':
raise RuntimeError
else:
self.db.execute("INSERT INTO cert VALUES ('',null,null)")
# Loading certificates
with open(self.config.ca) as f:
self.ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
with open(self.config.key) as f:
self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
# Get vpn network prefix
self.network = utils.networkFromCa(self.ca)
logging.info("Network: %s/%u", utils.ipFromBin(self.network),
len(self.network))
self._email = self.ca.get_subject().emailAddress
def _handle_request(self, request, method, kw):
m = getattr(self, method)
if method in ('topology',) and \
request.client_address[0] not in ('127.0.0.1', '::'):
return request.send_error(httplib.FORBIDDEN)
key = m.getcallargs(**kw).get('cn')
if key:
h = base64.b64decode(request.headers[HMAC_HEADER])
with self.lock:
session = self.sessions[key]
for key in session:
if h == hmac.HMAC(key, request.path, hashlib.sha1).digest():
break
else:
raise Exception("Wrong HMAC")
key = hashlib.sha1(key).digest()
session[:] = hashlib.sha1(key).digest(),
try:
result = m(**kw)
except:
logging.warning(request.requestline, exc_info=1)
return request.send_error(httplib.INTERNAL_SERVER_ERROR)
if result:
request.send_response(httplib.OK)
request.send_header("Content-Length", str(len(result)))
else:
request.send_response(httplib.NO_CONTENT)
if key:
request.send_header(HMAC_HEADER, base64.b64encode(
hmac.HMAC(key, result, hashlib.sha1).digest()))
request.end_headers()
if result:
request.wfile.write(result)
def hello(self, client_prefix):
with self.lock:
cert = self._getCert(client_prefix)
key = hashlib.sha1(struct.pack('Q',
random.getrandbits(64))).digest()
self.sessions.setdefault(client_prefix, [])[1:] = key,
key = utils.encrypt(cert, key)
sign = crypto.sign(self.key, key, 'sha1')
assert len(key) == len(sign)
return key + sign
def _getCert(self, client_prefix):
assert self.lock.locked()
return self.db.execute("SELECT cert FROM cert WHERE prefix = ?",
(client_prefix,)).next()[0]
def requestToken(self, email):
while True:
# Generating token
token = ''.join(random.sample(string.ascii_lowercase, 8))
args = token, email, self.config.prefix_length, int(time.time())
# Updating database
try:
self.db.execute("INSERT INTO token VALUES (?,?,?,?)", args)
break
except sqlite3.IntegrityError:
pass
# Creating and sending email
msg = MIMEText('Hello, your token to join re6st network is: %s\n'
% token)
msg['Subject'] = '[re6stnet] Token Request'
if self._email:
msg['From'] = self._email
msg['To'] = email
if os.path.isabs(self.config.mailhost) or \
os.path.isfile(self.config.mailhost):
with self.lock:
m = mailbox.mbox(self.config.mailhost)
try:
m.add(msg)
finally:
m.close()
else:
s = smtplib.SMTP(self.config.mailhost)
s.sendmail(self._email, email, msg.as_string())
s.quit()
def _getPrefix(self, prefix_len):
max_len = 128 - len(self.network)
assert 0 < prefix_len <= max_len
try:
prefix, = self.db.execute("""SELECT prefix FROM cert WHERE length(prefix) <= ? AND cert is null
ORDER BY length(prefix) DESC""", (prefix_len,)).next()
except StopIteration:
logging.error('No more free /%u prefix available', prefix_len)
raise
while len(prefix) < prefix_len:
self.db.execute("UPDATE cert SET prefix = ? WHERE prefix = ?", (prefix + '1', prefix))
prefix += '0'
self.db.execute("INSERT INTO cert VALUES (?,null,null)", (prefix,))
if len(prefix) < max_len or '1' in prefix:
return prefix
self.db.execute("UPDATE cert SET cert = 'reserved' WHERE prefix = ?", (prefix,))
return self._getPrefix(prefix_len)
def requestCertificate(self, token, req):
req = crypto.load_certificate_request(crypto.FILETYPE_PEM, req)
with self.lock:
with self.db:
if token is None:
prefix_len = self.config.anonymous_prefix_length
if not prefix_len:
return
email = None
else:
try:
token, email, prefix_len, _ = self.db.execute(
"SELECT * FROM token WHERE token = ?",
(token,)).next()
except StopIteration:
return
self.db.execute("DELETE FROM token WHERE token = ?", (token,))
# Get a new prefix
prefix = self._getPrefix(prefix_len)
# Create certificate
cert = crypto.X509()
cert.set_serial_number(0) # required for libssl < 1.0
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(self.cert_duration)
cert.set_issuer(self.ca.get_subject())
subject = req.get_subject()
subject.CN = "%u/%u" % (int(prefix, 2), prefix_len)
cert.set_subject(subject)
cert.set_pubkey(req.get_pubkey())
cert.sign(self.key, 'sha1')
cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
# Insert certificate into db
self.db.execute("UPDATE cert SET email = ?, cert = ? WHERE prefix = ?", (email, cert, prefix))
return cert
def getCa(self):
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.ca)
def getPrivateAddress(self, cn):
return self.config.private
def getBootstrapPeer(self, cn):
with self.lock:
cert = self._getCert(cn)
address = self.config.private, tunnel.PORT
self.sock.sendto('\2', address)
peer = None
while select.select([self.sock], [], [], peer is None)[0]:
msg = self.sock.recv(1<<16)
if msg[0] == '\1':
try:
peer = msg[1:].split('\n')[-2]
except IndexError:
peer = ''
if peer is None:
raise EnvironmentError("Timeout while querying [%s]:%u" % address)
if not peer or peer.split()[0] == cn:
raise LookupError("No bootstrap peer found")
logging.info("Sending bootstrap peer: %s", peer)
return utils.encrypt(cert, peer)
def topology(self):
with self.lock:
is_registry = utils.binFromIp(self.config.private
)[len(self.network):].startswith
peers = deque('%u/%u' % (int(x, 2), len(x))
for x, in self.db.execute("SELECT prefix FROM cert")
if is_registry(x))
assert len(peers) == 1
cookie = hex(random.randint(0, 1<<32))[2:]
graph = dict.fromkeys(peers)
asked = 0
while True:
r, w, _ = select.select([self.sock],
[self.sock] if peers else [], [], 1)
if r:
answer = self.sock.recv(1<<16)
if answer[0] == '\xfe':
answer = answer[1:].split('\n')[:-1]
if len(answer) >= 3 and answer[0] == cookie:
x = answer[3:]
assert answer[1] not in x, (answer, graph)
graph[answer[1]] = x[:int(answer[2])]
x = set(x).difference(graph)
peers += x
graph.update(dict.fromkeys(x))
if w:
x = utils.binFromSubnet(peers.popleft())
x = utils.ipFromBin(self.network + x)
try:
self.sock.sendto('\xff%s\n' % cookie, (x, tunnel.PORT))
except socket.error:
pass
elif not r:
break
return repr(graph)
class RegistryClient(object):
_hmac = None
def __init__(self, url, key_path=None, ca=None, auto_close=True):
self.key_path = key_path
self.ca = ca
self.auto_close = auto_close
scheme, host = splittype(url)
host, path = splithost(host)
host, port = splitport(host)
self._conn = dict(http=httplib.HTTPConnection,
https=httplib.HTTPSConnection,
)[scheme](host, port)
self._path = path.rstrip('/')
def __getattr__(self, name):
getcallargs = getattr(RegistryServer, name).getcallargs
def rpc(*args, **kw):
kw = getcallargs(*args, **kw)
query = '/' + name
if kw:
if any(type(v) is not str for v in kw.itervalues()):
raise TypeError
query += '?' + urlencode(kw)
url = self._path + query
client_prefix = kw.get('cn')
retry = True
try:
while retry:
if client_prefix:
key = self._hmac
if not key:
retry = False
h = self.hello(client_prefix)
n = len(h) // 2
crypto.verify(self.ca, h[n:], h[:n], 'sha1')
key = utils.decrypt(self.key_path, h[:n])
h = hmac.HMAC(key, query, hashlib.sha1).digest()
key = hashlib.sha1(key).digest()
self._hmac = hashlib.sha1(key).digest()
else:
retry = False
self._conn.putrequest('GET', url, skip_accept_encoding=1)
if client_prefix:
self._conn.putheader(HMAC_HEADER, base64.b64encode(h))
self._conn.endheaders()
response = self._conn.getresponse()
body = response.read()
if response.status in (httplib.OK, httplib.NO_CONTENT) and (
not client_prefix or
hmac.HMAC(key, body, hashlib.sha1).digest() ==
base64.b64decode(response.msg[HMAC_HEADER])):
if self.auto_close and name != 'hello':
self._conn.close()
return body
if client_prefix:
self._hmac = None
except Exception:
logging.info(url, exc_info=1)
else:
logging.info('%s\nUnexpected response %s %s',
url, response.status, response.reason)
self._conn.close()
setattr(self, name, rpc)
return rpc
import argparse, errno, logging, os, shlex, signal, socket
import struct, subprocess, textwrap, threading, time
from OpenSSL import crypto
logging_levels = logging.WARNING, logging.INFO, logging.DEBUG, 5
......@@ -128,11 +127,9 @@ def ipFromBin(ip, suffix=''):
struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2)))
def networkFromCa(ca):
ca = crypto.load_certificate(crypto.FILETYPE_PEM, ca)
return bin(ca.get_serial_number())[3:]
def subnetFromCert(cert):
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
return cert.get_subject().CN
def dump_address(address):
......@@ -150,3 +147,27 @@ def parse_address(address_list):
def binFromSubnet(subnet):
p, l = subnet.split('/')
return bin(int(p))[2:].rjust(int(l), '0')
def decrypt(key_path, data):
p = subprocess.Popen(
('openssl', 'rsautl', '-decrypt', '-inkey', key_path),
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out, err = p.communicate(data)
if p.returncode:
raise subprocess.CalledProcessError(p.returncode, err)
return out
def encrypt(cert, data):
r, w = os.pipe()
try:
threading.Thread(target=os.write, args=(w, cert)).start()
p = subprocess.Popen(('openssl', 'rsautl', '-encrypt', '-certin',
'-inkey', '/proc/self/fd/%u' % r),
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
out, err = p.communicate(data)
finally:
os.close(r)
os.close(w)
if p.returncode:
raise subprocess.CalledProcessError(p.returncode, err)
return out
......@@ -2,7 +2,9 @@
import atexit, errno, logging, os, select, signal
import sqlite3, subprocess, sys, time, traceback
from collections import deque
from re6st import plib, utils, db, tunnel
from OpenSSL import crypto
from re6st import db, plib, tunnel, utils
from re6st.registry import RegistryClient
def getConfig():
......@@ -115,9 +117,11 @@ def main():
# Get arguments
config = getConfig()
with open(config.ca) as f:
network = utils.networkFromCa(f.read())
ca = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
with open(config.cert) as f:
prefix = utils.binFromSubnet(utils.subnetFromCert(f.read()))
cert = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
network = utils.networkFromCa(ca)
prefix = utils.binFromSubnet(utils.subnetFromCert(cert))
config.openvpn_args += (
'--ca', config.ca,
'--cert', config.cert,
......@@ -228,7 +232,8 @@ def main():
# Create and open read_only pipe to get server events
r_pipe, write_pipe = os.pipe()
read_pipe = os.fdopen(r_pipe)
peer_db = db.PeerDB(db_path, config.registry, config.key, prefix)
registry = RegistryClient(config.registry, config.key, ca)
peer_db = db.PeerDB(db_path, registry, config.key, prefix)
tunnel_manager = tunnel.TunnelManager(write_pipe, peer_db,
config.openvpn_args, timeout, config.tunnel_refresh,
config.client_count, config.iface_list, network, prefix,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment