Commit 4b51ef36 authored by Killian Lufau's avatar Killian Lufau

Implement HMAC for babel

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 hmac branch of nexedi/babeld, based on
the WIP branch of jech/babeld with Nexedi patches and another patch
adding an option to not check HMAC.
parent 73314e4d
...@@ -95,6 +95,8 @@ class Cache(object): ...@@ -95,6 +95,8 @@ class Cache(object):
config = {} config = {}
for k, v in x.iteritems(): for k, v in x.iteritems():
k = str(k) k = str(k)
if 'babel_hmac' in k and v:
v = self._decrypt(v.decode('base64'))
if k in base64: if k in base64:
v = v.decode('base64') v = v.decode('base64')
elif type(v) is unicode: elif type(v) is unicode:
...@@ -130,7 +132,8 @@ class Cache(object): ...@@ -130,7 +132,8 @@ 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 'babel_hmac' in k
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
......
...@@ -386,6 +386,14 @@ def main(): ...@@ -386,6 +386,14 @@ def main():
subprocess.check_call(x) subprocess.check_call(x)
ip('route', 'unreachable', my_network) 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 config.babel_args += config.iface_list
cleanup.append(plib.router((my_ip, len(subnet)), ipv4, cleanup.append(plib.router((my_ip, len(subnet)), ipv4,
None if config.gateway else None if config.gateway else
...@@ -395,6 +403,12 @@ def main(): ...@@ -395,6 +403,12 @@ 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,
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) *config.babel_args).stop)
if config.up: if config.up:
exit.release() exit.release()
......
...@@ -61,8 +61,8 @@ def client(iface, address_list, encrypt, *args, **kw): ...@@ -61,8 +61,8 @@ 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, hmac0, hmac1, hmac2, *args, **kw):
ip, n = ip ip, n = ip
if ip4: if ip4:
ip4, n4 = ip4 ip4, n4 = ip4
...@@ -79,9 +79,22 @@ def router(ip, ip4, src, hello_interval, log_path, state_path, ...@@ -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 # 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 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: 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:
......
...@@ -84,7 +84,7 @@ class RegistryServer(object): ...@@ -84,7 +84,7 @@ class RegistryServer(object):
utils.sqliteCreateTable(self.db, "crl", utils.sqliteCreateTable(self.db, "crl",
"serial INTEGER PRIMARY KEY NOT NULL", "serial INTEGER PRIMARY KEY NOT NULL",
# Expiration date of revoked certificate. # 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") "date INTEGER NOT NULL")
self.cert = x509.Cert(self.config.ca, self.config.key) self.cert = x509.Cert(self.config.ca, self.config.key)
...@@ -139,7 +139,7 @@ class RegistryServer(object): ...@@ -139,7 +139,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 +483,16 @@ class RegistryServer(object): ...@@ -483,7 +483,16 @@ 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 = {}
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): def _queryAddress(self, peer):
self.sendto(peer, 1) self.sendto(peer, 1)
...@@ -550,6 +559,36 @@ class RegistryServer(object): ...@@ -550,6 +559,36 @@ class RegistryServer(object):
q("INSERT INTO crl VALUES (?,?)", (serial, not_after)) q("INSERT INTO crl VALUES (?,?)", (serial, not_after))
self.updateNetworkConfig() 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 @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_hmac0',
'ipv4', 'ipv4_sublen')) 'babel_hmac1', 'babel_hmac2', 'encrypt',
'hello', 'ipv4', 'ipv4_sublen'))
_geoiplookup = None _geoiplookup = None
_forward = None _forward = None
......
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