Opportunistic encryption of DMs sent via notification client

merge-requests/30/head
Bob Mottram 2021-03-11 20:33:45 +00:00
parent e330a75696
commit 43e98aba6d
3 changed files with 61 additions and 6 deletions

View File

@ -11,6 +11,7 @@ import html
import time import time
import sys import sys
import select import select
from random import randint
from utils import getNicknameFromActor from utils import getNicknameFromActor
from utils import getDomainFromActor from utils import getDomainFromActor
from utils import getFullDomain from utils import getFullDomain
@ -26,6 +27,8 @@ from follow import sendUnfollowRequestViaServer
from posts import sendPostViaServer from posts import sendPostViaServer
from announce import sendAnnounceViaServer from announce import sendAnnounceViaServer
from pgp import pgpDecrypt from pgp import pgpDecrypt
from pgp import hasLocalPGPkey
from pgp import pgpEncryptToActor
def _waitForKeypress(timeout: int, debug: bool) -> str: def _waitForKeypress(timeout: int, debug: bool) -> str:
@ -320,6 +323,32 @@ def _notificationNewDM(session, toHandle: str,
subject = None subject = None
commentsEnabled = True commentsEnabled = True
subject = None subject = None
# if there is a local PGP key then attempt to encrypt the DM
# using the PGP public key of the recipient
if hasLocalPGPkey():
sayStr = \
'Local PGP key detected...' + \
'Fetching PGP public key for ' + toHandle
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
paddedMessage = newMessage
if len(paddedMessage) < 32:
# add some padding before and after
# This is to guard against cribs based on small messages, like "Hi"
for before in range(randint(1, 16)):
paddedMessage = ' ' + paddedMessage
for after in range(randint(1, 16)):
paddedMessage += ' '
cipherText = \
pgpEncryptToActor(paddedMessage, toHandle)
if not cipherText:
sayStr = toHandle + ' has no PGP public key'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
else:
newMessage = cipherText
sayStr = 'Message encrypted'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
sayStr = 'Sending' sayStr = 'Sending'
_sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak) _sayCommand(sayStr, sayStr, screenreader, systemLanguage, espeak)
if sendPostViaServer(__version__, if sendPostViaServer(__version__,

35
pgp.py
View File

@ -6,7 +6,9 @@ __maintainer__ = "Bob Mottram"
__email__ = "bob@freedombone.net" __email__ = "bob@freedombone.net"
__status__ = "Production" __status__ = "Production"
import os
import subprocess import subprocess
from pathlib import Path
from person import getActorJson from person import getActorJson
@ -300,7 +302,7 @@ def _pgpImportPubKey(recipientPubKey: str) -> str:
return keyId return keyId
def pgpEncrypt(content: str, recipientPubKey: str) -> str: def _pgpEncrypt(content: str, recipientPubKey: str) -> str:
""" Encrypt using your default pgp key to the given recipient """ Encrypt using your default pgp key to the given recipient
""" """
keyId = _pgpImportPubKey(recipientPubKey) keyId = _pgpImportPubKey(recipientPubKey)
@ -321,18 +323,22 @@ def pgpEncrypt(content: str, recipientPubKey: str) -> str:
return encryptResult return encryptResult
def _getPGPPublicKeyFromActor(handle: str) -> str: def _getPGPPublicKeyFromActor(handle: str, actorJson=None) -> str:
"""Searches tags on the actor to see if there is any PGP """Searches tags on the actor to see if there is any PGP
public key specified public key specified
""" """
actorJson = getActorJson(handle, False, False, True) if not actorJson:
actorJson = getActorJson(handle, False, False, True)
if not actorJson: if not actorJson:
return None return None
if not actorJson.get('attachment'): if not actorJson.get('attachment'):
return None return None
if not isinstance(actorJson['attachment'], list): if not isinstance(actorJson['attachment'], list):
return None return None
# search through the tags on the actor
for tag in actorJson['attachment']: for tag in actorJson['attachment']:
if not isinstance(tag, dict):
continue
if not tag.get('value'): if not tag.get('value'):
continue continue
if '--BEGIN PGP PUBLIC KEY BLOCK--' in tag['value']: if '--BEGIN PGP PUBLIC KEY BLOCK--' in tag['value']:
@ -340,6 +346,27 @@ def _getPGPPublicKeyFromActor(handle: str) -> str:
return None return None
def hasLocalPGPkey() -> bool:
"""Returns true if there is a local .gnupg directory
"""
homeDir = str(Path.home())
gpgDir = homeDir + '/.gnupg'
if os.path.isfile(gpgDir):
return True
return False
def pgpEncryptToActor(content: str, toHandle: str) -> str:
"""PGP encrypt a message to the given actor or handle
"""
# get the actor and extract the pgp public key from it
recipientPubKey = _getPGPPublicKeyFromActor(toHandle)
if not recipientPubKey:
return None
# encrypt using the recipient public key
return _pgpEncrypt(content, recipientPubKey)
def pgpDecrypt(content: str, fromHandle: str) -> str: def pgpDecrypt(content: str, fromHandle: str) -> str:
""" Encrypt using your default pgp key to the given recipient """ Encrypt using your default pgp key to the given recipient
fromHandle can be a handle or actor url fromHandle can be a handle or actor url
@ -363,5 +390,5 @@ def pgpDecrypt(content: str, fromHandle: str) -> str:
(decryptResult, err) = proc.communicate() (decryptResult, err) = proc.communicate()
if not decryptResult: if not decryptResult:
return content return content
decryptResult = decryptResult.decode('utf-8') decryptResult = decryptResult.decode('utf-8').strip()
return decryptResult return decryptResult

View File

@ -3000,8 +3000,7 @@ def testFunctions():
'E2EEremoveDevice', 'E2EEremoveDevice',
'setOrganizationScheme', 'setOrganizationScheme',
'fill_headers', 'fill_headers',
'_nothing', '_nothing'
"pgpEncrypt"
] ]
excludeImports = [ excludeImports = [
'link', 'link',