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):
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 +132,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
......
......@@ -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)
......@@ -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
......
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