Commit 8ebdd500 authored by Julien Muchembled's avatar Julien Muchembled

Certificate revocation, with broadcast of CRL

parent f73c51ec
......@@ -20,3 +20,8 @@
- registry: add '--home PATH' command line option so that / display an HTML
page from PATH (use new str.format for templating)
- Better UI to revoke certificates, for example with a HTML form.
Currently, one have to forge the URL manually. Examples:
wget -O /dev/null http://re6st.example.com/revoke?cn_or_serial=123
wget -O /dev/null http://re6st.example.com/revoke?cn_or_serial=4/16
......@@ -4,6 +4,8 @@ from . import utils, version, x509
class Cache(object):
crl = ()
def __init__(self, db_path, registry, cert, db_size=200):
self._prefix = cert.prefix
self._db_size = db_size
......@@ -40,6 +42,7 @@ class Cache(object):
# when it tried to send us new parameters.
or self._prefix == self.registry_prefix):
self.updateConfig()
self.next_renew = cert.maybeRenew(self._registry, self.crl)
if version.protocol < self.min_protocol:
logging.critical("Your version of re6stnet is too old."
" Please update.")
......@@ -64,7 +67,11 @@ class Cache(object):
cls = self.__class__
logging.debug("Loading network parameters:")
for k, v in config:
hasattr(cls, k) or setattr(self, k, v)
if k == 'crl':
v = set(json.loads(v))
elif hasattr(cls, k):
continue
setattr(self, k, v)
logging.debug("- %s: %r", k, v)
def updateConfig(self):
......@@ -77,6 +84,7 @@ class Cache(object):
config = dict((str(k), v.decode('base64') if k in base64 else
str(v) if type(v) is unicode else v)
for k, v in config.iteritems())
config['crl'] = json.dumps(config['crl'])
except socket.error, e:
logging.warning(e)
return
......
......@@ -9,7 +9,7 @@ if script_type == 'up':
os.execlp('ip', 'ip', 'link', 'set', os.environ['dev'], 'up',
'mtu', os.environ['tun_mtu'])
# Write into pipe external ip address received
import time
os.write(int(sys.argv[1]), "%s %s %s %s\n" % (script_type,
os.environ['common_name'], time.time(), os.environ['OPENVPN_external_ip']))
if script_type == 'route-up':
import time
os.write(int(sys.argv[1]), repr((os.environ['common_name'], time.time(),
int(os.environ['tls_serial_0']), os.environ['OPENVPN_external_ip'])))
......@@ -2,15 +2,16 @@
import os, sys
script_type = os.environ['script_type']
external_ip = lambda: os.getenv('trusted_ip') or os.environ['trusted_ip6']
external_ip = os.getenv('trusted_ip') or os.environ['trusted_ip6']
# Write into pipe connect/disconnect events
fd = int(sys.argv[1])
os.write(fd, repr((script_type, (os.environ['common_name'], os.environ['dev'],
int(os.environ['tls_serial_0']), external_ip))))
if script_type == 'client-connect':
if os.read(fd, 1) == '\0':
sys.exit(1)
# Send client its external ip address
with open(sys.argv[2], 'w') as f:
f.write('push "setenv-safe external_ip %s"\n' % external_ip())
# Write into pipe connect/disconnect events
arg1 = sys.argv[1]
if arg1 != 'None':
os.write(int(arg1), '%s %s %s\n' % (
script_type, os.environ['common_name'], external_ip()))
f.write('push "setenv-safe external_ip %s"\n' % external_ip)
......@@ -25,10 +25,8 @@ def openvpn(iface, encrypt, *args, **kw):
ovpn_link_mtu_dict = {'udp': 1481, 'udp6': 1450}
def server(iface, max_clients, dh_path, pipe_fd, port, proto, encrypt, *args, **kw):
client_script = '%s %s' % (ovpn_server, pipe_fd)
if pipe_fd is not None:
args = ('--client-disconnect', client_script) + args
def server(iface, max_clients, dh_path, fd, port, proto, encrypt, *args, **kw):
client_script = '%s %s' % (ovpn_server, fd)
try:
args = ('--link-mtu', str(ovpn_link_mtu_dict[proto]),
# mtu-disc ignored for udp6 due to a bug in OpenVPN
......@@ -39,6 +37,7 @@ def server(iface, max_clients, dh_path, pipe_fd, port, proto, encrypt, *args, **
'--tls-server',
'--mode', 'server',
'--client-connect', client_script,
'--client-disconnect', client_script,
'--dh', dh_path,
'--max-clients', str(max_clients),
'--port', str(port),
......
......@@ -25,6 +25,7 @@ from collections import defaultdict, deque
from datetime import datetime
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from email.mime.text import MIMEText
from operator import itemgetter
from OpenSSL import crypto
from urllib import splittype, splithost, splitport, urlencode
from . import ctl, tunnel, utils, version, x509
......@@ -71,6 +72,20 @@ class RegistryServer(object):
"email TEXT",
"cert TEXT"):
self.db.execute("INSERT INTO cert VALUES ('',null,null)")
if utils.sqliteCreateTable(self.db, "crl",
"serial INTEGER PRIMARY KEY NOT NULL",
# Expiration date of revoked certificate.
# TODO: purge rows with dates in the past.
"date INTEGER NOT NULL"):
# Revoke certificates produced by previous version.
# They all have serial 0.
try:
date = max(x509.notAfter(x[0]) for x in self.iterCert())
except ValueError:
pass
else:
if time.time() < date:
self.db.execute("INSERT INTO crl VALUES (0,?)", (date,))
self.cert = x509.Cert(self.config.ca, self.config.key)
# Get vpn network prefix
......@@ -97,9 +112,11 @@ class RegistryServer(object):
self.db.execute("INSERT OR REPLACE INTO config VALUES (?, ?)",
name_value)
def updateNetworkConfig(self):
def updateNetworkConfig(self, _it0=itemgetter(0)):
kw = {
'babel_default': 'max-rtt-penalty 5000 rtt-max 500 rtt-decay 125',
'crl': map(_it0, self.db.execute(
"SELECT serial FROM crl ORDER BY serial")),
'protocol': version.protocol,
'registry_prefix': self.prefix,
}
......@@ -220,7 +237,7 @@ class RegistryServer(object):
def handle_request(self, request, method, kw,
_localhost=('127.0.0.1', '::1')):
m = getattr(self, method)
if method in ('versions', 'topology'):
if method in ('revoke', 'versions', 'topology'):
x_forwarded_for = request.headers.get('X-Forwarded-For')
if request.client_address[0] not in _localhost or \
x_forwarded_for and x_forwarded_for not in _localhost:
......@@ -393,15 +410,16 @@ class RegistryServer(object):
@rpc
def renewCertificate(self, cn):
with self.lock:
with self.db:
with self.db as db:
pem = self.getCert(cn)
cert = crypto.load_certificate(crypto.FILETYPE_PEM, pem)
if x509.notAfter(cert) - RENEW_PERIOD < time.time():
not_after = None
elif cert.get_serial_number():
return pem
else:
elif db.execute("SELECT count(*) FROM crl WHERE serial=?",
(cert.get_serial_number(),)).fetchone()[0]:
not_after = cert.get_notAfter()
else:
return pem
return self.createCertificate(cn,
cert.get_subject(), cert.get_pubkey(), not_after)
......@@ -452,6 +470,29 @@ class RegistryServer(object):
logging.info("Sending bootstrap peer: %s", msg)
return x509.encrypt(cert, msg)
@rpc
def revoke(self, cn_or_serial):
with self.lock:
with self.db:
q = self.db.execute
try:
serial = int(cn_or_serial)
except ValueError:
prefix = utils.binFromSubnet(cn_or_serial)
cert = self.getCert(prefix)
q("UPDATE cert SET email=null, cert=null WHERE prefix=?",
(prefix,))
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
serial = cert.get_serial_number()
self.sessions.pop(prefix, None)
else:
cert, = (cert for cert, prefix, email in self.iterCert()
if cert.get_serial_number() == serial)
not_after = x509.notAfter(cert)
if time.time() < not_after:
q("INSERT INTO crl VALUES (?,?)", (serial, not_after))
self.updateNetworkConfig()
@rpc
def versions(self):
with self.peers_lock:
......
This diff is collapsed.
......@@ -42,10 +42,12 @@ def encrypt(cert, data):
def fingerprint(cert, alg='sha1'):
return hashlib.new(alg, crypto.dump_certificate(crypto.FILETYPE_ASN1, cert))
def maybe_renew(path, cert, info, renew):
def maybe_renew(path, cert, info, renew, force=False):
from .registry import RENEW_PERIOD
while True:
if cert.get_serial_number():
if force:
force = False
else:
next_renew = notAfter(cert) - RENEW_PERIOD
if time.time() < next_renew:
return cert, next_renew
......@@ -110,11 +112,10 @@ class Cert(object):
'--cert', self.cert_path,
'--key', self.key_path)
def maybeRenew(self, registry):
from .registry import RegistryClient
registry = RegistryClient(registry, self)
def maybeRenew(self, registry, crl):
self.cert, next_renew = maybe_renew(self.cert_path, self.cert,
"Certificate", lambda: registry.renewCertificate(self.prefix))
"Certificate", lambda: registry.renewCertificate(self.prefix),
self.cert.get_serial_number() in crl)
self.ca, ca_renew = maybe_renew(self.ca_path, self.ca,
"CA Certificate", registry.getCa)
return min(next_renew, ca_renew)
......@@ -181,6 +182,7 @@ class Peer(object):
"""
_hello = _last = 0
_key = newHmacSecret()
serial = None
stop_date = float('inf')
version = ''
......
......@@ -2,6 +2,7 @@
import atexit, errno, logging, os, shutil, signal
import socket, subprocess, sys, time, threading
from collections import deque
from functools import partial
from re6st import plib, tunnel, utils, version, x509
from re6st.cache import Cache
from re6st.utils import exit, ReexecException
......@@ -130,7 +131,6 @@ def main():
exit.signal(0, signal.SIGINT, signal.SIGTERM)
exit.signal(-1, signal.SIGHUP, signal.SIGUSR2)
next_renew = cert.maybeRenew(config.registry)
cache = Cache(db_path, config.registry, cert)
network = cert.network
......@@ -249,14 +249,12 @@ def main():
control_socket = os.path.join(config.run, 'babeld.sock')
if config.client_count and not config.client:
tunnel_manager = tunnel.TunnelManager(control_socket,
cache, cert, next_renew, config.openvpn_args, timeout,
cache, cert, config.openvpn_args, timeout,
config.client_count, config.iface_list, address, ip_changed,
remote_gateway, config.disable_proto, config.neighbour)
tunnel_interfaces += tunnel_manager.new_iface_list
write_pipe = tunnel_manager.write_pipe
else:
write_pipe = None
tunnel_manager = tunnel.BaseTunnelManager(cache, cert, next_renew)
tunnel_manager = tunnel.BaseTunnelManager(cache, cert)
cleanup.append(tunnel_manager.sock.close)
try:
......@@ -275,6 +273,7 @@ def main():
# an public IP so Babel must be changed to set a source
# address on routes it installs.
ip('addrlabel', 'prefix', my_network, 'label', '99')
R = {}
# prepare persistent interfaces
if config.client:
address_list = [x for x in utils.parse_address(config.client)
......@@ -288,9 +287,13 @@ def main():
elif server_tunnels:
required('dh')
for iface, (port, proto) in server_tunnels.iteritems():
r, x = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
cleanup.append(plib.server(iface, config.max_clients,
config.dh, write_pipe, port, proto, cache.encrypt,
'--ping-exit', str(timeout), *config.openvpn_args).stop)
config.dh, x.fileno(), port, proto, cache.encrypt,
'--ping-exit', str(timeout), *config.openvpn_args,
preexec_fn=r.close).stop)
R[r] = partial(tunnel_manager.handleServerEvent, r)
x.close()
ip('addr', my_ip, 'dev', config.main_interface)
if_rt = ['ip', '-6', 'route', 'del',
......@@ -371,7 +374,7 @@ def main():
select_list = [forwarder.select] if forwarder else []
select_list += tunnel_manager.select, utils.select
while True:
args = {}, {}, []
args = R.copy(), {}, []
for s in select_list:
s(*args)
finally:
......
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