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): ...@@ -95,7 +95,10 @@ class Cache(object):
config = {} config = {}
for k, v in x.iteritems(): for k, v in x.iteritems():
k = str(k) 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') v = v.decode('base64')
elif type(v) is unicode: elif type(v) is unicode:
v = str(v) v = str(v)
...@@ -130,12 +133,14 @@ class Cache(object): ...@@ -130,12 +133,14 @@ class Cache(object):
# BBB: Use buffer because of http://bugs.python.org/issue13676 # BBB: Use buffer because of http://bugs.python.org/issue13676
# on Python 2.6 # on Python 2.6
db.executemany("INSERT OR REPLACE INTO config VALUES(?,?)", 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())) for k, v in config.iteritems()))
self._loadConfig(config.iteritems()) self._loadConfig(config.iteritems())
return [k[:-5] if k.endswith(':json') else k return [k[:-5] if k.endswith(':json') else k
for k, v in config.iteritems() for k in chain(remove, (k
if k not in old or old[k] != v] for k, v in config.iteritems()
if k not in old or old[k] != v))]
def warnProtocol(self): def warnProtocol(self):
if version.protocol < self.protocol: if version.protocol < self.protocol:
......
...@@ -395,6 +395,8 @@ def main(): ...@@ -395,6 +395,8 @@ def main():
os.path.join(config.state, 'babeld.state'), os.path.join(config.state, 'babeld.state'),
os.path.join(config.run, 'babeld.pid'), os.path.join(config.run, 'babeld.pid'),
control_socket, cache.babel_default, control_socket, cache.babel_default,
tuple(getattr(cache, k, None) for k in
('babel_hmac_sign', 'babel_hmac_accept')),
*config.babel_args).stop) *config.babel_args).stop)
if config.up: if config.up:
exit.release() exit.release()
......
...@@ -61,9 +61,10 @@ def client(iface, address_list, encrypt, *args, **kw): ...@@ -61,9 +61,10 @@ def client(iface, address_list, encrypt, *args, **kw):
return openvpn(iface, encrypt, *remote, **kw) return openvpn(iface, encrypt, *remote, **kw)
def router(ip, ip4, src, hello_interval, log_path, state_path, def router(ip, ip4, src, hello_interval, log_path, state_path, pidfile,
pidfile, control_socket, default, *args, **kw): control_socket, default, hmac, *args, **kw):
ip, n = ip ip, n = ip
hmac_sign, hmac_accept = hmac
if ip4: if ip4:
ip4, n4 = ip4 ip4, n4 = ip4
cmd = ['babeld', cmd = ['babeld',
...@@ -79,9 +80,21 @@ def router(ip, ip4, src, hello_interval, log_path, state_path, ...@@ -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 # is not equivalent, at least not the way we use babeld
# (and we don't need RTA_SRC for ipv4). # (and we don't need RTA_SRC for ipv4).
'-C', 'ipv6-subtrees true', '-C', 'ipv6-subtrees true',
'-C', 'default ' + default,
'-C', 'redistribute local deny', '-C', 'redistribute local deny',
'-C', 'redistribute ip %s/%s eq %s' % (ip, n, n)] '-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: if ip4:
cmd += '-C', 'redistribute ip %s/%s eq %s' % (ip4, n4, n4) cmd += '-C', 'redistribute ip %s/%s eq %s' % (ip4, n4, n4)
if src: if src:
......
...@@ -33,6 +33,7 @@ from . import ctl, tunnel, utils, version, x509 ...@@ -33,6 +33,7 @@ from . import ctl, tunnel, utils, version, x509
HMAC_HEADER = "Re6stHMAC" HMAC_HEADER = "Re6stHMAC"
RENEW_PERIOD = 30 * 86400 RENEW_PERIOD = 30 * 86400
GRACE_PERIOD = 100 * 86400 GRACE_PERIOD = 100 * 86400
BABEL_HMAC = 'babel_hmac0', 'babel_hmac1', 'babel_hmac2'
def rpc(f): def rpc(f):
args, varargs, varkw, defaults = inspect.getargspec(f) args, varargs, varkw, defaults = inspect.getargspec(f)
...@@ -102,6 +103,8 @@ class RegistryServer(object): ...@@ -102,6 +103,8 @@ class RegistryServer(object):
if self.prefix: if self.prefix:
with self.db: with self.db:
self.updateNetworkConfig() self.updateNetworkConfig()
else:
self.newHMAC(0)
def getConfig(self, name, *default): def getConfig(self, name, *default):
r, = next(self.db.execute( r, = next(self.db.execute(
...@@ -139,7 +142,7 @@ class RegistryServer(object): ...@@ -139,7 +142,7 @@ class RegistryServer(object):
# The following entry lists values that are base64-encoded. # The following entry lists values that are base64-encoded.
kw[''] = 'version', kw[''] = 'version',
kw['version'] = self.version.encode('base64') 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. # The 3 first bits code the number of bytes.
def encodeVersion(self, version): def encodeVersion(self, version):
...@@ -483,7 +486,14 @@ class RegistryServer(object): ...@@ -483,7 +486,14 @@ class RegistryServer(object):
@rpc @rpc
def getNetworkConfig(self, cn): 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): def _queryAddress(self, peer):
self.sendto(peer, 1) self.sendto(peer, 1)
...@@ -550,6 +560,39 @@ class RegistryServer(object): ...@@ -550,6 +560,39 @@ class RegistryServer(object):
q("INSERT INTO crl VALUES (?,?)", (serial, not_after)) q("INSERT INTO crl VALUES (?,?)", (serial, not_after))
self.updateNetworkConfig() 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 @rpc_private
def getNodePrefix(self, email): def getNodePrefix(self, email):
with self.lock: with self.lock:
......
...@@ -191,8 +191,9 @@ class BaseTunnelManager(object): ...@@ -191,8 +191,9 @@ class BaseTunnelManager(object):
# TODO: To minimize downtime when network parameters change, we should do # TODO: To minimize downtime when network parameters change, we should do
# our best to not restart any process. Ideally, this list should be # our best to not restart any process. Ideally, this list should be
# empty and the affected subprocesses reloaded. # empty and the affected subprocesses reloaded.
NEED_RESTART = frozenset(('babel_default', 'encrypt', 'hello', NEED_RESTART = frozenset(('babel_default', 'babel_hmac_accept',
'ipv4', 'ipv4_sublen')) 'babel_hmac_sign', 'encrypt',
'hello', 'ipv4', 'ipv4_sublen'))
_geoiplookup = None _geoiplookup = None
_forward = None _forward = None
...@@ -537,7 +538,7 @@ class BaseTunnelManager(object): ...@@ -537,7 +538,7 @@ class BaseTunnelManager(object):
logging.info("will retry to update network parameters in 5 minutes") logging.info("will retry to update network parameters in 5 minutes")
self.selectTimeout(time.time() + 300, self.newVersion) self.selectTimeout(time.time() + 300, self.newVersion)
return return
logging.info("changed: %r", changed) logging.info("changed: %r", sorted(changed))
self.selectTimeout(None, self.newVersion) self.selectTimeout(None, self.newVersion)
self._version = self.cache.version self._version = self.cache.version
self.broadcastNewVersion() self.broadcastNewVersion()
......
...@@ -32,7 +32,7 @@ if dirty: ...@@ -32,7 +32,7 @@ if dirty:
# they are intended to the network admin. # they are intended to the network admin.
# Only 'protocol' is important and it must be increased whenever they would be # Only 'protocol' is important and it must be increased whenever they would be
# a wish to force an update of nodes. # a wish to force an update of nodes.
protocol = 4 protocol = 5
min_protocol = 1 min_protocol = 1
if __name__ == "__main__": 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