-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CI: add live tests for dkimpy and Mail::DKIM interop
- Loading branch information
1 parent
b87826b
commit 0675ae1
Showing
6 changed files
with
147 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 = [ | ||
|
@@ -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') | ||
|
@@ -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__], | ||
] | ||
|
@@ -218,6 +212,8 @@ def _run_miltertest( | |
|
||
return { | ||
'headers': ins_headers, | ||
'msg_headers': headers, | ||
'msg_body': body, | ||
} | ||
|
||
return _run_miltertest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[ | ||
{ | ||
"Canonicalization": "relaxed/simple" | ||
}, | ||
{ | ||
"Canonicalization": "relaxed/relaxed" | ||
}, | ||
{ | ||
"Canonicalization": "simple/relaxed" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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() | ||
|