From c88bdf1cd99446968a1a9dd8fb305e531c7b609b Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Mar 2021 09:50:08 +0000 Subject: [PATCH 1/6] Tidying detection of encrypted messages --- notifications_client.py | 11 ++++++----- pgp.py | 35 +++++++++++++++++++++++++---------- tests.py | 3 +++ 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/notifications_client.py b/notifications_client.py index 7359764dc..a045ed1b1 100644 --- a/notifications_client.py +++ b/notifications_client.py @@ -29,6 +29,7 @@ from announce import sendAnnounceViaServer from pgp import pgpDecrypt from pgp import hasLocalPGPkey from pgp import pgpEncryptToActor +from pgp import isPGPEncrypted def _waitForKeypress(timeout: int, debug: bool) -> str: @@ -518,16 +519,16 @@ def runNotificationsClient(baseDir: str, proxyType: str, httpPrefix: str, else: messageStr = speakerJson['say'] + '. ' + \ speakerJson['imageDescription'] - if speakerJson.get('id'): + encryptedMessage = False + if speakerJson.get('id') and \ + isPGPEncrypted(messageStr): + encryptedMessage = True messageStr = pgpDecrypt(messageStr, speakerJson['id']) content = messageStr if speakerJson.get('content'): - if speakerJson.get('id'): - content = pgpDecrypt(speakerJson['content'], - speakerJson['id']) - else: + if not encryptedMessage: content = speakerJson['content'] # say the speaker's name diff --git a/pgp.py b/pgp.py index 1720f75bf..c5c65209d 100644 --- a/pgp.py +++ b/pgp.py @@ -52,7 +52,7 @@ def getPGPpubKey(actorJson: {}) -> str: continue if propertyValue['type'] != 'PropertyValue': continue - if '--BEGIN PGP PUBLIC KEY' not in propertyValue['value']: + if not containsPGPPublicKey(propertyValue['value']): continue return propertyValue['value'] return '' @@ -139,7 +139,7 @@ def setPGPpubKey(actorJson: {}, PGPpubKey: str) -> None: if not PGPpubKey: removeKey = True else: - if '--BEGIN PGP PUBLIC KEY' not in PGPpubKey: + if not containsPGPPublicKey(PGPpubKey): removeKey = True if '<' in PGPpubKey: removeKey = True @@ -318,7 +318,7 @@ def _pgpEncrypt(content: str, recipientPubKey: str) -> str: if not encryptResult: return None encryptResult = encryptResult.decode('utf-8') - if '--BEGIN PGP MESSAGE--' not in encryptResult: + if not isPGPEncrypted(encryptResult): return None return encryptResult @@ -343,9 +343,8 @@ def _getPGPPublicKeyFromActor(handle: str, actorJson=None) -> str: continue if not isinstance(tag['value'], str): continue - if '--BEGIN PGP PUBLIC KEY BLOCK--' in tag['value']: - if '--END PGP PUBLIC KEY BLOCK--' in tag['value']: - return tag['value'] + if containsPGPPublicKey(tag['value']): + return tag['value'] return None @@ -370,17 +369,33 @@ def pgpEncryptToActor(content: str, toHandle: str) -> str: return _pgpEncrypt(content, recipientPubKey) +def isPGPEncrypted(content: str) -> bool: + """Returns true if the given content is PGP encrypted + """ + if '--BEGIN PGP MESSAGE--' in content: + if '--END PGP MESSAGE--' in content: + return True + return False + + +def containsPGPPublicKey(content: str) -> bool: + """Returns true if the given content contains a PGP public key + """ + if '--BEGIN PGP PUBLIC KEY BLOCK--' in content: + if '--END PGP PUBLIC KEY BLOCK--' in content: + return True + return False + + def pgpDecrypt(content: str, fromHandle: str) -> str: """ Encrypt using your default pgp key to the given recipient fromHandle can be a handle or actor url """ - if '--BEGIN PGP MESSAGE--' not in content: + if not isPGPEncrypted(content): return content # if the public key is also included within the message then import it - startBlock = '--BEGIN PGP PUBLIC KEY BLOCK--' - endBlock = '--END PGP PUBLIC KEY BLOCK--' - if startBlock in content and endBlock in content: + if containsPGPPublicKey(content): pubKey = extractPGPPublicKey(content) else: pubKey = _getPGPPublicKeyFromActor(content, fromHandle) diff --git a/tests.py b/tests.py index a2adea0ea..00dc92605 100644 --- a/tests.py +++ b/tests.py @@ -103,6 +103,7 @@ from webapp_post import prepareHtmlPostNickname from webapp_utils import markdownToHtml from speaker import speakerReplaceLinks from pgp import extractPGPPublicKey +from pgp import containsPGPPublicKey testServerAliceRunning = False testServerBobRunning = False @@ -3439,6 +3440,8 @@ def testExtractPGPPublicKey(): '=gv5G\n' + \ '-----END PGP PUBLIC KEY BLOCK-----' testStr = "Some introduction\n\n" + pubKey + "\n\nSome message." + assert containsPGPPublicKey(testStr) + assert not containsPGPPublicKey('String without a pgp key') result = extractPGPPublicKey(testStr) assert result assert result == pubKey From b920aac6ab9bfebc700023428a2f97bd690fe9d2 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Mar 2021 10:06:24 +0000 Subject: [PATCH 2/6] Store decrypted messages --- notifications_client.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/notifications_client.py b/notifications_client.py index a045ed1b1..b27fb0b21 100644 --- a/notifications_client.py +++ b/notifications_client.py @@ -11,7 +11,9 @@ import html import time import sys import select +from pathlib import Path from random import randint +from utils import saveJson from utils import getNicknameFromActor from utils import getDomainFromActor from utils import getFullDomain @@ -370,6 +372,23 @@ def _notificationNewDM(session, toHandle: str, _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak) +def _storeMessage(speakerJson: {}) -> None: + """Stores a message for later reading + """ + if not speakerJson.get('published'): + return + homeDir = str(Path.home()) + if not os.path.isdir(homeDir + '/.config'): + os.mkdir(homeDir + '/.config') + if not os.path.isdir(homeDir + '/.config/epicyon'): + os.mkdir(homeDir + '/.config/epicyon') + msgDir = homeDir + '/.config/epicyon/dm' + if not os.path.isdir(msgDir): + os.mkdir(msgDir) + msgFilename = msgDir + '/' + speakerJson['published'] + '.json' + saveJson(speakerJson, msgFilename) + + def runNotificationsClient(baseDir: str, proxyType: str, httpPrefix: str, nickname: str, domain: str, port: int, password: str, screenreader: str, @@ -543,6 +562,12 @@ def runNotificationsClient(baseDir: str, proxyType: str, httpPrefix: str, systemLanguage, espeak, nameStr, gender) + if encryptedMessage: + speakerJson['content'] = content + speakerJson['say'] = messageStr + speakerJson['decrypted'] = True + _storeMessage(speakerJson) + print('') prevSay = speakerJson['say'] From 3b19512e4db81b5612ba1f070546682d1665ccc3 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Mar 2021 10:14:58 +0000 Subject: [PATCH 3/6] Deny using actor rather than handle --- daemon.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/daemon.py b/daemon.py index d007ec902..394fe009e 100644 --- a/daemon.py +++ b/daemon.py @@ -6437,6 +6437,11 @@ class PubServer(BaseHTTPRequestHandler): originPathStr = path.split('/followdeny=')[0] followerNickname = originPathStr.replace('/users/', '') followingHandle = path.split('/followdeny=')[1] + if '://' in followingHandle: + handleNickname = getNicknameFromActor(followingHandle) + handleDomain, handlePort = getDomainFromActor(followingHandle) + followingHandle = \ + handleNickname + '@' + getFullDomain(handleDomain, handlePort) if '@' in followingHandle: manualDenyFollowRequest(self.server.session, baseDir, httpPrefix, From a0b714cf3361d094497ea3db2f4c293b5660cab2 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Mar 2021 10:16:43 +0000 Subject: [PATCH 4/6] Follow requests using actor rather than handle --- daemon.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/daemon.py b/daemon.py index 394fe009e..7f16a4f7b 100644 --- a/daemon.py +++ b/daemon.py @@ -6276,6 +6276,11 @@ class PubServer(BaseHTTPRequestHandler): originPathStr = path.split('/followapprove=')[0] followerNickname = originPathStr.replace('/users/', '') followingHandle = path.split('/followapprove=')[1] + if '://' in followingHandle: + handleNickname = getNicknameFromActor(followingHandle) + handleDomain, handlePort = getDomainFromActor(followingHandle) + followingHandle = \ + handleNickname + '@' + getFullDomain(handleDomain, handlePort) if '@' in followingHandle: if not self.server.session: print('Starting new session during follow approval') From 79faa91534b0da1d5fbf3fad507f7f01e455e9ef Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Mar 2021 10:44:37 +0000 Subject: [PATCH 5/6] Removing actors from follow request file --- follow.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/follow.py b/follow.py index 6c88c1adf..91f8ff9e0 100644 --- a/follow.py +++ b/follow.py @@ -84,7 +84,7 @@ def _removeFromFollowBase(baseDir: str, nickname: str, domain: str, acceptOrDenyHandle: str, followFile: str, debug: bool) -> None: - """Removes a handle from follow requests or rejects file + """Removes a handle/actor from follow requests or rejects file """ handle = nickname + '@' + domain accountsDir = baseDir + '/accounts/' + handle @@ -94,8 +94,26 @@ def _removeFromFollowBase(baseDir: str, print('WARN: Approve follow requests file ' + approveFollowsFilename + ' not found') return + acceptDenyActor = None if acceptOrDenyHandle not in open(approveFollowsFilename).read(): - return + # is this stored in the file as an actor rather than a handle? + acceptDenyNickname = acceptOrDenyHandle.split('@')[0] + acceptDenyDomain = acceptOrDenyHandle.split('@')[1] + # for each possible users path construct an actor and + # check if it exists in teh file + usersPaths = ('users', 'profile', 'channel', 'accounts', 'u') + actorFound = False + for usersName in usersPaths: + acceptDenyActor = \ + '://' + acceptDenyDomain + '/' + \ + usersName + '/' + acceptDenyNickname + if acceptDenyActor in open(approveFollowsFilename).read(): + # actor found, so use it in the subsequent removal + approveHandle = acceptDenyActor + actorFound = True + break + if not actorFound: + return approvefilenew = open(approveFollowsFilename + '.new', 'w+') with open(approveFollowsFilename, 'r') as approvefile: for approveHandle in approvefile: From d60abc89f1564da39147b0294e1bee6b3587b278 Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Fri, 12 Mar 2021 10:52:50 +0000 Subject: [PATCH 6/6] Check for actor in each line of the accept or reject file --- follow.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/follow.py b/follow.py index 91f8ff9e0..e4d20d815 100644 --- a/follow.py +++ b/follow.py @@ -108,17 +108,20 @@ def _removeFromFollowBase(baseDir: str, '://' + acceptDenyDomain + '/' + \ usersName + '/' + acceptDenyNickname if acceptDenyActor in open(approveFollowsFilename).read(): - # actor found, so use it in the subsequent removal - approveHandle = acceptDenyActor actorFound = True break if not actorFound: return approvefilenew = open(approveFollowsFilename + '.new', 'w+') with open(approveFollowsFilename, 'r') as approvefile: - for approveHandle in approvefile: - if not approveHandle.startswith(acceptOrDenyHandle): - approvefilenew.write(approveHandle) + if not acceptDenyActor: + for approveHandle in approvefile: + if not approveHandle.startswith(acceptOrDenyHandle): + approvefilenew.write(approveHandle) + else: + for approveHandle in approvefile: + if acceptDenyActor not in approveHandle: + approvefilenew.write(approveHandle) approvefilenew.close() os.rename(approveFollowsFilename + '.new', approveFollowsFilename)