Each incoming request contains up to max_batch_up bytes of payload.
It's a multipart/form-data.
-Authentication: for now, plaintext secret
+Authentication: clock-based lifetime-limited bearer tokens.
+
+Encryption and integrity checking: none. Use a real VPN over this!
Routing assistance: none in hippotat; can be requested on client
from userv-ipif via `vroutes' parameter. Use with secnet polypath
Client form parameters (multipart/form-data):
m metadata, newline-separated list (text file) of
client ip address (textual)
- password
+ token
target_requests_outstanding
http_timeout
d data (SLIP format, with SLIP_ESC and `-' swapped)
+Authentication token is:
+ <time_t in hex with no leading 0s> <hmac in base64>
+(separated by a single space). The hmac is
+ HMAC(secret, <time_t in hex>)
Possible future nonce-based authentication:
Virtual interface name on the client. [hippo%d]
Any %d is interpolated (by the kernel).
+Ordinary settings, used by server only:
+
+ max_clock_skew
+ Permissible clock skew between client and server.
+ hippotat will not work if clock skew is more than this.
+ Conversely: when moving client from one public network to
+ another, the first network can deny service to the client for
+ this period after the client leaves the first network.
+ [300s]
+
Ordinary settings, used by client only:
http_timeout_grace
d = mime_translate(d)
+ token = authtoken_make(cl.c.secret)
+
crlf = b'\r\n'
lf = b'\n'
mime = (b'--b' + crlf +
b'Content-Type: text/plain; charset="utf-8"' + crlf +
b'Content-Disposition: form-data; name="m"' + crlf + crlf +
str(cl.c.client) .encode('ascii') + crlf +
- cl.c.secret + crlf +
+ token + crlf +
str(cl.c.target_requests_outstanding)
.encode('ascii') + crlf +
str(cl.c.http_timeout) .encode('ascii') + crlf +
# find client, update config, etc.
metadata = request.args[b'm'][0]
metadata = metadata.split(b'\r\n')
- (ci_s, pw, tro, cto) = metadata[0:4]
+ (ci_s, token, tro, cto) = metadata[0:4]
desca['m[0,2:3]'] = [ci_s, tro, cto]
ci_s = ci_s.decode('utf-8')
tro = int(tro); desca['tro']= tro
ci = ipaddr(ci_s)
desca['ci'] = ci
cl = clients[ci]
- if pw != cl.cc.secret: raise ValueError('bad secret')
+ authtoken_check(cl.cc.secret, token, cl.cc.max_clock_skew)
desca['pwok']=True
if tro != cl.cc.target_requests_outstanding:
if not sections: continue
cfg_process_client_limited(cc,c.server,sections, 'max_batch_down')
cfg_process_client_limited(cc,c.server,sections, 'max_queue_time')
+ cc.max_clock_skew = cfg_search(cfg.getint, 'max_clock_skew', sections)
Client(ci, cc)
try:
import collections
import time
+import hmac
+import hashlib
+import base64
import codecs
import traceback
vroutes = ''
ifname_client = hippo%%d
ifname_server = shippo%%d
+max_clock_skew = 300
#[server] or [<client>] overrides
ipif = userv root ipif %(local)s,%(peer)s,%(mtu)s,slip,%(ifname)s %(rnets)s
if event.get('log_level') >= LogLevel.critical:
crash(twisted.logger.formatEvent(event))
+#---------- authentication tokens ----------
+
+_authtoken_digest = hashlib.sha256
+
+def _authtoken_time():
+ return int(time.time())
+
+def _authtoken_hmac(secret, hextime):
+ return hmac.new(secret, hextime, _authtoken_digest).digest()
+
+def authtoken_make(secret):
+ hextime = ('%x' % _authtoken_time()).encode('ascii')
+ mac = _authtoken_hmac(secret, hextime)
+ return hextime + b' ' + base64.b64encode(mac)
+
+def authtoken_check(secret, token, maxskew):
+ (hextime, theirmac64) = token.split(b' ')
+ now = _authtoken_time()
+ then = int(hextime, 16)
+ skew = then - now;
+ if (abs(skew) > maxskew):
+ raise ValueError('too much clock skew (client %ds ahead)' % skew)
+ theirmac = base64.b64decode(theirmac64)
+ ourmac = _authtoken_hmac(secret, hextime)
+ if not hmac.compare_digest(theirmac, ourmac):
+ raise ValueError('invalid token (wrong secret?)')
+ pass
+
#---------- config processing ----------
def _cfg_process_putatives():