mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-12-23 03:31:07 +00:00
use signature keyid instead of object actor to fetch actor
This commit is contained in:
parent
d2b243d88a
commit
d08bd6625a
|
@ -106,20 +106,6 @@ def sign_signing_string(sigstring, key):
|
|||
return base64.b64encode(sigdata).decode('utf-8')
|
||||
|
||||
|
||||
def split_signature(sig):
|
||||
default = {"headers": "date"}
|
||||
|
||||
sig = sig.strip().split(',')
|
||||
|
||||
for chunk in sig:
|
||||
k, _, v = chunk.partition('=')
|
||||
v = v.strip('\"')
|
||||
default[k] = v
|
||||
|
||||
default['headers'] = default['headers'].split()
|
||||
return default
|
||||
|
||||
|
||||
async def fetch_actor_key(actor):
|
||||
actor_data = await request(actor)
|
||||
|
||||
|
@ -250,29 +236,19 @@ async def request(uri, data=None, force=False, sign_headers=True, activity=True)
|
|||
traceback.print_exc()
|
||||
|
||||
|
||||
async def validate_signature(actor, http_request):
|
||||
pubkey = await fetch_actor_key(actor)
|
||||
|
||||
if not pubkey:
|
||||
return False
|
||||
|
||||
logging.debug(f'actor key: {pubkey}')
|
||||
|
||||
async def validate_signature(actor, signature, http_request):
|
||||
headers = {key.lower(): value for key, value in http_request.headers.items()}
|
||||
headers['(request-target)'] = ' '.join([http_request.method.lower(), http_request.path])
|
||||
|
||||
sig = split_signature(headers['signature'])
|
||||
logging.debug(f'sigdata: {sig}')
|
||||
|
||||
sigstring = build_signing_string(headers, sig['headers'])
|
||||
sigstring = build_signing_string(headers, signature['headers'])
|
||||
logging.debug(f'sigstring: {sigstring}')
|
||||
|
||||
sign_alg, _, hash_alg = sig['algorithm'].partition('-')
|
||||
sign_alg, _, hash_alg = signature['algorithm'].partition('-')
|
||||
logging.debug(f'sign alg: {sign_alg}, hash alg: {hash_alg}')
|
||||
|
||||
sigdata = base64.b64decode(sig['signature'])
|
||||
sigdata = base64.b64decode(signature['signature'])
|
||||
|
||||
pkcs = PKCS1_v1_5.new(pubkey)
|
||||
pkcs = PKCS1_v1_5.new(actor.PUBKEY)
|
||||
h = HASHES[hash_alg].new()
|
||||
h.update(sigstring.encode('ascii'))
|
||||
result = pkcs.verify(h, sigdata)
|
||||
|
@ -333,6 +309,22 @@ class DotDict(dict):
|
|||
raise JSONDecodeError('Invalid body', data, 1)
|
||||
|
||||
|
||||
@classmethod
|
||||
def new_from_signature(cls, sig):
|
||||
data = cls({})
|
||||
|
||||
for chunk in sig.strip().split(','):
|
||||
key, value = chunk.split('=', 1)
|
||||
value = value.strip('\"')
|
||||
|
||||
if key == 'headers':
|
||||
value = value.split()
|
||||
|
||||
data[key.lower()] = value
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def to_json(self, indent=None):
|
||||
return json.dumps(self, indent=indent)
|
||||
|
||||
|
@ -435,6 +427,11 @@ class Message(DotDict):
|
|||
|
||||
|
||||
# actor properties
|
||||
@property
|
||||
def PUBKEY(self):
|
||||
return RSA.import_key(self.pubkey)
|
||||
|
||||
|
||||
@property
|
||||
def pubkey(self):
|
||||
return self.publicKey.publicKeyPem
|
||||
|
|
|
@ -6,7 +6,7 @@ from pathlib import Path
|
|||
|
||||
from . import __version__, misc
|
||||
from .http_debug import STATS
|
||||
from .misc import Message, Response, WKNodeinfo
|
||||
from .misc import DotDict, Message, Response, WKNodeinfo
|
||||
from .processors import run_processor
|
||||
|
||||
|
||||
|
@ -78,11 +78,14 @@ async def inbox(request):
|
|||
database = request.app.database
|
||||
|
||||
## reject if missing signature header
|
||||
if 'signature' not in request.headers:
|
||||
try:
|
||||
signature = DotDict.new_from_signature(request.headers['signature'])
|
||||
|
||||
except KeyError:
|
||||
logging.verbose('Actor missing signature header')
|
||||
raise HTTPUnauthorized(body='missing signature')
|
||||
|
||||
## read message and get actor id and domain
|
||||
## read message
|
||||
try:
|
||||
data = await request.json(loads=Message.new_from_json)
|
||||
|
||||
|
@ -96,22 +99,22 @@ async def inbox(request):
|
|||
logging.verbose('Failed to parse inbox message')
|
||||
return Response.new_error(400, 'failed to parse message', 'json')
|
||||
|
||||
software = await misc.fetch_nodeinfo(data.domain)
|
||||
actor = await misc.request(data.actorid)
|
||||
actor = await misc.request(signature.keyid)
|
||||
software = await misc.fetch_nodeinfo(actor.domain)
|
||||
|
||||
## reject if actor is empty
|
||||
if not actor:
|
||||
logging.verbose(f'Failed to fetch actor: {data.actorid}')
|
||||
logging.verbose(f'Failed to fetch actor: {actor.id}')
|
||||
return Response.new_error(400, 'failed to fetch actor', 'json')
|
||||
|
||||
## reject if the actor isn't whitelisted while the whiltelist is enabled
|
||||
elif config.whitelist_enabled and not config.is_whitelisted(data.domain):
|
||||
logging.verbose(f'Rejected actor for not being in the whitelist: {data.actorid}')
|
||||
elif config.whitelist_enabled and not config.is_whitelisted(actor.domain):
|
||||
logging.verbose(f'Rejected actor for not being in the whitelist: {actor.id}')
|
||||
return Response.new_error(403, 'access denied', 'json')
|
||||
|
||||
## reject if actor is banned
|
||||
if request.app['config'].is_banned(data.domain):
|
||||
logging.verbose(f'Ignored request from banned actor: {data.actorid}')
|
||||
if request.app['config'].is_banned(actor.domain):
|
||||
logging.verbose(f'Ignored request from banned actor: {actor.id}')
|
||||
return Response.new_error(403, 'access denied', 'json')
|
||||
|
||||
## reject if software used by actor is banned
|
||||
|
@ -120,13 +123,13 @@ async def inbox(request):
|
|||
return Response.new_error(403, 'access denied', 'json')
|
||||
|
||||
## reject if the signature is invalid
|
||||
if not (await misc.validate_signature(data.actorid, request)):
|
||||
logging.verbose(f'signature validation failed for: {data.actorid}')
|
||||
if not (await misc.validate_signature(actor, signature, request)):
|
||||
logging.verbose(f'signature validation failed for: {actor.id}')
|
||||
return Response.new_error(401, 'signature check failed', 'json')
|
||||
|
||||
## reject if activity type isn't 'Follow' and the actor isn't following
|
||||
if data['type'] != 'Follow' and not database.get_inbox(data.domain):
|
||||
logging.verbose(f'Rejected actor for trying to post while not following: {data.actorid}')
|
||||
if data['type'] != 'Follow' and not database.get_inbox(actor.domain):
|
||||
logging.verbose(f'Rejected actor for trying to post while not following: {actor.id}')
|
||||
return Response.new_error(401, 'access denied', 'json')
|
||||
|
||||
logging.debug(f">> payload {data}")
|
||||
|
|
Loading…
Reference in a new issue