Skip to content

Commit 5f5e466

Browse files
committed
CI: add live tests for dkimpy and Mail::DKIM interop
1 parent b87826b commit 5f5e466

File tree

6 files changed

+147
-67
lines changed

6 files changed

+147
-67
lines changed

.github/workflows/build.yml

+2-6
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,7 @@ jobs:
4444
run: sudo apt update
4545

4646
- name: Install dependencies
47-
run: sudo apt install libbsd-dev libidn2-dev libjansson-dev libmilter-dev libssl-dev
48-
49-
- name: Set up Python
50-
uses: actions/setup-python@v5
51-
with:
47+
run: sudo apt install libbsd-dev libidn2-dev libjansson-dev libmail-dkim-perl libmilter-dev libssl-dev
5248
# 3.8 is listed last because it's the lowest version we support for
5349
# tests, so we want to use it as the default.
5450
python-version: |
@@ -61,7 +57,7 @@ jobs:
6157
3.8
6258
6359
- name: Install Python dependencies
64-
run: sudo pip install pytest miltertest
60+
run: sudo pip install pytest miltertest dkimpy[ARC]
6561

6662
- name: Build OpenARC
6763
run: |

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ Tests can be run with `make check`. OpenARC's test suite requires:
104104
* [pytest](https://pytest.org)
105105
* The Python [miltertest](https://pypi.org/project/miltertest/) library
106106

107+
There are also optional test dependencies whose associated tests will be
108+
skipped if the dependency is not found:
109+
110+
* [dkimpy](https://launchpad.net/dkimpy) >= 0.9.0
111+
* [Mail::DKIM](https://metacpan.org/pod/Mail::DKIM)
112+
107113
## Additional Documentation
108114

109115
The man pages for the `openarc` filter are present in the `openarc`

test/conftest.py

+13-17
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@
1616
def private_key(tmp_path_factory, tool_path):
1717
basepath = tmp_path_factory.mktemp('keys')
1818

19-
for s, d in [
19+
selectors = [
2020
['elpmaxe', 'example.com'],
2121
['xn--2j5b', 'xn--vv4b606a.example.com'],
22+
['dkimpy', 'example.com'],
23+
['perl', 'example.com'],
24+
]
25+
26+
for s, d in [
27+
*selectors,
2228
['unsafe', 'example.com'],
2329
]:
2430
binargs = [
@@ -39,21 +45,9 @@ def private_key(tmp_path_factory, tool_path):
3945

4046
basepath.joinpath('unsafe._domainkey.example.com.key').chmod(0o644)
4147

42-
testkeys = (
43-
'sel._domainkey.dkimpy.example.com v=DKIM1; k=rsa; '
44-
'p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqf/MoqRqzK3/bcCyLSx5'
45-
'CDvyPotNDBjLLFHdMmcWDiSZ8saslFyNR6FkFxuNtw843m7MkwOSJ9TRd9p+OoRLDv'
46-
'H0jDR1Dqq22QOJKiG5XQ91aZwin9jpWKkuoRoRZRhWrzUOJWAybHarsEQm9iCPh2zn'
47-
'dbSPSzPQL1OsjURIuw5G9+/nr5rhJ72Qi6v86zofWUKdXhLf+oVmho79D0xGMFFm0f'
48-
'b98xIeZlgJTnmrj/zuxIKHeVmGKI1j6L3xttdcDiUVRGxoubkFzg9TIBGhdeFkpa0C'
49-
'ZuhB/1/U3f1oG3Upx5o/jXTQk/dwVaaeEXnRmTsfGYn4GQ9ziity1ijLsQIDAQAB\n'
50-
)
51-
52-
for fname in [
53-
'elpmaxe._domainkey.example.com.txt',
54-
'xn--2j5b._domainkey.xn--vv4b606a.example.com.txt',
55-
]:
56-
with open(basepath.joinpath(fname), 'r') as f:
48+
testkeys = ''
49+
for s, d in selectors:
50+
with open(basepath.joinpath(f'{s}._domainkey.{d}.txt'), 'r') as f:
5751
testkeys += f.read()
5852

5953
keyfile = basepath.joinpath('public.key')
@@ -176,7 +170,7 @@ def _run_miltertest(
176170
if standard_headers:
177171
headers.extend(
178172
[
179-
['From', ' [email protected]\n'],
173+
['From', ' [email protected]'],
180174
['Date', ' Fri, 04 Oct 2024 10:11:12 -0400'],
181175
['Subject', request.function.__name__],
182176
]
@@ -218,6 +212,8 @@ def _run_miltertest(
218212

219213
return {
220214
'headers': ins_headers,
215+
'msg_headers': headers,
216+
'msg_body': body,
221217
}
222218

223219
return _run_miltertest

test/files/test_dkimpy_verify.conf

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[
2+
{
3+
"Canonicalization": "relaxed/simple"
4+
},
5+
{
6+
"Canonicalization": "relaxed/relaxed"
7+
},
8+
{
9+
"Canonicalization": "simple/relaxed"
10+
}
11+
]

test/test_interop.py

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
3+
import subprocess
4+
5+
import pytest
6+
7+
HAS_DKIMPY = True
8+
try:
9+
from dkim import ARC
10+
except ImportError:
11+
HAS_DKIMPY = False
12+
13+
14+
@pytest.fixture(scope='session')
15+
def dkimpy():
16+
if not HAS_DKIMPY:
17+
pytest.skip('dkimpy not found')
18+
19+
20+
@pytest.fixture(scope='session')
21+
def perl_mail_dkim():
22+
try:
23+
subprocess.run(['perl', '-MMail::DKIM', '-e', ''], check=True)
24+
except Exception:
25+
pytest.skip('Mail::DKIM not found')
26+
27+
28+
def test_dkimpy_sign(run_miltertest, private_key, dkimpy):
29+
hdrs = [
30+
['Subject', ' test message from dkimpy'],
31+
['From', ' [email protected]'],
32+
['To', ' [email protected]'],
33+
['Authentication-Results', ' dkimpy.example.com; none'],
34+
]
35+
36+
msg = b''
37+
for h, v in hdrs:
38+
msg += f'{h}: {v}\r\n'.encode()
39+
msg += b'\r\ntest body\r\n'
40+
41+
res = ARC(msg).sign(b'dkimpy', b'example.com', private_key['basepath'].joinpath('dkimpy._domainkey.example.com.key').read_bytes(), b'dkimpy.example.com')
42+
43+
hdrs = [
44+
*[h.decode().rstrip().split(':', 1) for h in res],
45+
*hdrs,
46+
]
47+
res = run_miltertest(hdrs, False)
48+
49+
assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']
50+
assert res['headers'][1][0] == 'ARC-Seal'
51+
assert 'cv=pass' in res['headers'][1][1]
52+
assert res['headers'][2][0] == 'ARC-Message-Signature'
53+
assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']
54+
55+
56+
def test_dkimpy_verify(run_miltertest, private_key, dkimpy):
57+
# we don't test simple/simple because dkimpy uses the wrong default
58+
for i in range(0, 3):
59+
res = run_miltertest(milter_instance=i)
60+
61+
msg = b''
62+
for h, v in [*res['headers'], *res['msg_headers']]:
63+
msg += f'{h}:{v}\r\n'.encode()
64+
msg += f'\r\n{res["msg_body"]}'.encode()
65+
66+
def dnsfunc(domain, timeout=5):
67+
with open(private_key['public_keys'], 'rb') as f:
68+
for l in f:
69+
if l.startswith(domain[:-1]):
70+
return l.split(None, 1)[1]
71+
72+
return ''
73+
74+
res = ARC(msg).verify(dnsfunc)
75+
assert res[0] == b'pass'
76+
77+
78+
def test_perl_sign(run_miltertest, private_key, perl_mail_dkim):
79+
hdrs = [
80+
['Subject', ' test message from Mail::DKIM'],
81+
['From', ' [email protected]'],
82+
['To', ' [email protected]'],
83+
['Authentication-Results', ' perl.example.com; none'],
84+
]
85+
86+
msg = ''
87+
for h, v in hdrs:
88+
msg += f'{h}:{v}\r\n'
89+
msg += '\r\ntest body\r\n'
90+
91+
res = subprocess.run(
92+
[
93+
'perl',
94+
'-MMail::DKIM::ARC::Signer',
95+
'-E',
96+
f'my $arc = new Mail::DKIM::ARC::Signer(Domain => "example.com", Selector => "perl", SrvId => "perl.example.com", KeyFile => "{private_key["basepath"].joinpath("perl._domainkey.example.com.key")}", Chain => "ar"); while (<STDIN>) {{ $arc->PRINT($_) }}; $arc->CLOSE; say join("\n", $arc->as_strings)',
97+
],
98+
input=msg,
99+
text=True,
100+
capture_output=True,
101+
)
102+
103+
assert res.returncode == 0
104+
105+
hdrs = [
106+
*[x.split(':', 1) for x in res.stdout.splitlines()],
107+
*hdrs,
108+
]
109+
110+
res = run_miltertest(hdrs, False)
111+
assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']
112+
assert res['headers'][1][0] == 'ARC-Seal'
113+
assert 'cv=pass' in res['headers'][1][1]
114+
assert res['headers'][2][0] == 'ARC-Message-Signature'
115+
assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']

test/test_milter.py

-44
Original file line numberDiff line numberDiff line change
@@ -26,50 +26,6 @@ def test_milter_v2(run_miltertest):
2626
assert res['headers'][3] == ['ARC-Authentication-Results', 'i=1; example.com; arc=none smtp.remote-ip=127.0.0.1']
2727

2828

29-
def test_milter_staticmsg(run_miltertest):
30-
headers = [
31-
[
32-
'ARC-Seal',
33-
(
34-
' i=1; cv=none; a=rsa-sha256; d=dkimpy.example.com; s=sel;\r\n'
35-
' t=1728713840;\r\n'
36-
' b=jmHJmDXHe4eFAurv+yXz1RTRLj+XNaHedD4GYWPt0XntR94pMNSFlU2TxT0rzkMcE4Nkt\r\n'
37-
' xFrz0OYVfexpgNJ393tO8czBH4OwEwV2E5h+U/8N1vM+KHKfcg2n02SOxUa991Z1+CXUrO6\r\n'
38-
' lUnIx7gN+iz3x2muWG6hm6d1J0h4+yaQCuVlNImf3PM/M7l57GbfHvQpbYI9m4hf6IMncRS\r\n'
39-
' sOuXyTaH8NrWpqqM0KctxR4x+kC/Y3dKNYcL5VwbajlXletkmHO79sbuGD0HsK8HUdzfE1Z\r\n'
40-
' gGinobwxRu7skmTPq0TSlBQQ/1fuxpSOpocjnY+E/g3FH3ZsAtbOG2jVYd9w=='
41-
),
42-
],
43-
[
44-
'ARC-Message-Signature',
45-
(
46-
' i=1; a=rsa-sha256; c=relaxed/relaxed;\r\n'
47-
' d=dkimpy.example.com; s=sel; t=1728713840; h=content-type :\r\n'
48-
' mime-version : content-transfer-encoding : subject : from : to : from;\r\n'
49-
' bh=Pb6s/Xlf4u1eDlYyO0NCaMRMrCg6xDNkK5byz8RDY1s=;\r\n'
50-
' b=dmFKbeiAEsaA/gnLQyuRBcX72pvARuJMrZIptplgCGp9vqudMP2ngI/g8eo63nQYMB0md\r\n'
51-
' AaofYsl5lD8qE/B20FDgn66jTHQIGsPi0Fv06Mf45NaTFpeaEyexjZunYXSLao3RY5Cqtac\r\n'
52-
' m0BcCS/MaaiMBoDmcRa5GOzBi02coJG5IsDt+ZWT6P7nHQHrDNsuLBeJBX7+vJ0bM9QHbCE\r\n'
53-
' Q+eZZxcT7W2MWaByV2Jjz4B+sh0IzfX2wPNsGOsNpD+MvpehQsa9ig7eEndNWw7V1qpaMN+\r\n'
54-
' vtOnb5H80nu0K4H7fvrNUI4h4b+UTumqR/HhiNTFRobUGiwuvrP4CWHj3dtQ==\r\n'
55-
),
56-
],
57-
['ARC-Authentication-Results', ' i=1; dkimpy.example.com'],
58-
['Content-Type', ' text/plain; charset="us-ascii"'],
59-
['MIME-Version', ' 1.0'],
60-
['Content-Transfer-Encoding', ' 7bit'],
61-
['Subject', ' test message from dkimpy'],
62-
['From', ' [email protected]'],
63-
['To', ' [email protected]'],
64-
]
65-
res = run_miltertest(headers, False, 'test message\r\n')
66-
assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']
67-
assert res['headers'][1][0] == 'ARC-Seal'
68-
assert 'cv=pass' in res['headers'][1][1]
69-
assert res['headers'][2][0] == 'ARC-Message-Signature'
70-
assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']
71-
72-
7329
def test_milter_canon_simple(run_miltertest):
7430
"""Sign a message with simple canonicalization and then verify it"""
7531
res = run_miltertest()

0 commit comments

Comments
 (0)