Commit 12ba2ee4 authored by Killian Lufau's avatar Killian Lufau Committed by Julien Muchembled

Implement HMAC for babel

HMAC is added in babel call to prevent babel communication between nodes of different re6st networks. 
This solves the problem of machines in different re6st networks but on the same LAN that exchange routes through babel. 
The key used to authenticate packets is randomly created on 16 bytes by the registry and sent to nodes when they fetch network parameters. 
This uses the WIP hmac branch of jech/babeld with Nexedi patches and the added possibility to not check HMAC in incoming packets for better HMAC integration on a HMAC-less network.

/reviewed-on nexedi/re6stnet!18
parent 63b5c4c2
......@@ -95,7 +95,10 @@ class Cache(object):
config = {}
for k, v in x.iteritems():
k = str(k)
if k in base64:
if k.startswith('babel_hmac'):
if v:
v = self._decrypt(v.decode('base64'))
elif k in base64:
v = v.decode('base64')
elif type(v) is unicode:
v = str(v)
......@@ -130,12 +133,14 @@ 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
k.startswith('babel_hmac') else v)
for k, v in config.iteritems()))
self._loadConfig(config.iteritems())
return [k[:-5] if k.endswith(':json') else k
for k in chain(remove, (k
for k, v in config.iteritems()
if k not in old or old[k] != v]
if k not in old or old[k] != v))]
def warnProtocol(self):
if version.protocol < self.protocol:
......
......@@ -395,6 +395,8 @@ def main():
os.path.join(config.state, 'babeld.state'),
os.path.join(config.run, 'babeld.pid'),
control_socket, cache.babel_default,
tuple(getattr(cache, k, None) for k in
('babel_hmac_sign', 'babel_hmac_accept')),
*config.babel_args).stop)
if config.up:
exit.release()
......
......@@ -61,9 +61,10 @@ 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, hmac, *args, **kw):
ip, n = ip
hmac_sign, hmac_accept = hmac
if ip4:
ip4, n4 = ip4
cmd = ['babeld',
......@@ -79,9 +80,21 @@ 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 hmac_sign:
def key(cmd, id, value):
cmd += '-C', ('key type blake2s id %s value %s' %
(id, value.encode('hex')))
key(cmd, 'sign', hmac_sign)
cmd += '-C', 'default %s hmac sign' % default
if hmac_accept is not None:
if hmac_accept:
key(cmd, 'accept', hmac_accept)
else:
cmd += '-C', 'ignore_no_hmac'
else:
cmd += '-C', 'default ' + default
if ip4:
cmd += '-C', 'redistribute ip %s/%s eq %s' % (ip4, n4, n4)
if src:
......
......@@ -33,6 +33,7 @@ from . import ctl, tunnel, utils, version, x509
HMAC_HEADER = "Re6stHMAC"
RENEW_PERIOD = 30 * 86400
GRACE_PERIOD = 100 * 86400
BABEL_HMAC = 'babel_hmac0', 'babel_hmac1', 'babel_hmac2'
def rpc(f):
args, varargs, varkw, defaults = inspect.getargspec(f)
......@@ -102,6 +103,8 @@ class RegistryServer(object):
if self.prefix:
with self.db:
self.updateNetworkConfig()
else:
self.newHMAC(0)
def getConfig(self, name, *default):
r, = next(self.db.execute(
......@@ -139,7 +142,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 +486,14 @@ class RegistryServer(object):
@rpc
def getNetworkConfig(self, cn):
return self.network_config
with self.lock:
cert = self.getCert(cn)
config = self.network_config.copy()
hmac = [self.getConfig(k, None) for k in BABEL_HMAC]
for i, v in enumerate(v for v in hmac if v is not None):
config[('babel_hmac_sign', 'babel_hmac_accept')[i]] = \
v and x509.encrypt(cert, v).encode('base64')
return zlib.compress(json.dumps(config))
def _queryAddress(self, peer):
self.sendto(peer, 1)
......@@ -550,6 +560,39 @@ class RegistryServer(object):
q("INSERT INTO crl VALUES (?,?)", (serial, not_after))
self.updateNetworkConfig()
def newHMAC(self, i, key=None):
if key is None:
key = buffer(os.urandom(16))
self.setConfig(BABEL_HMAC[i], key)
def delHMAC(self, i):
self.db.execute("DELETE FROM config WHERE name=?", (BABEL_HMAC[i],))
@rpc_private
def updateHMAC(self):
with self.lock:
with self.db:
hmac = [self.getConfig(BABEL_HMAC[i], None) for i in (0,1,2)]
if hmac[0]:
if hmac[1]:
self.newHMAC(2, hmac[0])
self.delHMAC(0)
else:
self.newHMAC(1)
elif hmac[1]:
self.newHMAC(0, hmac[1])
self.delHMAC(1)
self.delHMAC(2)
else:
# Initialization of HMAC on the network
self.newHMAC(1)
self.newHMAC(2, '')
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_hmac_accept',
'babel_hmac_sign', 'encrypt',
'hello', 'ipv4', 'ipv4_sublen'))
_geoiplookup = None
_forward = None
......@@ -537,7 +538,7 @@ class BaseTunnelManager(object):
logging.info("will retry to update network parameters in 5 minutes")
self.selectTimeout(time.time() + 300, self.newVersion)
return
logging.info("changed: %r", changed)
logging.info("changed: %r", sorted(changed))
self.selectTimeout(None, self.newVersion)
self._version = self.cache.version
self.broadcastNewVersion()
......
......@@ -32,7 +32,7 @@ if dirty:
# they are intended to the network admin.
# Only 'protocol' is important and it must be increased whenever they would be
# a wish to force an update of nodes.
protocol = 4
protocol = 5
min_protocol = 1
if __name__ == "__main__":
......
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