mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-11-10 02:17:59 +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')
|
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):
|
async def fetch_actor_key(actor):
|
||||||
actor_data = await request(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()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
async def validate_signature(actor, http_request):
|
async def validate_signature(actor, signature, http_request):
|
||||||
pubkey = await fetch_actor_key(actor)
|
|
||||||
|
|
||||||
if not pubkey:
|
|
||||||
return False
|
|
||||||
|
|
||||||
logging.debug(f'actor key: {pubkey}')
|
|
||||||
|
|
||||||
headers = {key.lower(): value for key, value in http_request.headers.items()}
|
headers = {key.lower(): value for key, value in http_request.headers.items()}
|
||||||
headers['(request-target)'] = ' '.join([http_request.method.lower(), http_request.path])
|
headers['(request-target)'] = ' '.join([http_request.method.lower(), http_request.path])
|
||||||
|
|
||||||
sig = split_signature(headers['signature'])
|
sigstring = build_signing_string(headers, signature['headers'])
|
||||||
logging.debug(f'sigdata: {sig}')
|
|
||||||
|
|
||||||
sigstring = build_signing_string(headers, sig['headers'])
|
|
||||||
logging.debug(f'sigstring: {sigstring}')
|
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}')
|
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 = HASHES[hash_alg].new()
|
||||||
h.update(sigstring.encode('ascii'))
|
h.update(sigstring.encode('ascii'))
|
||||||
result = pkcs.verify(h, sigdata)
|
result = pkcs.verify(h, sigdata)
|
||||||
|
@ -333,6 +309,22 @@ class DotDict(dict):
|
||||||
raise JSONDecodeError('Invalid body', data, 1)
|
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):
|
def to_json(self, indent=None):
|
||||||
return json.dumps(self, indent=indent)
|
return json.dumps(self, indent=indent)
|
||||||
|
|
||||||
|
@ -435,6 +427,11 @@ class Message(DotDict):
|
||||||
|
|
||||||
|
|
||||||
# actor properties
|
# actor properties
|
||||||
|
@property
|
||||||
|
def PUBKEY(self):
|
||||||
|
return RSA.import_key(self.pubkey)
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pubkey(self):
|
def pubkey(self):
|
||||||
return self.publicKey.publicKeyPem
|
return self.publicKey.publicKeyPem
|
||||||
|
|
|
@ -6,7 +6,7 @@ from pathlib import Path
|
||||||
|
|
||||||
from . import __version__, misc
|
from . import __version__, misc
|
||||||
from .http_debug import STATS
|
from .http_debug import STATS
|
||||||
from .misc import Message, Response, WKNodeinfo
|
from .misc import DotDict, Message, Response, WKNodeinfo
|
||||||
from .processors import run_processor
|
from .processors import run_processor
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,11 +78,14 @@ async def inbox(request):
|
||||||
database = request.app.database
|
database = request.app.database
|
||||||
|
|
||||||
## reject if missing signature header
|
## 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')
|
logging.verbose('Actor missing signature header')
|
||||||
raise HTTPUnauthorized(body='missing signature')
|
raise HTTPUnauthorized(body='missing signature')
|
||||||
|
|
||||||
## read message and get actor id and domain
|
## read message
|
||||||
try:
|
try:
|
||||||
data = await request.json(loads=Message.new_from_json)
|
data = await request.json(loads=Message.new_from_json)
|
||||||
|
|
||||||
|
@ -96,22 +99,22 @@ async def inbox(request):
|
||||||
logging.verbose('Failed to parse inbox message')
|
logging.verbose('Failed to parse inbox message')
|
||||||
return Response.new_error(400, 'failed to parse message', 'json')
|
return Response.new_error(400, 'failed to parse message', 'json')
|
||||||
|
|
||||||
software = await misc.fetch_nodeinfo(data.domain)
|
actor = await misc.request(signature.keyid)
|
||||||
actor = await misc.request(data.actorid)
|
software = await misc.fetch_nodeinfo(actor.domain)
|
||||||
|
|
||||||
## reject if actor is empty
|
## reject if actor is empty
|
||||||
if not actor:
|
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')
|
return Response.new_error(400, 'failed to fetch actor', 'json')
|
||||||
|
|
||||||
## reject if the actor isn't whitelisted while the whiltelist is enabled
|
## reject if the actor isn't whitelisted while the whiltelist is enabled
|
||||||
elif config.whitelist_enabled and not config.is_whitelisted(data.domain):
|
elif config.whitelist_enabled and not config.is_whitelisted(actor.domain):
|
||||||
logging.verbose(f'Rejected actor for not being in the whitelist: {data.actorid}')
|
logging.verbose(f'Rejected actor for not being in the whitelist: {actor.id}')
|
||||||
return Response.new_error(403, 'access denied', 'json')
|
return Response.new_error(403, 'access denied', 'json')
|
||||||
|
|
||||||
## reject if actor is banned
|
## reject if actor is banned
|
||||||
if request.app['config'].is_banned(data.domain):
|
if request.app['config'].is_banned(actor.domain):
|
||||||
logging.verbose(f'Ignored request from banned actor: {data.actorid}')
|
logging.verbose(f'Ignored request from banned actor: {actor.id}')
|
||||||
return Response.new_error(403, 'access denied', 'json')
|
return Response.new_error(403, 'access denied', 'json')
|
||||||
|
|
||||||
## reject if software used by actor is banned
|
## reject if software used by actor is banned
|
||||||
|
@ -120,13 +123,13 @@ async def inbox(request):
|
||||||
return Response.new_error(403, 'access denied', 'json')
|
return Response.new_error(403, 'access denied', 'json')
|
||||||
|
|
||||||
## reject if the signature is invalid
|
## reject if the signature is invalid
|
||||||
if not (await misc.validate_signature(data.actorid, request)):
|
if not (await misc.validate_signature(actor, signature, request)):
|
||||||
logging.verbose(f'signature validation failed for: {data.actorid}')
|
logging.verbose(f'signature validation failed for: {actor.id}')
|
||||||
return Response.new_error(401, 'signature check failed', 'json')
|
return Response.new_error(401, 'signature check failed', 'json')
|
||||||
|
|
||||||
## reject if activity type isn't 'Follow' and the actor isn't following
|
## reject if activity type isn't 'Follow' and the actor isn't following
|
||||||
if data['type'] != 'Follow' and not database.get_inbox(data.domain):
|
if data['type'] != 'Follow' and not database.get_inbox(actor.domain):
|
||||||
logging.verbose(f'Rejected actor for trying to post while not following: {data.actorid}')
|
logging.verbose(f'Rejected actor for trying to post while not following: {actor.id}')
|
||||||
return Response.new_error(401, 'access denied', 'json')
|
return Response.new_error(401, 'access denied', 'json')
|
||||||
|
|
||||||
logging.debug(f">> payload {data}")
|
logging.debug(f">> payload {data}")
|
||||||
|
|
Loading…
Reference in a new issue