From 7560362d070602f4a261d300c9a8f24f93476cca Mon Sep 17 00:00:00 2001 From: girst Date: Fri, 19 Jun 2020 18:50:58 +0200 Subject: [PATCH] implement websub hmac verification (not yet enforced) only enforcing this once all current websub-subscriptions have expired --- app/common/common.py | 10 ++++++++++ app/common/utils.py | 12 ++++++++---- app/webhooks.py | 12 +++++++++++- config/config.ini | 3 ++- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/common/common.py b/app/common/common.py index f936706..f52b97d 100644 --- a/app/common/common.py +++ b/app/common/common.py @@ -2,7 +2,9 @@ import os import re import json import html +import base64 import requests +import hmac, hashlib import requests_cache import dateutil.parser from xml.etree import ElementTree @@ -480,6 +482,14 @@ def fallback_route(*args, **kwargs): # TODO: worthy as a flask-extension? else: raise NoFallbackException +def websub_url_hmac(key, feed_id, timestamp, nonce): + """ generate sha1 hmac, as required by websub/pubsubhubbub """ + sig_input = f"{feed_id}:{timestamp}:{nonce}".encode('ascii') + return hmac.new(key.encode('ascii'), sig_input, hashlib.sha1).hexdigest() + +def websub_body_hmac(key, body): + return hmac.new(key.encode('ascii'), body, hashlib.sha1).hexdigest() + def pp(*args): from pprint import pprint import sys, codecs diff --git a/app/common/utils.py b/app/common/utils.py index 11dc787..2d041e0 100755 --- a/app/common/utils.py +++ b/app/common/utils.py @@ -6,6 +6,7 @@ exec python "$0" "$@" import sys import time +import secrets import sqlite3 import requests @@ -76,7 +77,7 @@ def pull_feed(feed_id, feed_type, conn, verbose): return True -def update_subscriptions(verbose=1, limit=-1): +def update_subscriptions(verbose=1, force_all=False, limit=-1): """ Refreshes the websub (pubsubhubhub) subscription requests for youtube feeds. verbose: 0: completely silent; 1: warn on errors; 2: log all accessed feeds @@ -88,9 +89,10 @@ def update_subscriptions(verbose=1, limit=-1): SELECT DISTINCT s.channel_id, type FROM subscriptions AS s LEFT JOIN websub AS w ON s.channel_id = w.channel_id - WHERE IFNULL(subscribed_until,0) < datetime('now', '+12 hours') + WHERE ? OR IFNULL(subscribed_until,0) < datetime('now','+12 hours') ORDER BY subscribed_until - """) + LIMIT ? + """, (force_all,limit)) results = c.fetchall() if verbose >= 2 and not len(results): @@ -109,7 +111,9 @@ def update_feed(feed_id, feed_type, verbose): sys.stderr.write(f'updating {feed_id}\n') feed_type = feed_param[feed_type] - version, timestamp, nonce, sig = "v1", int(time.time()), 0, "x" # TODO:sig,nonce + version, timestamp = "v1", int(time.time()) + nonce = secrets.token_urlsafe(16) + sig = websub_url_hmac(hmackey, feed_id, timestamp, nonce) r = requests.post("https://pubsubhubbub.appspot.com/subscribe", { "hub.callback": f"{webhook}/websub/{version}/{timestamp}/" + \ f"{nonce}/{feed_id}/{sig}", diff --git a/app/webhooks.py b/app/webhooks.py index 05fa294..c4bdb8f 100755 --- a/app/webhooks.py +++ b/app/webhooks.py @@ -46,12 +46,22 @@ def websub(timestamp, nonce, subject, sig): def websub_post(timestamp, nonce, subject, sig): # TODO: implement hmac check: return '',400 unless hmac_sha1_hex("$timestamp/$nonce", $HMACKEY) eq $sig # todo: # say 400 and exit unless hmac_sha1_hex($body, $HMACKEY) eq $ENV{X-Hub-Signature } + lease = cf['websub']['lease'] + hmackey = cf['websub']['hmac_key'] + + if sig != websub_url_hmac(hmackey, subject, timestamp, nonce): + app.logger.warning("url hmac failed (request was not authorized by us)") + # TODO: return '',400 + if request.headers.get('X-Hub-Signature').replace("sha1=","") != websub_body_hmac(hmackey, request.data): + app.logger.warning("body hmac failed (request was not authenticated from websub hub)") + # TODO: return '',400 with sqlite3.connect(cf['global']['database']) as conn: c = conn.cursor() try: update_channel(conn, request.data) - except: + except Exception as e: + app.logger.error(e) with open('/tmp/websub-subscriptions.err', 'ab') as f: #data = request.data.decode("utf-8", errors="ignore") f.write(f"\n".encode('ascii')) diff --git a/config/config.ini b/config/config.ini index 8016ea0..8867b5c 100644 --- a/config/config.ini +++ b/config/config.ini @@ -15,5 +15,6 @@ modules = invidious,youtube,reddit lease = 432000 # public url of our webhook server without "/websub/v1/"...: public_uri = http://delta.gir.st:8801 -# secret string to sign websub responses and urls (not yet used, but may not be empty): +# secret (7-bit ASCII) string to sign websub responses and urls (but may not be empty): +# `tr -dc '[:print:]'