Skip to content

Commit b907411

Browse files
authored
enh: collector_mail_attach Decrypt GPG attachments (#2623)
1 parent 25eee3e commit b907411

20 files changed

+269
-12
lines changed

.github/workflows/unittests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ jobs:
7272
- name: Run basic testsuite
7373
if: ${{ matrix.type == 'basic' }}
7474
run: pytest --cov intelmq/ --cov-report=xml --cov-branch intelmq/
75-
75+
- name: Assure permissions
76+
run: chmod -R u=rwX,go= intelmq/tests/bots/collectors/mail/gpg_ring/
7677
- name: Run full testsuite
7778
if: ${{ matrix.type == 'full' }}
7879
run: pytest --cov intelmq/ --cov-report=xml --cov-branch intelmq/ contrib/
@@ -81,3 +82,4 @@ jobs:
8182
INTELMQ_TEST_DATABASES: 1
8283
INTELMQ_TEST_EXOTIC: 1
8384
INTELMQ_TEST_INSTALLATION: 1
85+
GPG_RING_PATH: intelmq/tests/bots/collectors/mail/gpg_ring/

.reuse/dep5

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,15 @@ License: AGPL-3.0-or-later
3333
Files: docs/_static/n6/data-flow.png docs/_static/n6/n6-schemat2.png
3434
Copyright: CERT.pl <[email protected]>
3535
License: AGPL-3.0-only
36+
37+
Files: intelmq/tests/bots/collectors/mail/gpg_attachment.eml
38+
Copyright: 2025 Edvard Rejthar, CSIRT.cz <[email protected]>
39+
License: AGPL-3.0-or-later
40+
41+
Files: intelmq/tests/bots/collectors/mail/gpg_wrong_attachment.eml
42+
Copyright: 2025 Edvard Rejthar, CSIRT.cz <[email protected]>
43+
License: AGPL-3.0-or-later
44+
45+
Files: intelmq/tests/bots/collectors/mail/gpg_ring/**
46+
Copyright: 2025 Edvard Rejthar, CSIRT.cz <[email protected]>
47+
License: AGPL-3.0-or-later

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Please refer to the [NEWS](NEWS.md) for a list of changes which have an affect o
2929

3030
### Bots
3131
#### Collectors
32+
- `intelmq.bots.collectors.mail.collector_mail_attach`: Decrypt GPG attachments (PR#2623 by Edvard Rejthar).
3233
- `intelmq.bots.collectors.shodan.collector_alert`: Added a new collector to query the Shodan Alert API (PR#2618 by Sebastian Wagner and Malawi CERT).
3334
- Remove `intelmq.bots.collectors.blueliv` as it uses an unmaintained library, does not work any more and breaks other CI tests (fixes #2593, PR#2632 by Sebastian Wagner).
3435

docs/user/bots.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,18 @@ The fields can be used by parsers to identify the feed and are not automatically
486486

487487
(optional, boolean) Whether to extract compress files from the attachment. Defaults to true.
488488

489+
**`decrypt_openpgp`**
490+
491+
(optional, boolean) Whether to decrypt the attachment with GPG. Defaults to false.
492+
493+
**`openpgp_passphrase`**
494+
495+
(optional, string) The OpenPGP private key passhrase.
496+
497+
**`gpg_home`**
498+
499+
(optional, string) Change the GPG home directory.
500+
489501
**`sent_from`**
490502

491503
(optional, string) Only process messages sent from this address. Defaults to null (any sender).

intelmq/bots/collectors/mail/REQUIREMENTS.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
# SPDX-License-Identifier: AGPL-3.0-or-later
33

44
imbox>=0.8.5
5+
python-gnupg>=0.5

intelmq/bots/collectors/mail/collector_mail_attach.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@
1111
Uses the common mail iteration method from the lib file.
1212
"""
1313
import re
14+
1415
from intelmq.lib.utils import unzip
15-
from intelmq.lib.exceptions import InvalidArgument
16+
from intelmq.lib.exceptions import InvalidArgument, MissingDependencyError
1617

1718
from ._lib import MailCollectorBot
1819

1920

2021
class MailAttachCollectorBot(MailCollectorBot):
2122
"""Monitor IMAP mailboxes and retrieve mail attachments"""
23+
2224
attach_regex: str = "csv.zip"
2325
extract_files: bool = True
2426
folder: str = "INBOX"
@@ -28,11 +30,25 @@ class MailAttachCollectorBot(MailCollectorBot):
2830
mail_user: str = "<user>"
2931
rate_limit: int = 60
3032
subject_regex: str = "<subject>"
33+
decrypt_openpgp: bool = False
34+
""" Decrypt the attachment with OpenPGP """
35+
36+
openpgp_passphrase: str = ""
37+
""" The OpenPGP private key passhrase """
38+
39+
gpg_home: str = ""
40+
""" Change the GPG home directory """
3141

3242
def init(self):
3343
super().init()
3444
if self.attach_regex is None:
3545
raise InvalidArgument('attach_regex', expected='string')
46+
if self.decrypt_openpgp:
47+
try:
48+
from gnupg import GPG
49+
except ImportError:
50+
raise MissingDependencyError("python-gnupg", ">=0.5")
51+
self._gpg = GPG(gnupghome=self.gpg_home)
3652

3753
def process_message(self, uid, message):
3854
seen = False
@@ -63,6 +79,15 @@ def process_message(self, uid, message):
6379
raw_reports = ((attach_filename, attach['content'].read()), )
6480

6581
for file_name, raw_report in raw_reports:
82+
if self.decrypt_openpgp:
83+
gpg = self._gpg.decrypt(
84+
raw_report, passphrase=self.openpgp_passphrase
85+
)
86+
if gpg.ok:
87+
raw_report = gpg.data
88+
else:
89+
self.logger.error('Could not decrypt attachment %s: %s.', file_name, gpg.status)
90+
continue
6691
report = self.new_report()
6792
report.add("raw", raw_report)
6893
if file_name:
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
MIME-Version: 1.0
2+
Content-Type: multipart/mixed; boundary="===============0256050913907025376=="
3+
Subject: foobar zip
4+
From: Sebastian Wagner <[email protected]>
5+
6+
Message-ID: <[email protected]>
7+
Date: Tue, 3 Sep 2019 16:57:40 +0200
8+
Content-Language: en-US
9+
10+
--===============0256050913907025376==
11+
Content-Type: text/html; charset="utf-8"
12+
Content-Transfer-Encoding: 7bit
13+
14+
<html>
15+
<head>
16+
17+
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
18+
</head>
19+
<body text="#000000" bgcolor="#FFFFFF">
20+
Please look at the attachment<br>
21+
</body>
22+
</html>
23+
24+
--===============0256050913907025376==
25+
Content-Type: application/octet-stream
26+
Content-Transfer-Encoding: base64
27+
Content-Disposition: attachment; filename="foobar.txt.gpg"
28+
MIME-Version: 1.0
29+
30+
hQGMA9ig68HPFWOpAQv/fsRXK1mdmGw83CdbIOZeUlFVx2KKEONWVYIu/CHPxqxREYUWTva+XxYE
31+
LirKR9xK9lG19H6BSSZD3xXMahAShRzgs1dHH4UMxGqdpiXo5DbdVkTS7kg5+hG7QX20x+ExwsRd
32+
r76/gzY+Lmsv4hydxMHyG/w0OBsV/0mLWGAAVzer9Y8xkUP6jB16XzoSQGkfCP+7DpK224135JgU
33+
E6XSD2p+WKzALxXiET4IfinJnG8sSTHlkCQZgBp8lpDPdP2BbHo0KmvAofxaRqkvMfpVk7kUyy2W
34+
8tENKSEPu0sKEX8HZhT8Sw3X2pn8ggbAadHRjOzOO58MUvWJvwURqa1LIITA4pmyhw4svp1UzODb
35+
Q2Cd+MyWikjD4CebJ7P/JpnoDokux9qxWKnuuvmM4mqr6bvL+Sztud4V2Z60idokbzuEaJR6ZztT
36+
SBMatdh/OHcUnCaYCJiEOlrgk0hYcZ12XR5C8OBAI4St9F5eGjMB0l1tZz7CeQ6MrAoPY2pZ54XO
37+
0k0BA3PpG+K/khPOns8rW2mlYKgWfJqRdc91EasA4+iFoM+iFwyDQGBKbAdCu0BeIf9j7t5s5LOG
38+
iPWI5m2AIWf+aFNqKotGFAWXeSbMWw==
39+
40+
--===============0256050913907025376==--
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v:1:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
This is a revocation certificate for the OpenPGP key:
2+
3+
pub rsa3072 2019-12-11 [S]
4+
3C8124A8245618D286CF871E94CE2905DB00CDB7
5+
uid Example identity 2 <[email protected]>
6+
7+
A revocation certificate is a kind of "kill switch" to publicly
8+
declare that a key shall not anymore be used. It is not possible
9+
to retract such a revocation certificate once it has been published.
10+
11+
Use it to revoke this key in case of a compromise or loss of
12+
the secret key. However, if the secret key is still accessible,
13+
it is better to generate a new revocation certificate and give
14+
a reason for the revocation. For details see the description of
15+
of the gpg command "--generate-revocation" in the GnuPG manual.
16+
17+
To avoid an accidental use of this file, a colon has been inserted
18+
before the 5 dashes below. Remove this colon with a text editor
19+
before importing and publishing this revocation certificate.
20+
21+
:-----BEGIN PGP PUBLIC KEY BLOCK-----
22+
Comment: This is a revocation certificate
23+
24+
iQG2BCABCgAgFiEEPIEkqCRWGNKGz4celM4pBdsAzbcFAl3xFwwCHQAACgkQlM4p
25+
BdsAzbeofgv+JUQ4Qb40Z0p2ML+jciJjNiRdKYOUyhY1UKECwq6QlbW/MSkQMn4g
26+
esJfevFwGHKxaI4bw9ywFMtR/UB91YDY4D+lURm4qMuc8pFYEBSMhro0x8ToWz1E
27+
vupwAqTF484ybBrujg2UaOox9BbyhIbsiAH3ttB6wThCbXdhkxp/w6qUaWo+n5Ra
28+
5xWrtFkHJx+WIE4mzxqvnuN3RtA9tkr3L8oavw8Vy0j44lhkVE4Mrl/XDGOnryvv
29+
3WYuiHjOdTP5plb/p4veTvCfUZOcrmhTiwU8gQWMQfy3Nr1NmIhNwoXRTwB41ogt
30+
j3/mijdKVlRkzzmungIt0G3NJM8sAI9TiZ6KyaH4g3fVHU0ddfUwd5sCV3Q+UHfJ
31+
iUZeO1qHA8PA0NZwquRE2rnGdc63nHE/7AD9SjpvqIoBcvfoZCniCBm70pSmJvSP
32+
Csd2/TvaI8PQ28vAVY2LKqCX6JX1MvkiJOZbDGl+VBhCavQCQ8lR0d2b+wriDa3y
33+
2rhCXk0+ozc+
34+
=92kE
35+
-----END PGP PUBLIC KEY BLOCK-----
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
This is a revocation certificate for the OpenPGP key:
2+
3+
pub rsa3072 2019-12-11 [S]
4+
F14F2E8097E0CCDE93C4E871F4A4F26779FA03BB
5+
uid Example identity <[email protected]>
6+
7+
A revocation certificate is a kind of "kill switch" to publicly
8+
declare that a key shall not anymore be used. It is not possible
9+
to retract such a revocation certificate once it has been published.
10+
11+
Use it to revoke this key in case of a compromise or loss of
12+
the secret key. However, if the secret key is still accessible,
13+
it is better to generate a new revocation certificate and give
14+
a reason for the revocation. For details see the description of
15+
of the gpg command "--generate-revocation" in the GnuPG manual.
16+
17+
To avoid an accidental use of this file, a colon has been inserted
18+
before the 5 dashes below. Remove this colon with a text editor
19+
before importing and publishing this revocation certificate.
20+
21+
:-----BEGIN PGP PUBLIC KEY BLOCK-----
22+
Comment: This is a revocation certificate
23+
24+
iQG2BCABCgAgFiEE8U8ugJfgzN6TxOhx9KTyZ3n6A7sFAl3xFtwCHQAACgkQ9KTy
25+
Z3n6A7vCwwv/U1mKrClA3lxfI+lTAecMeYwlYtYcAveDjJkMs3AtAUQyqd6B7I5F
26+
RXQL50xjiB+S+yv3WoXd4eQbT9Z2g1OnMJfSHzENM0K+yosUJS+TpN3SU7YxJ9GR
27+
4J3eDxo0IyB0mL1QFQXcsWSu92yAyQpwJnscg6S0hvxiq8ij+OyDPNctDtpxcArr
28+
orh9+rGfLBNABemtKgVgzmedjyB7fdPYjgCi/xJL8eYUxlRUgwuwCS7A3DBaSdwU
29+
bd4dGvGTvzSmKp8OrbW1j7yf4Vq91qVcSqAdv1y0EpKwnnefAPJV8TsFr4wg0GpI
30+
ocKDGMiQMPekxfeDPrZcII4jjYXlk6WRzXPTQySZcHBN6o/TKBjdanZv5iV4JktU
31+
jIvru6M7JAM0PJPFj+AwY6hLV3p741qCYYEUlFs9Yjq7j4vhoHqgdVbmZSVcditn
32+
KLcM6F7jEx0odcbL157/xLHHqsCPVoZrr9uZVuN8J6uIYw0GTXFRlpMhDEpVg+Jd
33+
mB4WSiNPkql0
34+
=rwt1
35+
-----END PGP PUBLIC KEY BLOCK-----

0 commit comments

Comments
 (0)