...
 
Commits (2)
  • Killian Lufau's avatar
    Implement HMAC for babel · 0f77428d
    Killian Lufau authored
    HMAC is added to babel to make sure nodes from a given re6st network
    don't talk to nodes from another re6st network. This is useful when
    machines from separate re6st networks are on a LAN.
    The key is the same for all nodes with the same registry: a random
    part created by their registry and passed through network parameters,
    combined with the prefix and prefix length of this re6st network.
    
    This uses the WIP hmac branch of jech/babeld with Nexedi patches
    and the added possibility to not check HMAC for better HMAC integration.
    0f77428d
  • Killian Lufau's avatar
    WIP: Fix startup of nodes for HMAC · c119a92f
    Killian Lufau authored
    c119a92f
......@@ -8,6 +8,7 @@ class Cache(object):
def __init__(self, db_path, registry, cert, db_size=200):
self._prefix = cert.prefix
self._db_size = db_size
self._crypt_size = len(cert)
self._decrypt = cert.decrypt
self._registry = RegistryClient(registry, cert)
......@@ -95,6 +96,8 @@ class Cache(object):
config = {}
for k, v in x.iteritems():
k = str(k)
if 'babel_hmac' in k and v:
v = self._decrypt(v.decode('base64'))
if k in base64:
v = v.decode('base64')
elif type(v) is unicode:
......@@ -130,7 +133,8 @@ class Cache(object):
# BBB: Use buffer because of http://bugs.python.org/issue13676
# on Python 2.6
db.executemany("INSERT OR REPLACE INTO config VALUES(?,?)",
((k, buffer(v) if k in base64 else v)
((k, buffer(v) if k in base64 or 'babel_hmac' in k
else v)
for k, v in config.iteritems()))
self._loadConfig(config.iteritems())
return [k[:-5] if k.endswith(':json') else k
......@@ -234,14 +238,18 @@ class Cache(object):
logging.info('Getting Boot peer...')
try:
bootpeer = self._registry.getBootstrapPeer(self._prefix)
prefix, address = self._decrypt(bootpeer).split()
n = self._crypt_size
prefix, address = self._decrypt(bootpeer[:n]).split()
except (socket.error, subprocess.CalledProcessError, ValueError), e:
logging.warning('Failed to bootstrap (%s)',
e if bootpeer else 'no peer returned')
else:
version = bootpeer[n:]
if self.version == version:
version = None
if prefix != self._prefix:
self.addPeer(prefix, address)
return prefix, address
return prefix, address, version
logging.warning('Buggy registry sent us our own address')
def addPeer(self, prefix, address, set_preferred=False):
......
......@@ -386,6 +386,14 @@ def main():
subprocess.check_call(x)
ip('route', 'unreachable', my_network)
def hmacFromKey(key):
# We combine a key generated by the registry
# and our network prefix to get a hmac key for babel
if key is '':
return key
return '%064x' % int(bin(len(network))[2:].zfill(7) + network +
bin(int(key.encode('hex'),16))[9+len(network):],2)
config.babel_args += config.iface_list
cleanup.append(plib.router((my_ip, len(subnet)), ipv4,
None if config.gateway else
......@@ -395,6 +403,12 @@ def main():
os.path.join(config.state, 'babeld.state'),
os.path.join(config.run, 'babeld.pid'),
control_socket, cache.babel_default,
hmacFromKey(cache.babel_hmac0) if hasattr(cache, 'babel_hmac0'
) else None,
hmacFromKey(cache.babel_hmac1) if hasattr(cache, 'babel_hmac1'
) else None,
hmacFromKey(cache.babel_hmac2) if hasattr(cache, 'babel_hmac2'
) else None,
*config.babel_args).stop)
if config.up:
exit.release()
......
......@@ -61,8 +61,8 @@ def client(iface, address_list, encrypt, *args, **kw):
return openvpn(iface, encrypt, *remote, **kw)
def router(ip, ip4, src, hello_interval, log_path, state_path,
pidfile, control_socket, default, *args, **kw):
def router(ip, ip4, src, hello_interval, log_path, state_path, pidfile,
control_socket, default, hmac0, hmac1, hmac2, *args, **kw):
ip, n = ip
if ip4:
ip4, n4 = ip4
......@@ -79,9 +79,22 @@ def router(ip, ip4, src, hello_interval, log_path, state_path,
# is not equivalent, at least not the way we use babeld
# (and we don't need RTA_SRC for ipv4).
'-C', 'ipv6-subtrees true',
'-C', 'default ' + default,
'-C', 'redistribute local deny',
'-C', 'redistribute ip %s/%s eq %s' % (ip, n, n)]
if hmac0:
cmd += '-C', 'key type sha256 id %s value %s' % ('hmac0', hmac0)
cmd += '-C', 'default %s hmac %s' % (default, 'hmac0')
if hmac1:
cmd += '-C', 'key type sha256 id %s value %s' % ('hmac1', hmac1)
elif hmac1 and hmac2 is not None:
cmd += '-C', 'key type sha256 id %s value %s' % ('hmac1', hmac1)
cmd += '-C', 'default %s hmac %s' % (default, 'hmac1')
if hmac2 is '':
cmd += '-C', 'ignore_no_hmac'
else:
cmd += '-C', 'key type sha256 id %s value %s' % ('hmac2', hmac2)
else:
cmd += '-C', 'default ' + default
if ip4:
cmd += '-C', 'redistribute ip %s/%s eq %s' % (ip4, n4, n4)
if src:
......
......@@ -84,7 +84,7 @@ class RegistryServer(object):
utils.sqliteCreateTable(self.db, "crl",
"serial INTEGER PRIMARY KEY NOT NULL",
# Expiration date of revoked certificate.
# TODO: purge rows with dates in the past.
# TODO: purge rows with dates in the past, add hmac random.
"date INTEGER NOT NULL")
self.cert = x509.Cert(self.config.ca, self.config.key)
......@@ -139,7 +139,7 @@ class RegistryServer(object):
# The following entry lists values that are base64-encoded.
kw[''] = 'version',
kw['version'] = self.version.encode('base64')
self.network_config = zlib.compress(json.dumps(kw), 9)
self.network_config = kw
# The 3 first bits code the number of bytes.
def encodeVersion(self, version):
......@@ -483,7 +483,16 @@ class RegistryServer(object):
@rpc
def getNetworkConfig(self, cn):
return self.network_config
with self.lock:
cert = self.getCert(cn)
config = {}
for k in 'babel_hmac0', 'babel_hmac1', 'babel_hmac2':
v = self.getConfig(k, None)
if v is not None:
config[k] = '' if v is '' else x509.encrypt(
cert, v).encode('base64')
config.update(self.network_config)
return zlib.compress(json.dumps(config), 9)
def _queryAddress(self, peer):
self.sendto(peer, 1)
......@@ -525,7 +534,7 @@ class RegistryServer(object):
cert = self.getCert(cn)
msg = "%s %s" % (peer, msg)
logging.info("Sending bootstrap peer: %s", msg)
return x509.encrypt(cert, msg)
return x509.encrypt(cert, msg) + self.version
@rpc_private
def revoke(self, cn_or_serial):
......@@ -550,6 +559,36 @@ class RegistryServer(object):
q("INSERT INTO crl VALUES (?,?)", (serial, not_after))
self.updateNetworkConfig()
@rpc_private
def updateHMAC(self):
with self.lock:
with self.db:
hmac0 = self.getConfig('babel_hmac0', None)
hmac1 = self.getConfig('babel_hmac1', None)
hmac2 = self.getConfig('babel_hmac2', None)
if hmac0:
if hmac1:
self.setConfig('babel_hmac2', hmac0)
self.db.execute("DELETE FROM config WHERE name=?",
('babel_hmac0',))
else:
self.setConfig('babel_hmac1', buffer(os.urandom(32)))
elif hmac1 and hmac2 is not None:
self.setConfig('babel_hmac0', hmac1)
self.db.execute("DELETE FROM config WHERE name=?",
('babel_hmac1',))
self.db.execute("DELETE FROM config WHERE name=?",
('babel_hmac2',))
elif not (hmac0 or hmac1 or hmac2):
# Initialization of HMAC on the network
self.setConfig('babel_hmac1', buffer(os.urandom(32)))
self.setConfig('babel_hmac2', '')
self.version = self.encodeVersion(
1 + self.decodeVersion(self.version))
self.setConfig('version', buffer(self.version))
self.network_config['version'] = self.version.encode('base64')
self.sendto(self.prefix, 0)
@rpc_private
def getNodePrefix(self, email):
with self.lock:
......
......@@ -191,8 +191,9 @@ class BaseTunnelManager(object):
# TODO: To minimize downtime when network parameters change, we should do
# our best to not restart any process. Ideally, this list should be
# empty and the affected subprocesses reloaded.
NEED_RESTART = frozenset(('babel_default', 'encrypt', 'hello',
'ipv4', 'ipv4_sublen'))
NEED_RESTART = frozenset(('babel_default', 'babel_hmac0',
'babel_hmac1', 'babel_hmac2', 'encrypt',
'hello', 'ipv4', 'ipv4_sublen'))
_geoiplookup = None
_forward = None
......@@ -301,8 +302,9 @@ class BaseTunnelManager(object):
logging.debug("timeout: updating %r (%s)", callback.__name__, next)
t[i] = next, callback
return
logging.debug("timeout: adding %r (%s)", callback.__name__, next)
t.append((next, callback))
if next:
logging.debug("timeout: adding %r (%s)", callback.__name__, next)
t.append((next, callback))
def invalidatePeers(self):
next = float('inf')
......@@ -537,6 +539,7 @@ class BaseTunnelManager(object):
logging.info("will retry to update network parameters in 5 minutes")
self.selectTimeout(time.time() + 300, self.newVersion)
return
assert changed
logging.info("changed: %r", changed)
self.selectTimeout(None, self.newVersion)
self._version = self.cache.version
......@@ -888,11 +891,11 @@ class TunnelManager(BaseTunnelManager):
if route_dumped:
logging.debug('Analyze routes ...')
neighbours = self.ctl.neighbours
# Collect all nodes known by Babel
# Collect all reachable nodes known by Babel
peers = set(prefix
for neigh_routes in neighbours.itervalues()
for prefix in neigh_routes[1]
if prefix)
for prefix, route in neigh_routes[1].iteritems()
if prefix and route.metric < 0xffff)
# Keep only distant peers.
distant_peers[:] = peers.difference(neighbours)
distant_peers.sort(key=self._newTunnelScore)
......@@ -903,9 +906,7 @@ class TunnelManager(BaseTunnelManager):
# Faster recovery of registry node: use cache instead
# of waiting that another node tries to connect to it.
distant_peers = None
elif (registry in peers or
registry in self._connection_dict or
registry in self._served):
elif registry in peers:
self._disconnected = 0
# Be ready to receive any message from the registry.
self.sendto(registry, None)
......@@ -920,7 +921,7 @@ class TunnelManager(BaseTunnelManager):
if peers:
# We aren't the only disconnected node
# so force rebootstrapping.
peer = self.cache.getBootstrapPeer()
peer = self.getBootstrapPeer()
if not peer:
# Registry dead ? Assume we're connected after all.
distant_peers = self._distant_peers
......@@ -965,7 +966,7 @@ class TunnelManager(BaseTunnelManager):
if not (new or peers):
if bootstrap and registry != self._prefix:
# Startup without any good address in the cache.
peer = self.cache.getBootstrapPeer()
peer = self.getBootstrapPeer()
if peer and self._makeTunnel(*peer):
return
# Failed to bootstrap ! Last chance to connect is to
......@@ -974,6 +975,13 @@ class TunnelManager(BaseTunnelManager):
if self._makeTunnel(*peer):
break
def getBootstrapPeer(self):
peer, prefix, version = self.cache.getBootstrapPeer()
if version:
self._version = version
self.newVersion()
return peer, prefix
def killAll(self):
for prefix in self._connection_dict.keys():
self._kill(prefix)
......
......@@ -94,6 +94,9 @@ class Cert(object):
with open(cert) as f:
self.cert = self.loadVerify(f.read())
def __len__(self):
return self.key.bits() // 8
@property
def prefix(self):
return utils.binFromSubnet(subnetFromCert(self.cert))
......