Skip to content

Commit

Permalink
CI: add live tests for dkimpy and Mail::DKIM interop
Browse files Browse the repository at this point in the history
  • Loading branch information
flowerysong committed Nov 12, 2024
1 parent b87826b commit 0675ae1
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 63 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
run: sudo apt update

- name: Install dependencies
run: sudo apt install libbsd-dev libidn2-dev libjansson-dev libmilter-dev libssl-dev
run: sudo apt install libbsd-dev libidn2-dev libjansson-dev libmail-dkim-perl libmilter-dev libssl-dev

- name: Set up Python
uses: actions/setup-python@v5
Expand All @@ -61,7 +61,7 @@ jobs:
3.8
- name: Install Python dependencies
run: sudo pip install pytest miltertest
run: sudo pip install pytest miltertest dkimpy[ARC]

- name: Build OpenARC
run: |
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ Tests can be run with `make check`. OpenARC's test suite requires:
* [pytest](https://pytest.org)
* The Python [miltertest](https://pypi.org/project/miltertest/) library

There are also optional test dependencies whose associated tests will be
skipped if the dependency is not found:

* [dkimpy](https://launchpad.net/dkimpy) >= 0.9.0
* [Mail::DKIM](https://metacpan.org/pod/Mail::DKIM)

## Additional Documentation

The man pages for the `openarc` filter are present in the `openarc`
Expand Down
30 changes: 13 additions & 17 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@
def private_key(tmp_path_factory, tool_path):
basepath = tmp_path_factory.mktemp('keys')

for s, d in [
selectors = [
['elpmaxe', 'example.com'],
['xn--2j5b', 'xn--vv4b606a.example.com'],
['dkimpy', 'example.com'],
['perl', 'example.com'],
]

for s, d in [
*selectors,
['unsafe', 'example.com'],
]:
binargs = [
Expand All @@ -39,21 +45,9 @@ def private_key(tmp_path_factory, tool_path):

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

testkeys = (
'sel._domainkey.dkimpy.example.com v=DKIM1; k=rsa; '
'p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqf/MoqRqzK3/bcCyLSx5'
'CDvyPotNDBjLLFHdMmcWDiSZ8saslFyNR6FkFxuNtw843m7MkwOSJ9TRd9p+OoRLDv'
'H0jDR1Dqq22QOJKiG5XQ91aZwin9jpWKkuoRoRZRhWrzUOJWAybHarsEQm9iCPh2zn'
'dbSPSzPQL1OsjURIuw5G9+/nr5rhJ72Qi6v86zofWUKdXhLf+oVmho79D0xGMFFm0f'
'b98xIeZlgJTnmrj/zuxIKHeVmGKI1j6L3xttdcDiUVRGxoubkFzg9TIBGhdeFkpa0C'
'ZuhB/1/U3f1oG3Upx5o/jXTQk/dwVaaeEXnRmTsfGYn4GQ9ziity1ijLsQIDAQAB\n'
)

for fname in [
'elpmaxe._domainkey.example.com.txt',
'xn--2j5b._domainkey.xn--vv4b606a.example.com.txt',
]:
with open(basepath.joinpath(fname), 'r') as f:
testkeys = ''
for s, d in selectors:
with open(basepath.joinpath(f'{s}._domainkey.{d}.txt'), 'r') as f:
testkeys += f.read()

keyfile = basepath.joinpath('public.key')
Expand Down Expand Up @@ -176,7 +170,7 @@ def _run_miltertest(
if standard_headers:
headers.extend(
[
['From', ' [email protected]\n'],
['From', ' [email protected]'],
['Date', ' Fri, 04 Oct 2024 10:11:12 -0400'],
['Subject', request.function.__name__],
]
Expand Down Expand Up @@ -218,6 +212,8 @@ def _run_miltertest(

return {
'headers': ins_headers,
'msg_headers': headers,
'msg_body': body,
}

return _run_miltertest
11 changes: 11 additions & 0 deletions test/files/test_dkimpy_verify.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
{
"Canonicalization": "relaxed/simple"
},
{
"Canonicalization": "relaxed/relaxed"
},
{
"Canonicalization": "simple/relaxed"
}
]
115 changes: 115 additions & 0 deletions test/test_interop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/env python3

import subprocess

import pytest

HAS_DKIMPY = True
try:
from dkim import ARC
except ImportError:
HAS_DKIMPY = False


@pytest.fixture(scope='session')
def dkimpy():
if not HAS_DKIMPY:
pytest.skip('dkimpy not found')


@pytest.fixture(scope='session')
def perl_mail_dkim():
try:
subprocess.run(['perl', '-MMail::DKIM', '-e', ''], check=True)
except Exception:
pytest.skip('Mail::DKIM not found')


def test_dkimpy_sign(run_miltertest, private_key, dkimpy):
hdrs = [
['Subject', ' test message from dkimpy'],
['From', ' [email protected]'],
['To', ' [email protected]'],
['Authentication-Results', ' dkimpy.example.com; none'],
]

msg = b''
for h, v in hdrs:
msg += f'{h}: {v}\r\n'.encode()
msg += b'\r\ntest body\r\n'

res = ARC(msg).sign(b'dkimpy', b'example.com', private_key['basepath'].joinpath('dkimpy._domainkey.example.com.key').read_bytes(), b'dkimpy.example.com')

hdrs = [
*[h.decode().rstrip().split(':', 1) for h in res],
*hdrs,
]
res = run_miltertest(hdrs, False)

assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']
assert res['headers'][1][0] == 'ARC-Seal'
assert 'cv=pass' in res['headers'][1][1]
assert res['headers'][2][0] == 'ARC-Message-Signature'
assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']


def test_dkimpy_verify(run_miltertest, private_key, dkimpy):
# we don't test simple/simple because dkimpy uses the wrong default
for i in range(0, 3):
res = run_miltertest(milter_instance=i)

msg = b''
for h, v in [*res['headers'], *res['msg_headers']]:
msg += f'{h}:{v}\r\n'.encode()
msg += f'\r\n{res["msg_body"]}'.encode()

def dnsfunc(domain, timeout=5):
with open(private_key['public_keys'], 'rb') as f:
for line in f:
if line.startswith(domain[:-1]):
return line.split(None, 1)[1]

return ''

res = ARC(msg).verify(dnsfunc)
assert res[0] == b'pass'


def test_perl_sign(run_miltertest, private_key, perl_mail_dkim):
hdrs = [
['Subject', ' test message from Mail::DKIM'],
['From', ' [email protected]'],
['To', ' [email protected]'],
['Authentication-Results', ' perl.example.com; none'],
]

msg = ''
for h, v in hdrs:
msg += f'{h}:{v}\r\n'
msg += '\r\ntest body\r\n'

res = subprocess.run(
[
'perl',
'-MMail::DKIM::ARC::Signer',
'-E',
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)', # noqa: E501
],
input=msg,
text=True,
capture_output=True,
)

assert res.returncode == 0

hdrs = [
*[x.split(':', 1) for x in res.stdout.splitlines()],
*hdrs,
]

res = run_miltertest(hdrs, False)
assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']
assert res['headers'][1][0] == 'ARC-Seal'
assert 'cv=pass' in res['headers'][1][1]
assert res['headers'][2][0] == 'ARC-Message-Signature'
assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']
44 changes: 0 additions & 44 deletions test/test_milter.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,50 +26,6 @@ def test_milter_v2(run_miltertest):
assert res['headers'][3] == ['ARC-Authentication-Results', 'i=1; example.com; arc=none smtp.remote-ip=127.0.0.1']


def test_milter_staticmsg(run_miltertest):
headers = [
[
'ARC-Seal',
(
' i=1; cv=none; a=rsa-sha256; d=dkimpy.example.com; s=sel;\r\n'
' t=1728713840;\r\n'
' b=jmHJmDXHe4eFAurv+yXz1RTRLj+XNaHedD4GYWPt0XntR94pMNSFlU2TxT0rzkMcE4Nkt\r\n'
' xFrz0OYVfexpgNJ393tO8czBH4OwEwV2E5h+U/8N1vM+KHKfcg2n02SOxUa991Z1+CXUrO6\r\n'
' lUnIx7gN+iz3x2muWG6hm6d1J0h4+yaQCuVlNImf3PM/M7l57GbfHvQpbYI9m4hf6IMncRS\r\n'
' sOuXyTaH8NrWpqqM0KctxR4x+kC/Y3dKNYcL5VwbajlXletkmHO79sbuGD0HsK8HUdzfE1Z\r\n'
' gGinobwxRu7skmTPq0TSlBQQ/1fuxpSOpocjnY+E/g3FH3ZsAtbOG2jVYd9w=='
),
],
[
'ARC-Message-Signature',
(
' i=1; a=rsa-sha256; c=relaxed/relaxed;\r\n'
' d=dkimpy.example.com; s=sel; t=1728713840; h=content-type :\r\n'
' mime-version : content-transfer-encoding : subject : from : to : from;\r\n'
' bh=Pb6s/Xlf4u1eDlYyO0NCaMRMrCg6xDNkK5byz8RDY1s=;\r\n'
' b=dmFKbeiAEsaA/gnLQyuRBcX72pvARuJMrZIptplgCGp9vqudMP2ngI/g8eo63nQYMB0md\r\n'
' AaofYsl5lD8qE/B20FDgn66jTHQIGsPi0Fv06Mf45NaTFpeaEyexjZunYXSLao3RY5Cqtac\r\n'
' m0BcCS/MaaiMBoDmcRa5GOzBi02coJG5IsDt+ZWT6P7nHQHrDNsuLBeJBX7+vJ0bM9QHbCE\r\n'
' Q+eZZxcT7W2MWaByV2Jjz4B+sh0IzfX2wPNsGOsNpD+MvpehQsa9ig7eEndNWw7V1qpaMN+\r\n'
' vtOnb5H80nu0K4H7fvrNUI4h4b+UTumqR/HhiNTFRobUGiwuvrP4CWHj3dtQ==\r\n'
),
],
['ARC-Authentication-Results', ' i=1; dkimpy.example.com'],
['Content-Type', ' text/plain; charset="us-ascii"'],
['MIME-Version', ' 1.0'],
['Content-Transfer-Encoding', ' 7bit'],
['Subject', ' test message from dkimpy'],
['From', ' [email protected]'],
['To', ' [email protected]'],
]
res = run_miltertest(headers, False, 'test message\r\n')
assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']
assert res['headers'][1][0] == 'ARC-Seal'
assert 'cv=pass' in res['headers'][1][1]
assert res['headers'][2][0] == 'ARC-Message-Signature'
assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']


def test_milter_canon_simple(run_miltertest):
"""Sign a message with simple canonicalization and then verify it"""
res = run_miltertest()
Expand Down

0 comments on commit 0675ae1

Please sign in to comment.