PGP Encryption working

pull/1205/head
Chris Caron 2024-09-30 22:51:53 -04:00
parent 1a9d1dddd5
commit a9ed27ee02
2 changed files with 57 additions and 19 deletions

View File

@ -34,6 +34,7 @@ import typing as t
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.mime.application import MIMEApplication from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.utils import formataddr, make_msgid from email.utils import formataddr, make_msgid
from email.header import Header from email.header import Header
from email import charset from email import charset
@ -787,6 +788,10 @@ class NotifyEmail(NotifyBase):
'There are no Email recipients to notify') 'There are no Email recipients to notify')
return False return False
elif self.use_pgp and not PGP_SUPPORT:
self.logger.warning('PGP Support unavailable')
return False
messages: t.List[EmailMessage] = [] messages: t.List[EmailMessage] = []
# Create a copy of the targets list # Create a copy of the targets list
@ -887,10 +892,33 @@ class NotifyEmail(NotifyBase):
if self.use_pgp: if self.use_pgp:
self.logger.debug("Securing email with PGP Encryption") self.logger.debug("Securing email with PGP Encryption")
# Set our header information to include in the encryption
base['From'] = formataddr(
(None, self.from_addr[1]), charset='utf-8')
base['To'] = formataddr((None, to_addr), charset='utf-8')
base['Subject'] = Header(title, self._get_charset(title))
# Apply our encryption # Apply our encryption
encrypted_content = self.pgp_encrypt_message(base.as_string()) encrypted_content = self.pgp_encrypt_message(base.as_string())
if encrypted_content: if encrypted_content:
base = MIMEText(encrypted_content, "plain") # prepare our messsage
base = MIMEMultipart(
"encrypted", protocol="application/pgp-encrypted")
# Store Autocrypt header (DeltaChat Support)
base.add_header(
"Autocrypt",
"addr=%s; prefer-encrypt=mutual" % formataddr(
(False, to_addr), charset='utf-8'))
# Set Encryption Info Part
enc_payload = MIMEText("Version: 1", "plain")
enc_payload.set_type("application/pgp-encrypted")
base.attach(enc_payload)
enc_payload = MIMEBase("application", "octet-stream")
enc_payload.set_payload(encrypted_content)
base.attach(enc_payload)
# Apply any provided custom headers # Apply any provided custom headers
for k, v in self.headers.items(): for k, v in self.headers.items():
@ -984,8 +1012,14 @@ class NotifyEmail(NotifyBase):
finally: finally:
# Gracefully terminate the connection with the server # Gracefully terminate the connection with the server
if socket is not None: # pragma: no branch if socket is not None: # pragma: no branch
try:
socket.quit() socket.quit()
except (SocketError, smtplib.SMTPException):
# No need to make this a bigger issue as we were exiting
# anyway
pass
# Reduce our dictionary (eliminate expired keys if any) # Reduce our dictionary (eliminate expired keys if any)
self.pgp_public_keys = { self.pgp_public_keys = {
key: value for key, value in self.pgp_public_keys.items() key: value for key, value in self.pgp_public_keys.items()
@ -1079,8 +1113,7 @@ class NotifyEmail(NotifyBase):
os.path.basename(pub_path)) os.path.basename(pub_path))
return True return True
@property def pgp_pubkey(self, email=None):
def pgp_pubkey(self):
""" """
Returns a list of filenames worth scanning for Returns a list of filenames worth scanning for
""" """
@ -1107,9 +1140,14 @@ class NotifyEmail(NotifyBase):
'pub.asc', 'pub.asc',
] ]
# Prepare our key files: emails = []
email = self.from_addr[1] if email:
emails.append(email)
# Prepare our key files:
emails.append(self.from_addr[1])
for email in emails:
_entry = email.split('@')[0].lower() _entry = email.split('@')[0].lower()
if _entry not in fnames: if _entry not in fnames:
fnames.insert(0, f'{_entry}-pub.asc') fnames.insert(0, f'{_entry}-pub.asc')
@ -1125,17 +1163,17 @@ class NotifyEmail(NotifyBase):
if os.path.isfile(os.path.join(self.store.path, fname))), if os.path.isfile(os.path.join(self.store.path, fname))),
None) None)
def pgp_public_key(self, path=None): def pgp_public_key(self, path=None, email=None):
""" """
Opens a spcified pgp public file and returns the key from it which Opens a spcified pgp public file and returns the key from it which
is used to encrypt the message is used to encrypt the message
""" """
if path is None: if path is None:
path = self.pgp_pubkey path = self.pgp_pubkey(email=email)
if not path: if not path:
if self.pgp_generate_keys(path=self.store.path): if self.pgp_generate_keys(path=self.store.path):
path = self.pgp_pubkey path = self.pgp_pubkey(email=email)
if path: if path:
# We should get a hit now # We should get a hit now
return self.pgp_public_key(path=path) return self.pgp_public_key(path=path)

View File

@ -2069,7 +2069,7 @@ def test_plugin_email_pgp(mock_smtp, mock_smtpssl, tmpdir):
obj = Apprise.instantiate('mailto://user:pass@nuxref.com?pgp=yes') obj = Apprise.instantiate('mailto://user:pass@nuxref.com?pgp=yes')
# Nothing to lookup # Nothing to lookup
assert obj.pgp_pubkey is None assert obj.pgp_pubkey() is None
assert obj.pgp_public_key() is None assert obj.pgp_public_key() is None
assert obj.pgp_encrypt_message("message") is False assert obj.pgp_encrypt_message("message") is False
# Keys can not be generated in memory mode # Keys can not be generated in memory mode
@ -2090,15 +2090,15 @@ def test_plugin_email_pgp(mock_smtp, mock_smtpssl, tmpdir):
assert obj.store.mode == PersistentStoreMode.FLUSH assert obj.store.mode == PersistentStoreMode.FLUSH
# Still no public key # Still no public key
assert obj.pgp_pubkey is None assert obj.pgp_pubkey() is None
assert obj.pgp_generate_keys() is True assert obj.pgp_generate_keys() is True
# Now we'll have a public key # Now we'll have a public key
assert isinstance(obj.pgp_pubkey, str) assert isinstance(obj.pgp_pubkey(), str)
# Prepare PGP # Prepare PGP
obj = Apprise.instantiate( obj = Apprise.instantiate(
f'mailto://pgp:pass@nuxref.com?pgp=yes&pgpkey={obj.pgp_pubkey}', 'mailto://pgp:pass@nuxref.com?pgp=yes&pgpkey=%s' % obj.pgp_pubkey(),
asset=asset) asset=asset)
# We will find our key # We will find our key