Skip to content

Commit

Permalink
PGP Encryption working
Browse files Browse the repository at this point in the history
  • Loading branch information
caronc committed Oct 1, 2024
1 parent 1a9d1dd commit a9ed27e
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 19 deletions.
68 changes: 53 additions & 15 deletions apprise/plugins/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.utils import formataddr, make_msgid
from email.header import Header
from email import charset
Expand Down Expand Up @@ -787,6 +788,10 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
'There are no Email recipients to notify')
return False

elif self.use_pgp and not PGP_SUPPORT:
self.logger.warning('PGP Support unavailable')
return False

Check warning on line 793 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L792-L793

Added lines #L792 - L793 were not covered by tests

messages: t.List[EmailMessage] = []

# Create a copy of the targets list
Expand Down Expand Up @@ -887,10 +892,33 @@ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,

if self.use_pgp:
self.logger.debug("Securing email with PGP Encryption")

Check warning on line 894 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L894

Added line #L894 was not covered by tests
# Set our header information to include in the encryption
base['From'] = formataddr(

Check warning on line 896 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L896

Added line #L896 was not covered by tests
(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))

Check warning on line 899 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L898-L899

Added lines #L898 - L899 were not covered by tests

# Apply our encryption
encrypted_content = self.pgp_encrypt_message(base.as_string())

Check warning on line 902 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L902

Added line #L902 was not covered by tests
if encrypted_content:
base = MIMEText(encrypted_content, "plain")
# prepare our messsage
base = MIMEMultipart(

Check warning on line 905 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L905

Added line #L905 was not covered by tests
"encrypted", protocol="application/pgp-encrypted")

# Store Autocrypt header (DeltaChat Support)
base.add_header(

Check warning on line 909 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L909

Added line #L909 was not covered by tests
"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)

Check warning on line 917 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L915-L917

Added lines #L915 - L917 were not covered by tests

enc_payload = MIMEBase("application", "octet-stream")
enc_payload.set_payload(encrypted_content)
base.attach(enc_payload)

Check warning on line 921 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L919-L921

Added lines #L919 - L921 were not covered by tests

# Apply any provided custom headers
for k, v in self.headers.items():
Expand Down Expand Up @@ -984,7 +1012,13 @@ def submit(self, messages: t.List[EmailMessage]):
finally:
# Gracefully terminate the connection with the server
if socket is not None: # pragma: no branch
socket.quit()
try:
socket.quit()

except (SocketError, smtplib.SMTPException):

Check warning on line 1018 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L1018

Added line #L1018 was not covered by tests
# No need to make this a bigger issue as we were exiting
# anyway
pass

Check warning on line 1021 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L1021

Added line #L1021 was not covered by tests

# Reduce our dictionary (eliminate expired keys if any)
self.pgp_public_keys = {
Expand Down Expand Up @@ -1079,8 +1113,7 @@ def pgp_generate_keys(self, path=None):
os.path.basename(pub_path))
return True

@property
def pgp_pubkey(self):
def pgp_pubkey(self, email=None):
"""
Returns a list of filenames worth scanning for
"""
Expand All @@ -1107,35 +1140,40 @@ def pgp_pubkey(self):
'pub.asc',
]

emails = []
if email:
emails.append(email)

Check warning on line 1145 in apprise/plugins/email.py

View check run for this annotation

Codecov / codecov/patch

apprise/plugins/email.py#L1145

Added line #L1145 was not covered by tests

# Prepare our key files:
email = self.from_addr[1]
emails.append(self.from_addr[1])

_entry = email.split('@')[0].lower()
if _entry not in fnames:
fnames.insert(0, f'{_entry}-pub.asc')
for email in emails:
_entry = email.split('@')[0].lower()
if _entry not in fnames:
fnames.insert(0, f'{_entry}-pub.asc')

# Lowercase email (Highest Priority)
_entry = email.lower()
if _entry not in fnames:
fnames.insert(0, f'{_entry}-pub.asc')
# Lowercase email (Highest Priority)
_entry = email.lower()
if _entry not in fnames:
fnames.insert(0, f'{_entry}-pub.asc')

return next(
(os.path.join(self.store.path, fname)
for fname in fnames
if os.path.isfile(os.path.join(self.store.path, fname))),
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
is used to encrypt the message
"""
if path is None:
path = self.pgp_pubkey
path = self.pgp_pubkey(email=email)

if not path:
if self.pgp_generate_keys(path=self.store.path):
path = self.pgp_pubkey
path = self.pgp_pubkey(email=email)
if path:
# We should get a hit now
return self.pgp_public_key(path=path)
Expand Down
8 changes: 4 additions & 4 deletions test/test_plugin_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -2069,7 +2069,7 @@ def test_plugin_email_pgp(mock_smtp, mock_smtpssl, tmpdir):
obj = Apprise.instantiate('mailto://user:[email protected]?pgp=yes')

# 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_encrypt_message("message") is False
# Keys can not be generated in memory mode
Expand All @@ -2090,15 +2090,15 @@ def test_plugin_email_pgp(mock_smtp, mock_smtpssl, tmpdir):
assert obj.store.mode == PersistentStoreMode.FLUSH

# Still no public key
assert obj.pgp_pubkey is None
assert obj.pgp_pubkey() is None

assert obj.pgp_generate_keys() is True
# Now we'll have a public key
assert isinstance(obj.pgp_pubkey, str)
assert isinstance(obj.pgp_pubkey(), str)

# Prepare PGP
obj = Apprise.instantiate(
f'mailto://pgp:[email protected]?pgp=yes&pgpkey={obj.pgp_pubkey}',
'mailto://pgp:[email protected]?pgp=yes&pgpkey=%s' % obj.pgp_pubkey(),
asset=asset)

# We will find our key
Expand Down

0 comments on commit a9ed27e

Please sign in to comment.