Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-dkimpy for openSUSE:Factory checked in at 2024-01-06 19:38:04 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-dkimpy (Old) and /work/SRC/openSUSE:Factory/.python-dkimpy.new.28375 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-dkimpy" Sat Jan 6 19:38:04 2024 rev:6 rq:1137284 version:1.1.5 Changes: -------- --- /work/SRC/openSUSE:Factory/python-dkimpy/python-dkimpy.changes 2023-06-18 23:08:28.189650071 +0200 +++ /work/SRC/openSUSE:Factory/.python-dkimpy.new.28375/python-dkimpy.changes 2024-01-06 19:38:05.602285235 +0100 @@ -1,0 +2,44 @@ +Sat Jan 6 18:02:16 UTC 2024 - Dirk Müller <dmuel...@suse.com> + +- update to 1.1.5: + * Use dns.resolver.resolve instead of dns.resolver.query + due to deprecation + * Treat dns.resolver.NoNameservers like NXDOMAIN (not an + error) + * Confine errors from dnspython to dnsplug and use dkim + errors, since dkim.__init__.py doesn't import dns and + needs dkim errors + * Catch nacl.exceptions.ValueError and raise + KeyFormatError, similar to how RSA key errors are treated + * Create ed25519 key files with secure permissions to + avoid risk of insecure chmode call/race condition + * Properly cleanup temporary directories in tests + * Verify correct AMS header is used for ARC seal + verification (André Cruz) + * Document dropping of Python 2 support + * Fix traceback when attempting to verify an unsigned + message using async verify + * Add domain validity check for ascii domains (no + specials) + * Add option to specify index number of signature to + verify to dkimverify + * Correct signature indexing error introduced in 1.0.0 + that prevents verification of multiple signatures in a + single message + * Correct dkim.verify processing to avoid errors when + verifying messages with no DKIM signatures + * Update dnsplug for DNS Python (dns) 2.0 compatibility + * Provide more specific error message when ed25519 + private key is invalid + * Add support for PKCS#8 for private keys, openssl 3 + default + * Add limitations section to README to document current + IDN status + * Add USE_ASYNC flag to allow async to be disabled when + aiodns is installed (LP: #1954331) + * Add new dkim.DnsTimeoutError class to report queried + domain and selector along with timeout error from dnspython + * Invalid Authentication-Results header fields are + ignored for ARC signing + +------------------------------------------------------------------- Old: ---- dkimpy-1.0.5.tar.gz New: ---- dkimpy-1.1.5.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-dkimpy.spec ++++++ --- /var/tmp/diff_new_pack.bq8BcV/_old 2024-01-06 19:38:07.162342240 +0100 +++ /var/tmp/diff_new_pack.bq8BcV/_new 2024-01-06 19:38:07.174342678 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-dkimpy # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %{?sle15_python_module_pythons} %define commands arcsign arcverify dkimsign dkimverify dknewkey Name: python-dkimpy -Version: 1.0.5 +Version: 1.1.5 Release: 0 Summary: DKIM (DomainKeys Identified Mail) License: BSD-2-Clause @@ -28,6 +28,7 @@ Patch0: no-optional.patch BuildRequires: %{python_module setuptools} BuildRequires: fdupes +BuildRequires: openssl BuildRequires: python-rpm-macros Requires: python-PyNaCl Requires: python-authres ++++++ dkimpy-1.0.5.tar.gz -> dkimpy-1.1.5.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/ChangeLog new/dkimpy-1.1.5/ChangeLog --- old/dkimpy-1.0.5/ChangeLog 2020-08-09 04:34:58.000000000 +0200 +++ new/dkimpy-1.1.5/ChangeLog 2023-07-28 17:57:20.000000000 +0200 @@ -1,14 +1,56 @@ -2020-08-08 Version 1.0.5 - - Update dnsplug for DNS Python (dns) 2.0 compatibility (LP: #1888583) - - Fix @param srv_id typos (LP: #1890532) +2023-07-28 Version 1.1.5 + - Use dns.resolver.resolve instead of dns.resolver.query due to deprecation + (LP: 2028783) - Thanks to Pedro Vicente for the report and the fix -2020-04-06 Version 1.0.4 - - Correct dkim.verify processing to avoid errors when verifying messages - with no DKIM signatures +2023-05-12 Version 1.1.4 + - Treat dns.resolver.NoNameservers like NXDOMAIN (not an error) (Thanks to + David for the patch and the report) + - Confine errors from dnspython to dnsplug and use dkim errors, since + dkim.__init__.py doesn't import dns and needs dkim errors (LP: #2018646) + +2023-04-30 Version 1.1.3 + - Catch nacl.exceptions.ValueError and raise KeyFormatError, similar to how + RSA key errors are treated (LP: #2018021) + - Create ed25519 key files with secure permissions to avoid risk of + insecure chmode call/race condition (Thanks to Hanno Böck for the report + and the suggested fix) (LP: #2017430) + - Properly cleanup temporary directories in tests + +2023-04-09 Version 1.1.2 + - Verify correct AMS header is used for ARC seal verification (André Cruz) -2020-01-15 Version 1.0.3 +2023-03-09 Version 1.1.1 + - Document dropping of Python 2 support (dropped as of 1.1.0) (LP: + #20086738) + - Fix traceback when attempting to verify an unsigned message using + async verify (Thanks to Nikita Sychev for the report and a suggested + fix) (LP: #2008723) + +2023-02-25 Version 1.1.0 + - Add domain validity check for ascii domains (no specials) + - Add option to specify index number of signature to verify to dkimverify + (Thanks to Nick Baugh for the change) - Correct signature indexing error introduced in 1.0.0 that prevents verification of multiple signatures in a single message + - Correct dkim.verify processing to avoid errors when verifying messages + with no DKIM signatures + - Update dnsplug for DNS Python (dns) 2.0 compatibility (LP: #1888583) + - Fix @param srv_id typos (LP: #1890532) + - Provide more specific error message when ed25519 private key is invalid + (See LP 1901569 for background) + - Add support for PKCS#8 for private keys, openssl 3 default (LP: + #1978835) - Thanks to Adrien (spitap) for the change + - Add limitations section to README to document current IDN status + - Add USE_ASYNC flag to allow async to be disabled when aiodns is + installed (LP: #1954331) - see README.md for details + - Add new dkim.DnsTimeoutError class to report queried domain and selector + along with timeout error from dnspython (LP: #1873449) + - Invalid Authentication-Results header fields are ignored for ARC signing + (LP: #1884044) + - Correct base64 validation regexp so that valid signature with == split + between two lines are not incorrectly evaluated as invalid (LP: + #2002295) - Thanks to <https://launchpad.net/~obadz> for the report and + the proposed fix 2019-12-31 Version 1.0.2 - dknewkey: On posix operating systems set file permissions to 600 for diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/PKG-INFO new/dkimpy-1.1.5/PKG-INFO --- old/dkimpy-1.0.5/PKG-INFO 2020-08-09 04:35:02.000000000 +0200 +++ new/dkimpy-1.1.5/PKG-INFO 2023-07-28 18:02:28.864313000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: dkimpy -Version: 1.0.5 +Version: 1.1.5 Summary: DKIM (DomainKeys Identified Mail), ARC (Authenticated Receive Chain), and TLSRPT (TLS Report) email signing and verification Home-page: https://launchpad.net/dkimpy Author: Scott Kitterman @@ -21,7 +21,7 @@ # VERSION - This is dkimpy 1.0.5. + This is dkimpy 1.1.5. # REQUIREMENTS @@ -32,12 +32,10 @@ Similarly, extras_requires feature 'asyncio' will add the extra dependencies needed for asyncio. - - Python 2.x >= 2.7, or Python 3.x >= 3.5. Recent versions have not been - tested on python < 2.7 or python3 < 3.4, but may still work on python2.6 - and python 3.1 - 3.3. - - dnspython or pydns. dnspython is preferred if both are present and + - Python 3.x >= 3.5. Recent versions have not been on python3 < 3.4, but + may still work on earlier python3 versions. + - dnspython or py3dns. dnspython is preferred if both are present and installed to satisfy the DNS module requirement if neither are installed. - - argparse. Standard library in python2.7 and later. - authres. Needed for ARC. - PyNaCl. Needed for use of ed25519 capability. - aiodns. Needed for asycnio (Requires python3.5 or later) @@ -91,8 +89,10 @@ ships with test runners for dkimpy. After downloading the test suite, you can run the signing and validation tests like this: - ```python2.7 ./testarc.py sign runners/arcsigntest.py``` - ```python2.7 ./testarc.py validate runners/arcverifytest.py``` + ```python3 ./testarc.py sign runners/arcsigntest.py``` + ```python3 ./testarc.py validate runners/arcverifytest.py``` + + As ov version 1.1.0, python2.7 is no longer supported. # USAGE @@ -160,7 +160,10 @@ dknewkey is s script that produces private and public key pairs suitable for use with DKIM. Note that the private key file format used for ed25519 is - not standardized (there is no standard) and is unique to dkimpy. + not standardized (there is no standard) and is unique to dkimpy. Creation of + keys should be done in a secure environment. If an unauthorized entity gains + access to current private keys they can generate signed email that will pass + DKIM checkes and will be difficult to repudiate. dkimsign is a filter that reads an RFC822 message on standard input, and writes the same message on standard output with a DKIM-Signature line @@ -184,6 +187,9 @@ In addition to arcsign and arcverify, the dkim module now provides arc_sign and arc_verify functions as well as an ARC class. + If an invalid authentication results header field is included in the set for + ARC, it is ignored and no error is raised. + Both DKIM ed25519 and ARC are now considered stable (no longer experimantal). ## ASYNC SUPPORT @@ -205,6 +211,9 @@ This feature requires python3.5 or newer. + If aiodns is available, the async functions will be used. To avoide async + when aiodns is availale, set dkim.USE_ASYNC = False. + ## TLSRPT (TLS Report) As of version 1.0, the RFC 8460 tlsrpt service type is supported: @@ -224,6 +233,13 @@ valid. If set to tlsrpt=True, the service type is not required, but other RFC 8460 requirements are applied. + # LIMITATIONS + + Dkimpy will correctly sign/verify messages with ASCII or UTF-8 content. + Messages that contain other types of content will not verify correctly. It + does not yet implement RFC 8616, Email Authentication for Internationalized + Mail. + # FEEDBACK Bug reports may be submitted to the bug tracker for the dkimpy project on @@ -236,7 +252,6 @@ Classifier: License :: DFSG approved Classifier: Natural Language :: English Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Communications :: Email :: Mail Transport Agents Classifier: Topic :: Communications :: Email :: Filters diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/README.md new/dkimpy-1.1.5/README.md --- old/dkimpy-1.0.5/README.md 2020-08-08 23:04:57.000000000 +0200 +++ new/dkimpy-1.1.5/README.md 2023-07-28 17:59:27.000000000 +0200 @@ -13,7 +13,7 @@ # VERSION -This is dkimpy 1.0.5. +This is dkimpy 1.1.5. # REQUIREMENTS @@ -24,12 +24,10 @@ Similarly, extras_requires feature 'asyncio' will add the extra dependencies needed for asyncio. - - Python 2.x >= 2.7, or Python 3.x >= 3.5. Recent versions have not been - tested on python < 2.7 or python3 < 3.4, but may still work on python2.6 - and python 3.1 - 3.3. - - dnspython or pydns. dnspython is preferred if both are present and + - Python 3.x >= 3.5. Recent versions have not been on python3 < 3.4, but + may still work on earlier python3 versions. + - dnspython or py3dns. dnspython is preferred if both are present and installed to satisfy the DNS module requirement if neither are installed. - - argparse. Standard library in python2.7 and later. - authres. Needed for ARC. - PyNaCl. Needed for use of ed25519 capability. - aiodns. Needed for asycnio (Requires python3.5 or later) @@ -83,8 +81,10 @@ ships with test runners for dkimpy. After downloading the test suite, you can run the signing and validation tests like this: -```python2.7 ./testarc.py sign runners/arcsigntest.py``` -```python2.7 ./testarc.py validate runners/arcverifytest.py``` +```python3 ./testarc.py sign runners/arcsigntest.py``` +```python3 ./testarc.py validate runners/arcverifytest.py``` + +As ov version 1.1.0, python2.7 is no longer supported. # USAGE @@ -152,7 +152,10 @@ dknewkey is s script that produces private and public key pairs suitable for use with DKIM. Note that the private key file format used for ed25519 is -not standardized (there is no standard) and is unique to dkimpy. +not standardized (there is no standard) and is unique to dkimpy. Creation of +keys should be done in a secure environment. If an unauthorized entity gains +access to current private keys they can generate signed email that will pass +DKIM checkes and will be difficult to repudiate. dkimsign is a filter that reads an RFC822 message on standard input, and writes the same message on standard output with a DKIM-Signature line @@ -176,6 +179,9 @@ In addition to arcsign and arcverify, the dkim module now provides arc_sign and arc_verify functions as well as an ARC class. +If an invalid authentication results header field is included in the set for +ARC, it is ignored and no error is raised. + Both DKIM ed25519 and ARC are now considered stable (no longer experimantal). ## ASYNC SUPPORT @@ -197,6 +203,9 @@ This feature requires python3.5 or newer. +If aiodns is available, the async functions will be used. To avoide async +when aiodns is availale, set dkim.USE_ASYNC = False. + ## TLSRPT (TLS Report) As of version 1.0, the RFC 8460 tlsrpt service type is supported: @@ -216,6 +225,13 @@ valid. If set to tlsrpt=True, the service type is not required, but other RFC 8460 requirements are applied. +# LIMITATIONS + +Dkimpy will correctly sign/verify messages with ASCII or UTF-8 content. +Messages that contain other types of content will not verify correctly. It +does not yet implement RFC 8616, Email Authentication for Internationalized +Mail. + # FEEDBACK Bug reports may be submitted to the bug tracker for the dkimpy project on diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/__init__.py new/dkimpy-1.1.5/dkim/__init__.py --- old/dkimpy-1.0.5/dkim/__init__.py 2020-08-08 23:05:10.000000000 +0200 +++ new/dkimpy-1.1.5/dkim/__init__.py 2023-05-12 07:08:45.000000000 +0200 @@ -40,9 +40,12 @@ import time import binascii +# Set to False to not use async functions even though aiodns is installed. +USE_ASYNC = True + # only needed for arc try: - from authres import AuthenticationResultsHeader + import authres except ImportError: pass @@ -72,13 +75,17 @@ try: from dkim.dnsplug import get_txt except ImportError: - try: - import aiodns - from dkim.asyncsupport import get_txt_async as get_txt - except: - # Only true if not using async - def get_txt(s,timeout=5): - raise RuntimeError("DKIM.verify requires DNS or dnspython module") + if USE_ASYNC: + try: + import aiodns + from dkim.asyncsupport import get_txt_async as get_txt + except: + # Only true if not using async + def get_txt(s,timeout=5): + raise RuntimeError("DKIM.verify requires DNS or dnspython module") + else: + raise RuntimeError("DKIM.verify requires DNS or dnspython module") + from dkim.util import ( get_default_logger, InvalidTagValueList, @@ -94,6 +101,8 @@ "ValidationError", "AuthresNotFoundError", "NaClNotFoundError", + "DnsTimeoutError", + "USE_ASYNC", "CV_Pass", "CV_Fail", "CV_None", @@ -187,6 +196,9 @@ class UnknownKeyTypeError(DKIMException): """ Key type (k tag) is not known (rsa/ed25519) """ +class DnsTimeoutError(DKIMException): + """ DNS query for public key timed out """ + def select_headers(headers, include_headers): """Select message header fields to be signed/verified. @@ -272,13 +284,13 @@ raise ValidationError("unknown signature algorithm: %s" % sig[b'a']) if b'b' in sig: - if re.match(br"[\s0-9A-Za-z+/]+=*$", sig[b'b']) is None: + if re.match(br"[\s0-9A-Za-z+/]+[\s=]*$", sig[b'b']) is None: raise ValidationError("b= value is not valid base64 (%s)" % sig[b'b']) if len(re.sub(br"\s+", b"", sig[b'b'])) % 4 != 0: raise ValidationError("b= value is not valid base64 (%s)" % sig[b'b']) if b'bh' in sig: - if re.match(br"[\s0-9A-Za-z+/]+=*$", sig[b'bh']) is None: + if re.match(br"[\s0-9A-Za-z+/]+[\s=]*$", sig[b'b']) is None: raise ValidationError("bh= value is not valid base64 (%s)" % sig[b'bh']) if len(re.sub(br"\s+", b"", sig[b'bh'])) % 4 != 0: raise ValidationError("bh= value is not valid base64 (%s)" % sig[b'bh']) @@ -286,6 +298,16 @@ if b'cv' in sig and sig[b'cv'] not in (CV_Pass, CV_Fail, CV_None): raise ValidationError("cv= value is not valid (%s)" % sig[b'cv']) + # Limit domain validation to ASCII domains because too hard + try: + str(sig[b'd'], 'ascii') + # No specials, which is close enough + if re.findall(b"[\(\)<>\[\]:;@\\,]", sig[b'd']): + raise ValidationError("d= value is not valid (%s)" % sig[b'd']) + except UnicodeDecodeError as e: + # Not an ASCII domain + pass + # Nasty hack to support both str and bytes... check for both the # character and integer values. if not arc and b'i' in sig and ( @@ -446,6 +468,8 @@ pk = nacl.signing.VerifyKey(pub[b'p'], encoder=nacl.encoding.Base64Encoder) except NameError: raise NaClNotFoundError('pynacl module required for ed25519 signing, see README.md') + except nacl.exceptions.ValueError as e: + raise KeyFormatError("could not parse ed25519 public key (%s): %s" % (pub[b'p'],e)) keysize = 256 ktag = b'ed25519' except KeyError: @@ -455,9 +479,9 @@ pk = parse_public_key(base64.b64decode(pub[b'p'])) keysize = bitsize(pk['modulus']) except KeyError: - raise KeyFormatError("incomplete public key: %s" % s) + raise KeyFormatError("incomplete RSA public key: %s" % s) except (TypeError,UnparsableKeyError) as e: - raise KeyFormatError("could not parse public key (%s): %s" % (pub[b'p'],e)) + raise KeyFormatError("could not parse RSA public key (%s): %s" % (pub[b'p'],e)) ktag = b'rsa' if pub[b'k'] != b'rsa' and pub[b'k'] != b'ed25519': raise KeyFormatError('unknown algorithm in k= tag: {0}'.format(pub[b'k'])) @@ -778,6 +802,10 @@ except binascii.Error as e: self.logger.error('KeyFormatError: {0}'.format(e)) return False + except DnsTimeoutError as e: + self.logger.error('DnsTimeoutError: Domain: {0} Selector: {1} Error message: {2}'.format( + sig[b'd'], sig[b's'], e)) + return False return self.verify_sig_process(sig, include_headers, sig_header, dnsfunc) @@ -832,6 +860,8 @@ pk = nacl.signing.SigningKey(privkey, encoder=nacl.encoding.Base64Encoder) except NameError: raise NaClNotFoundError('pynacl module required for ed25519 signing, see README.md') + except nacl.exceptions.ValueError: + raise KeyFormatError('invalid ed25519 private key or format') if identity is not None and not identity.endswith(domain): raise ParameterError("identity must end with domain") @@ -1009,10 +1039,10 @@ self.add_should_not(('Authentication-Results',)) # check if authres has been imported try: - AuthenticationResultsHeader + authres.AuthenticationResultsHeader except: self.logger.debug("authres package not installed") - raise AuthresNotFoundError + raise authres.AuthresNotFoundError try: pk = parse_pem_private_key(privkey) @@ -1021,8 +1051,14 @@ # extract, parse, filter & group AR headers ar_headers = [res.strip() for [ar, res] in self.headers if ar == b'Authentication-Results'] - grouped_headers = [(res, AuthenticationResultsHeader.parse('Authentication-Results: ' + res.decode('utf-8'))) - for res in ar_headers] + + grouped_headers = [] + for res in ar_headers: + try: # see LP: #1884044 + grouped_headers.append((res, authres.AuthenticationResultsHeader.parse('Authentication-Results: ' + res.decode('utf-8')))) + except authres.core.SyntaxError: + # Skip over invalid AR header fields + pass auth_headers = [res for res in grouped_headers if res[1].authserv_id == srv_id.decode('utf-8')] if len(auth_headers) == 0: @@ -1036,7 +1072,7 @@ auth_results = srv_id + b'; ' + (b';' + self.linesep + b' ').join(results) # extract cv - parsed_auth_results = AuthenticationResultsHeader.parse('Authentication-Results: ' + auth_results.decode('utf-8')) + parsed_auth_results = authres.AuthenticationResultsHeader.parse('Authentication-Results: ' + auth_results.decode('utf-8')) arc_results = [res for res in parsed_auth_results.results if res.method == 'arc'] if len(arc_results) == 0: chain_validation_status = CV_None @@ -1259,7 +1295,9 @@ # we can't use the AMS provided above, as it's already been canonicalized relaxed # for use in validating the AS. However the AMS is included in the AMS itself, # and this can use simple canonicalization - raw_ams_header = [(x, y) for (x, y) in self.headers if x.lower() == b'arc-message-signature'][0] + raw_ams_header = [ + (x, y) for (x, y) in self.headers if x.lower() == b'arc-message-signature' and b" i="+sig[b'i']+b";" in y.lower() + ][0] # Only relaxed canonicalization used by ARC if b'c' not in sig: @@ -1357,7 +1395,7 @@ # aiodns requires Python 3.5+, so no async before that -if sys.version_info >= (3, 5): +if sys.version_info >= (3, 5) and USE_ASYNC: try: import aiodns from dkim.asyncsupport import verify_async diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/asn1.py new/dkimpy-1.1.5/dkim/asn1.py --- old/dkimpy-1.0.5/dkim/asn1.py 2020-04-06 06:06:13.000000000 +0200 +++ new/dkimpy-1.1.5/dkim/asn1.py 2023-04-30 16:18:48.000000000 +0200 @@ -84,6 +84,9 @@ elif tag == SEQUENCE: r.append(asn1_parse(t[1], data[i:i+length])) i += length + elif tag == OCTET_STRING: + r.append(data[i:i+length]) + i += length else: raise ASN1FormatError( "Unexpected tag in template: %02x" % tag) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/asyncsupport.py new/dkimpy-1.1.5/dkim/asyncsupport.py --- old/dkimpy-1.0.5/dkim/asyncsupport.py 2020-04-06 06:27:45.000000000 +0200 +++ new/dkimpy-1.1.5/dkim/asyncsupport.py 2023-04-30 15:58:34.000000000 +0200 @@ -94,8 +94,11 @@ async def verify(self,idx=0,dnsfunc=get_txt_async): - sig, include_headers, sigheaders = self.verify_headerprep(idx=0) - return await self.verify_sig(sig, include_headers, sigheaders[idx], dnsfunc) + prep = self.verify_headerprep(idx) + if prep: + sig, include_headers, sigheaders = prep + return await self.verify_sig(sig, include_headers, sigheaders[idx], dnsfunc) + return False # No signature async def verify_async(message, logger=None, dnsfunc=None, minkey=1024, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/crypto.py new/dkimpy-1.1.5/dkim/crypto.py --- old/dkimpy-1.0.5/dkim/crypto.py 2020-04-06 06:27:45.000000000 +0200 +++ new/dkimpy-1.1.5/dkim/crypto.py 2023-04-30 16:18:48.000000000 +0200 @@ -48,6 +48,13 @@ NULL, ) +ASN1_PKCS8_PrivateKey = [ + (SEQUENCE, [ + (INTEGER,), + (SEQUENCE, [ (OBJECT_IDENTIFIER,), (NULL,),]), + (OCTET_STRING,), + ]) +] ASN1_Object = [ (SEQUENCE, [ @@ -133,13 +140,19 @@ def parse_private_key(data): """Parse an RSA private key. - @param data: DER-encoded RFC8017 RSAPrivateKey. + @param data: DER-encoded RFC8017 RSAPrivateKey or CMS-encoded RFC5958 asymetric key package. @return: RSA private key """ try: pka = asn1_parse(ASN1_RSAPrivateKey, data) - except ASN1FormatError as e: - raise UnparsableKeyError('Unparsable private key: ' + str(e)) + except ASN1FormatError: + try: + #If it fails it might be because of PKCS#8 (key generated with openSSL 3.X) + pkt = asn1_parse(ASN1_PKCS8_PrivateKey, data) + pka = asn1_parse(ASN1_RSAPrivateKey, pkt[0][2]) + except ASN1FormatError as e: + raise UnparsableKeyError("Unparsable private key (are your sure it is encoded in PKCS#1 or PKCS#8 standard ?):\n" + str(e)) + pk = { 'version': pka[0][0], 'modulus': pka[0][1], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/dkimverify.py new/dkimpy-1.1.5/dkim/dkimverify.py --- old/dkimpy-1.0.5/dkim/dkimverify.py 2020-08-08 23:04:57.000000000 +0200 +++ new/dkimpy-1.1.5/dkim/dkimverify.py 2023-04-30 16:18:48.000000000 +0200 @@ -24,11 +24,17 @@ from __future__ import print_function import sys +import argparse import dkim - def main(): + parser = argparse.ArgumentParser( + description='Verify DKIM signature for email messages.', + epilog="message to be verified follows commands on stdin") + parser.add_argument('--index', metavar='N', type=int, default=0, + help='Index of DKIM signature header to verify: default=0') + args=parser.parse_args() if sys.version_info[0] >= 3: # Make sys.stdin a binary stream. sys.stdin = sys.stdin.detach() @@ -38,9 +44,9 @@ if verbose: import logging d = dkim.DKIM(message, logger=logging) - res = d.verify() else: - res = dkim.verify(message) + d = dkim.DKIM(message) + res = d.verify(args.index) if not res: print("signature verification failed") sys.exit(1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/dknewkey.py new/dkimpy-1.1.5/dkim/dknewkey.py --- old/dkimpy-1.0.5/dkim/dknewkey.py 2020-04-06 06:06:13.000000000 +0200 +++ new/dkimpy-1.1.5/dkim/dknewkey.py 2023-04-30 16:18:48.000000000 +0200 @@ -43,15 +43,17 @@ def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) -def GenRSAKeys(private_key_file): +def GenRSAKeys(private_key_file, verbose=True): """ Generates a suitable private key. Output is unprotected. You should encrypt your keys. """ - eprint('generating ' + private_key_file) + if verbose: + eprint('generating ' + private_key_file) subprocess.check_call([OPENSSL_BINARY, 'genrsa', '-out', private_key_file, - str(BITS_REQUIRED)]) + str(BITS_REQUIRED)], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) -def GenEd25519Keys(private_key_file): +def GenEd25519Keys(private_key_file, verbose=True): """Generates a base64 encoded private key for ed25519 DKIM signing. Output is unprotected. You should protect your keys. """ @@ -59,21 +61,26 @@ import nacl.encoding import os skg = nacl.signing.SigningKey(seed=os.urandom(32)) - eprint('generating ' + private_key_file) + if verbose: + eprint('generating ' + private_key_file) priv_key = skg.generate() + if os.name == 'posix': + old_umask = os.umask(0o077) with open(private_key_file, 'w') as pkf: pkf.write(priv_key.encode(encoder=nacl.encoding.Base64Encoder).decode("utf-8")) if os.name == 'posix': - os.chmod(private_key_file, 0o600) + os.umask(old_umask) return(priv_key) -def ExtractRSADnsPublicKey(private_key_file, dns_file): +def ExtractRSADnsPublicKey(private_key_file, dns_file, verbose=True): """ Given a key, extract the bit we should place in DNS. """ - eprint('extracting ' + private_key_file) + if verbose: + eprint('extracting ' + private_key_file) working_file = tempfile.NamedTemporaryFile(delete=False).name subprocess.check_call([OPENSSL_BINARY, 'rsa', '-in', private_key_file, - '-out', working_file, '-pubout', '-outform', 'PEM']) + '-out', working_file, '-pubout', '-outform', 'PEM'], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) try: with open(working_file) as wf: y = '' @@ -84,17 +91,19 @@ finally: os.unlink(working_file) with open(dns_file, 'w') as dns_fp: - eprint('writing ' + dns_file) + if verbose: + eprint('writing ' + dns_file) dns_fp.write("v=DKIM1; k=rsa; h=sha256; p={0}".format(output)) -def ExtractEd25519PublicKey(dns_file, priv_key): +def ExtractEd25519PublicKey(dns_file, priv_key, verbose=True): """ Given a ed25519 key, extract the bit we should place in DNS. """ import nacl.encoding # Yes, pep-8, but let's not make everyone install nacl pubkey = priv_key.verify_key output = pubkey.encode(encoder=nacl.encoding.Base64Encoder).decode("utf-8") with open(dns_file, 'w') as dns_fp: - eprint('writing ' + dns_file) + if verbose: + eprint('writing ' + dns_file) dns_fp.write("v=DKIM1; k=ed25519; p={0}".format(output)) def main(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/dnsplug.py new/dkimpy-1.1.5/dkim/dnsplug.py --- old/dkimpy-1.0.5/dkim/dnsplug.py 2020-08-08 22:55:43.000000000 +0200 +++ new/dkimpy-1.1.5/dkim/dnsplug.py 2023-07-28 17:54:58.000000000 +0200 @@ -27,12 +27,18 @@ def get_txt_dnspython(name, timeout=5): """Return a TXT record associated with a DNS name.""" + import dkim try: - a = dns.resolver.query(name, dns.rdatatype.TXT,raise_on_no_answer=False, lifetime=timeout) + a = dns.resolver.resolve(name, dns.rdatatype.TXT,raise_on_no_answer=False, lifetime=timeout, search=True) for r in a.response.answer: if r.rdtype == dns.rdatatype.TXT: return b"".join(list(r.items)[0].strings) except dns.resolver.NXDOMAIN: pass + except dns.resolver.NoNameservers: pass + except dns.resolver.NoResolverConfiguration as e: + raise dkim.DnsTimeoutError('dns.resolver.NoResolverConfiguration: {0}'.format(e)) + except dns.exception.Timeout as e: + raise dkim.DnsTimeoutError('dns.exception.Timeout: {0}'.format(e)) return None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/tests/__init__.py new/dkimpy-1.1.5/dkim/tests/__init__.py --- old/dkimpy-1.0.5/dkim/tests/__init__.py 2020-04-06 06:27:45.000000000 +0200 +++ new/dkimpy-1.1.5/dkim/tests/__init__.py 2023-04-30 16:18:48.000000000 +0200 @@ -35,6 +35,7 @@ test_util, test_arc, test_dnsplug, + test_dkim_generate, ) modules = [ test_canonicalization, @@ -46,6 +47,7 @@ test_util, test_arc, test_dnsplug, + test_dkim_generate, ] suites = [x.test_suite() for x in modules] return unittest.TestSuite(suites) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/tests/data/2048_testkey_PKCS8.key new/dkimpy-1.1.5/dkim/tests/data/2048_testkey_PKCS8.key --- old/dkimpy-1.0.5/dkim/tests/data/2048_testkey_PKCS8.key 1970-01-01 01:00:00.000000000 +0100 +++ new/dkimpy-1.1.5/dkim/tests/data/2048_testkey_PKCS8.key 2023-04-30 16:18:48.000000000 +0200 @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDYMGhEok34XiUg +szO7cuYph/dw0lliI18+wwX0ybUNwjkmc89jfRNZ6OEF5BfuyMYAoHB96D9EhIAI +qXzjmvFtjZIrYTyzb2X/4Cml7uxGLiekSFjrANL1mybg36FKeMAV59s078++IrcQ +Trr1E6e7q127/YRwCGy+KZQ0Sl2CrL+IdPuCK5WAJtYT1zQ/h3PQ/xUcHLnUtem5 +BOirg7sweoxdfZWsXu7WeGDYiFOOV5pMmdueFfun5CJe8MfdEJ4YsXqxCxa2yqrN +klkX4RSphEfwreU/sv0DsWj/eaWmFDhagvd2AwixId/lFYGsY2X7G6bs/SqThoBk +9GftRGihAgMBAAECggEAYiHjCpiUBPQTLVs61dEridmWp8dL3IDK6K3VA88VmMe7 +cmlqT7JEOPE9R5PIi1Lmkf1B4t0r7tmoVoY80wIPqhdzrK5IQ/kCl1n0/cXMyXSE ++Q0AE7h9ihAh3zyTtb7HDop+1fIvXhLa/xOFyN5hqo34j+9dkQ858T3lcLD67mfO +1/lpBhwEeF/7+wg2MiwM/4Rd217ReUgdgXTKZ8DYtLr+TFagv/8E8zAgHVqnklHf +yEGR5+O94WYpqbanagfivxmogHsW6Eu5Ub/RJuWj06AvhRQSkPgVV5pl1zv0EP2w +vhAl2YVzorRJDEzclmP+PyTzp+sPbrIqywof+K89WwKBgQDddD6eqISrWC4Vo2CZ +WvBetiC48KKKjWwcFGERHGYqrmZMTDVYKqSUO7PvcgHIVyElYnlcqJWqyyQDBhy/ +ShOZjQ0oy9SblZMoVjLYONS0iFDoz2of8/kvY+kuzh+3iU9Qg6BI+lfRSd8KjGBB +xsLOoUhzQceXqmx5SmOGIn6EmwKBgQD56efYqmYj/GGLz/gyfyElsQFUWG0tyNjm +ujufTxjhEDWw3pnS3vUVG/gleGJH7f9vzCfEI4WlBuZUidrwM64cnF4QXQwuhT7p +5U+nKNVfJpMeglehgVl6vSi/L1ovilCNo5A4aQJxyS73KTcU1UAZ4/VZnrvYrV/f +ysxWTrx1cwKBgDAAyKofoVJ69NJf7cqQOdZt6D3ue21JJowXpsrMuyC5WRdk1ZNc ++vvezSw0LEq/CEJQTDpXmMnC6vV017pnVkRMnPOg618mVxXBSZgxCXpwqgktHLX8 +bqFlKOCqcZmZPAYZ4h6vlWWae6yPrTXU3dlogInrUlZ/7K+F/njO9VnNAoGBAOvp +QsPDruGfd9GMQ2YfngG/glrFkmKa6y16dZfgCcNDEvvgVeK6Ny5zFZ8Bcf0mjG9T +j+JWCe2LgtggvfzrPBuj/COEQmCTxZzzq2pHYIwOlOhC8Ef0G6yCbbl0ELU54uqh +kR2++uDAokYMsQNIftcx2kR8VCSpHQzbmmKKttpDAoGBAIGVTOJtRi3nvmz1q3Zy +vad1gTrkyy/gtjuE+8KNKhWwnC+MYXsSOykh+x6GQQ1gHRjk6GMsQxwl3xbHvzdG +yp7Fc3aWkJbARSINOcAblH8JWdCMGBY/FlSogl0ptROVFxkyM2DnchM1bSsJ9wvf +xB4hqm1VSknnOCCK+NekIC48 +-----END PRIVATE KEY----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/tests/data/2048_testkey_PKCS8.key.pub.txt new/dkimpy-1.1.5/dkim/tests/data/2048_testkey_PKCS8.key.pub.txt --- old/dkimpy-1.0.5/dkim/tests/data/2048_testkey_PKCS8.key.pub.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/dkimpy-1.1.5/dkim/tests/data/2048_testkey_PKCS8.key.pub.txt 2023-04-30 16:18:48.000000000 +0200 @@ -0,0 +1 @@ +v=DKIM1; g=*; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2DBoRKJN+F4lILMzu3LmKYf3cNJZYiNfPsMF9Mm1DcI5JnPPY30TWejhBeQX7sjGAKBwfeg/RISACKl845rxbY2SK2E8s29l/+Appe7sRi4npEhY6wDS9Zsm4N+hSnjAFefbNO/PviK3EE669ROnu6tdu/2EcAhsvimUNEpdgqy/iHT7giuVgCbWE9c0P4dz0P8VHBy51LXpuQToq4O7MHqMXX2VrF7u1nhg2IhTjleaTJnbnhX7p+QiXvDH3RCeGLF6sQsWtsqqzZJZF+EUqYRH8K3lP7L9A7Fo/3mlphQ4WoL3dgMIsSHf5RWBrGNl+xum7P0qk4aAZPRn7URooQIDAQAB diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/tests/data/rfc6376.w1258.msg new/dkimpy-1.1.5/dkim/tests/data/rfc6376.w1258.msg --- old/dkimpy-1.0.5/dkim/tests/data/rfc6376.w1258.msg 1970-01-01 01:00:00.000000000 +0100 +++ new/dkimpy-1.1.5/dkim/tests/data/rfc6376.w1258.msg 2023-04-30 16:18:48.000000000 +0200 @@ -0,0 +1,12 @@ +From: Tomá? SixPack <Tomá?@football.example.com> +To: Suzie Q <su...@shopping.example.net> +Subject: Is dinner ready? +Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) +Message-ID: <20030712040037.46341.5...@football.example.com> + +Hi. + +We lost the game. Are you hungry yet? + +Tomá?. + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/tests/data/test.message.baddomain new/dkimpy-1.1.5/dkim/tests/data/test.message.baddomain --- old/dkimpy-1.0.5/dkim/tests/data/test.message.baddomain 1970-01-01 01:00:00.000000000 +0100 +++ new/dkimpy-1.1.5/dkim/tests/data/test.message.baddomain 2023-04-30 16:18:48.000000000 +0200 @@ -0,0 +1,16 @@ +DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; + d=legitimate.com(.attacker.com; i=@legitimate.com(.attacker.com; + q=dns/txt; s=test; t=1587514615; h=message-id : date : from : to : + subject : from; bh=wE7NXSkgnx9PGiavN4OZhJztvkqPDlemV3OGuEnLwNo=; + b=LsTV4fcR29N8CuUyrGn92jsTb67oAHx88vVIefoaUDghWxF5TpCyqcWbk/94Nt4PyxwUZ + pgzF4UM/zF1rclCeNm/V4m0wMj3X2eeOIUUa8GRQ0g7DzixiQ5qHLUGpRT4BHfPmdHZHYj8 + xv7+1O0/SJDK0YkaBjvhjDfkOoJhMmc= +Authentication-Results: lists.example.org; arc=none; spf=pass smtp.mfrom=jqd@d1.example; dkim=pass (1024-bit key) header.i=@d1.example; dmarc=pass +Received: from localhost +Message-ID: <exam...@example.com> +Date: Mon, 01 Jan 2011 01:02:03 +0400 +From: Test User <t...@example.com> +To: someb...@example.com +Subject: Testing + +This is a test message. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/tests/test_dkim.py new/dkimpy-1.1.5/dkim/tests/test_dkim.py --- old/dkimpy-1.0.5/dkim/tests/test_dkim.py 2020-08-08 23:04:57.000000000 +0200 +++ new/dkimpy-1.1.5/dkim/tests/test_dkim.py 2023-04-30 16:18:48.000000000 +0200 @@ -60,6 +60,8 @@ self.message3 = read_test_data("rfc6376.msg") self.message4 = read_test_data("rfc6376.signed.msg") self.message5 = read_test_data("rfc6376.signed.rsa.msg") + self.message6 = read_test_data("test.message.baddomain") + self.message7 = read_test_data("rfc6376.w1258.msg") self.key = read_test_data("test.private") self.rfckey = read_test_data("rfc8032_7_1.key") @@ -196,6 +198,23 @@ self.assertTrue(domain in _dns_responses,domain) return _dns_responses[domain] + def dnsfunc7(self, domain, timeout=5): + sample_dns = """\ +k=rsa; s=email;\ +p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANmBe10IgY+u7h3enWTukkqtUD5PR52T\ +b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ==""" + + _dns_responses = { + 'test._domainkey.legitimate.com(.attacker.com.': read_test_data("test.txt"), + } + try: + domain = domain.decode('ascii') + except UnicodeDecodeError: + return None + self.assertTrue(domain in _dns_responses,domain) + return _dns_responses[domain] + + def test_verifies(self): # A message verifies after being signed. for header_algo in (b"simple", b"relaxed"): @@ -231,6 +250,17 @@ d = dkim.DKIM(self.message4) res = d.verify(dnsfunc=self.dnsfunc5) self.assertTrue(res) + def test_non_utf8(self): + # A message with Windows-1258 encoding is signed and verifies. + for header_algo in (b"simple", b"relaxed"): + for body_algo in (b"simple", b"relaxed"): + sig = dkim.sign( + self.message7, b"test", b"football.example.com", self.key, + canonicalize=(header_algo, body_algo), signature_algorithm=b'rsa-sha256') + d = dkim.DKIM(self.message7) + res = d.verify(dnsfunc=self.dnsfunc5) + # As of 1.1.0 this won't verify, but at least we don't crash. FIXME + self.assertFalse(res) def test_catch_bad_key(self): # Raise correct error for defective public key. @@ -279,6 +309,18 @@ res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc4) self.assertFalse(res) + def test_invalid_domain_sign(self): + # RFC6376 says domain can be Alpha, Num, - only. + sig = dkim.sign( + self.message, b"test", b"legitimate.com(.attacker.com", self.key) + res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc7) + self.assertFalse(res) + + def test_invalid_domain_verify(self): + # RFC6376 says domain can be Alpha, Num, - only. + res = dkim.verify(self.message6, dnsfunc=self.dnsfunc7) + self.assertFalse(res) + def test_simple_signature(self): # A message verifies after being signed with SHOULD headers for header_algo in (b"simple", b"relaxed"): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/tests/test_dkim_generate.py new/dkimpy-1.1.5/dkim/tests/test_dkim_generate.py --- old/dkimpy-1.0.5/dkim/tests/test_dkim_generate.py 1970-01-01 01:00:00.000000000 +0100 +++ new/dkimpy-1.1.5/dkim/tests/test_dkim_generate.py 2023-04-30 16:18:48.000000000 +0200 @@ -0,0 +1,115 @@ +# This software is provided 'as-is', without any express or implied +# warranty. In no event will the author be held liable for any damages +# arising from the use of this software. +# +# Permission is granted to anyone to use this software for any purpose, +# including commercial applications, and to alter it and redistribute it +# freely, subject to the following restrictions: +# +# 1. The origin of this software must not be misrepresented; you must not +# claim that you wrote the original software. If you use this software +# in a product, an acknowledgment in the product documentation would be +# appreciated but is not required. +# 2. Altered source versions must be plainly marked as such, and must not be +# misrepresented as being the original software. +# 3. This notice may not be removed or altered from any source distribution. +# +# Copyright (c) 2011 William Grant <m...@williamgrant.id.au> +# Copyright (c) 2022 Adrien Precigout <d...@asdrip.fr> + +import os.path +import tempfile +import unittest + +import dkim +import dkim.dknewkey as dknewkey + +def read_data(path): + """Get the content of the given test data file.""" + + with open(path, 'rb') as f: + return f.read() + + +class TestSignAndVerify(unittest.TestCase): + """End-to-end signature and verification tests with a generated key.""" + + def setUp(self): + message_dir = os.path.join(os.path.dirname(__file__), 'data', "test.message") + self.message = read_data(message_dir) + self.ed25519_dns_key_file = "" + self.rsa_dns_key_file = "" + + + def test_generate_verifies_new_RSA_key(self): + #Create temporary dir + tmpdir = tempfile.TemporaryDirectory() + keydir = tmpdir.name + rsa_key_file = os.path.join(keydir, "dkim.rsa.key") + self.rsa_dns_key_file = os.path.join(keydir, "dkim.rsa.key.pub.txt") + #Generate a rsa key + dknewkey.GenRSAKeys(rsa_key_file, False) + dknewkey.ExtractRSADnsPublicKey(rsa_key_file, self.rsa_dns_key_file, False) + #Load the key + rsakey = read_data(rsa_key_file) + #Test signature with the newely generated key + for header_algo in (b"simple", b"relaxed"): + for body_algo in (b"simple", b"relaxed"): + sig = dkim.sign( + self.message, b"test", b"example.com", rsakey, + canonicalize=(header_algo, body_algo)) + res = dkim.verify(sig + self.message, dnsfunc=self.dnsfuncRSA) + self.assertTrue(res) + tmpdir.cleanup() + + + def test_generate_verifies_Ed25519_key(self): + #Create temporary dir + tmpdir = tempfile.TemporaryDirectory() + keydir = tmpdir.name + ed25519_key_file = os.path.join(keydir, "dkim.ed25519.key") + self.ed25519_dns_key_file = os.path.join(keydir, "dkim.ed25519.key.pub.txt") + #Generate a ed25519 key + pkt = dknewkey.GenEd25519Keys(ed25519_key_file, False) + dknewkey.ExtractEd25519PublicKey(self.ed25519_dns_key_file, pkt, False) + #Load the key + ed25519key = read_data(ed25519_key_file) + #Test signature with the newely generated key + for header_algo in (b"simple", b"relaxed"): + for body_algo in (b"simple", b"relaxed"): + sig = dkim.sign( + self.message, b"test1", b"example.com", ed25519key, + signature_algorithm=b'ed25519-sha256', + canonicalize=(header_algo, body_algo)) + res = dkim.verify(sig + self.message, dnsfunc=self.dnsfuncED25519) + self.assertTrue(res) + tmpdir.cleanup() + + + def dnsfuncRSA(self, domain, timeout=5): + _dns_responses = { + 'test._domainkey.example.com.': read_data(self.rsa_dns_key_file), + } + try: + domain = domain.decode('ascii') + except UnicodeDecodeError: + return None + self.assertTrue(domain in _dns_responses,domain) + return _dns_responses[domain] + + def dnsfuncED25519(self, domain, timeout=5): + _dns_responses = { + 'test1._domainkey.example.com.': read_data(self.ed25519_dns_key_file), + } + try: + domain = domain.decode('ascii') + except UnicodeDecodeError: + return None + self.assertTrue(domain in _dns_responses,domain) + return _dns_responses[domain] + + + +def test_suite(): + from unittest import TestLoader + return TestLoader().loadTestsFromName(__name__) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkim/tests/test_dkim_rsavariants.py new/dkimpy-1.1.5/dkim/tests/test_dkim_rsavariants.py --- old/dkimpy-1.0.5/dkim/tests/test_dkim_rsavariants.py 2020-04-06 06:27:45.000000000 +0200 +++ new/dkimpy-1.1.5/dkim/tests/test_dkim_rsavariants.py 2023-04-30 16:18:48.000000000 +0200 @@ -41,6 +41,7 @@ self.message = read_test_data("test.message") self.key1024 = read_test_data("1024_testkey.key") self.key2048 = read_test_data("2048_testkey.key") + self.key2048PKCS8 = read_test_data("2048_testkey_PKCS8.key") def dnsfunc(self, domain, timeout=5): _dns_responses = { @@ -48,6 +49,7 @@ 'test2._domainkey.example.com.': read_test_data("1024_testkey_wo_markers.pub.rsa.txt"), 'test3._domainkey.example.com.': read_test_data("2048_testkey_wo_markers.pub.txt"), 'test4._domainkey.example.com.': read_test_data("2048_testkey_wo_markers.pub.rsa.txt"), + 'test5._domainkey.example.com.': read_test_data("2048_testkey_PKCS8.key.pub.txt") } try: domain = domain.decode('ascii') @@ -56,7 +58,6 @@ self.assertTrue(domain in _dns_responses,domain) return _dns_responses[domain] - def test_verifies_SubjectPublicKeyInfo1024(self): # A message verifies after being signed. for header_algo in (b"simple", b"relaxed"): @@ -67,8 +68,6 @@ res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) self.assertTrue(res) - - def test_verifies_RSAPublicKey1024(self): # A message verifies after being signed. for header_algo in (b"simple", b"relaxed"): @@ -102,6 +101,16 @@ self.assertTrue(res) + def test_verifies_RSAPublicKey2048PKCS8(self): + #A message verifies after being signed (with PKCS8 private key) + for header_algo in (b"simple", b"relaxed"): + for body_algo in (b"simple", b"relaxed"): + sig = dkim.sign( + self.message, b"test5", b"example.com", self.key2048PKCS8, + canonicalize=(header_algo, body_algo)) + res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) + self.assertTrue(res) + def test_suite(): from unittest import TestLoader return TestLoader().loadTestsFromName(__name__) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkimpy.egg-info/PKG-INFO new/dkimpy-1.1.5/dkimpy.egg-info/PKG-INFO --- old/dkimpy-1.0.5/dkimpy.egg-info/PKG-INFO 2020-08-09 04:35:02.000000000 +0200 +++ new/dkimpy-1.1.5/dkimpy.egg-info/PKG-INFO 2023-07-28 18:02:28.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: dkimpy -Version: 1.0.5 +Version: 1.1.5 Summary: DKIM (DomainKeys Identified Mail), ARC (Authenticated Receive Chain), and TLSRPT (TLS Report) email signing and verification Home-page: https://launchpad.net/dkimpy Author: Scott Kitterman @@ -21,7 +21,7 @@ # VERSION - This is dkimpy 1.0.5. + This is dkimpy 1.1.5. # REQUIREMENTS @@ -32,12 +32,10 @@ Similarly, extras_requires feature 'asyncio' will add the extra dependencies needed for asyncio. - - Python 2.x >= 2.7, or Python 3.x >= 3.5. Recent versions have not been - tested on python < 2.7 or python3 < 3.4, but may still work on python2.6 - and python 3.1 - 3.3. - - dnspython or pydns. dnspython is preferred if both are present and + - Python 3.x >= 3.5. Recent versions have not been on python3 < 3.4, but + may still work on earlier python3 versions. + - dnspython or py3dns. dnspython is preferred if both are present and installed to satisfy the DNS module requirement if neither are installed. - - argparse. Standard library in python2.7 and later. - authres. Needed for ARC. - PyNaCl. Needed for use of ed25519 capability. - aiodns. Needed for asycnio (Requires python3.5 or later) @@ -91,8 +89,10 @@ ships with test runners for dkimpy. After downloading the test suite, you can run the signing and validation tests like this: - ```python2.7 ./testarc.py sign runners/arcsigntest.py``` - ```python2.7 ./testarc.py validate runners/arcverifytest.py``` + ```python3 ./testarc.py sign runners/arcsigntest.py``` + ```python3 ./testarc.py validate runners/arcverifytest.py``` + + As ov version 1.1.0, python2.7 is no longer supported. # USAGE @@ -160,7 +160,10 @@ dknewkey is s script that produces private and public key pairs suitable for use with DKIM. Note that the private key file format used for ed25519 is - not standardized (there is no standard) and is unique to dkimpy. + not standardized (there is no standard) and is unique to dkimpy. Creation of + keys should be done in a secure environment. If an unauthorized entity gains + access to current private keys they can generate signed email that will pass + DKIM checkes and will be difficult to repudiate. dkimsign is a filter that reads an RFC822 message on standard input, and writes the same message on standard output with a DKIM-Signature line @@ -184,6 +187,9 @@ In addition to arcsign and arcverify, the dkim module now provides arc_sign and arc_verify functions as well as an ARC class. + If an invalid authentication results header field is included in the set for + ARC, it is ignored and no error is raised. + Both DKIM ed25519 and ARC are now considered stable (no longer experimantal). ## ASYNC SUPPORT @@ -205,6 +211,9 @@ This feature requires python3.5 or newer. + If aiodns is available, the async functions will be used. To avoide async + when aiodns is availale, set dkim.USE_ASYNC = False. + ## TLSRPT (TLS Report) As of version 1.0, the RFC 8460 tlsrpt service type is supported: @@ -224,6 +233,13 @@ valid. If set to tlsrpt=True, the service type is not required, but other RFC 8460 requirements are applied. + # LIMITATIONS + + Dkimpy will correctly sign/verify messages with ASCII or UTF-8 content. + Messages that contain other types of content will not verify correctly. It + does not yet implement RFC 8616, Email Authentication for Internationalized + Mail. + # FEEDBACK Bug reports may be submitted to the bug tracker for the dkimpy project on @@ -236,7 +252,6 @@ Classifier: License :: DFSG approved Classifier: Natural Language :: English Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Communications :: Email :: Mail Transport Agents Classifier: Topic :: Communications :: Email :: Filters diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkimpy.egg-info/SOURCES.txt new/dkimpy-1.1.5/dkimpy.egg-info/SOURCES.txt --- old/dkimpy-1.0.5/dkimpy.egg-info/SOURCES.txt 2020-08-09 04:35:02.000000000 +0200 +++ new/dkimpy-1.1.5/dkimpy.egg-info/SOURCES.txt 2023-07-28 18:02:28.000000000 +0200 @@ -23,6 +23,7 @@ dkim/tests/test_crypto.py dkim/tests/test_dkim.py dkim/tests/test_dkim_ed25519.py +dkim/tests/test_dkim_generate.py dkim/tests/test_dkim_rsavariants.py dkim/tests/test_dkim_tlsrpt.py dkim/tests/test_dnsplug.py @@ -31,6 +32,8 @@ dkim/tests/data/1024_testkey_wo_markers.pub.rsa.txt dkim/tests/data/1024_testkey_wo_markers.pub.txt dkim/tests/data/2048_testkey.key +dkim/tests/data/2048_testkey_PKCS8.key +dkim/tests/data/2048_testkey_PKCS8.key.pub.txt dkim/tests/data/2048_testkey_wo_markers.pub.rsa.txt dkim/tests/data/2048_testkey_wo_markers.pub.txt dkim/tests/data/badk.txt @@ -44,8 +47,10 @@ dkim/tests/data/rfc6376.msg dkim/tests/data/rfc6376.signed.msg dkim/tests/data/rfc6376.signed.rsa.msg +dkim/tests/data/rfc6376.w1258.msg dkim/tests/data/rfc8032_7_1.key dkim/tests/data/test.message +dkim/tests/data/test.message.baddomain dkim/tests/data/test.private dkim/tests/data/test.txt dkim/tests/data/test2.message diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/dkimpy.egg-info/requires.txt new/dkimpy-1.1.5/dkimpy.egg-info/requires.txt --- old/dkimpy-1.0.5/dkimpy.egg-info/requires.txt 2020-08-09 04:35:02.000000000 +0200 +++ new/dkimpy-1.1.5/dkimpy.egg-info/requires.txt 2023-07-28 18:02:28.000000000 +0200 @@ -1,4 +1,4 @@ -dnspython>=1.16.0 +Py3DNS [ARC] authres diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/man/dkimverify.1 new/dkimpy-1.1.5/man/dkimverify.1 --- old/dkimpy-1.0.5/man/dkimverify.1 2020-08-08 23:04:57.000000000 +0200 +++ new/dkimpy-1.1.5/man/dkimverify.1 2023-04-30 16:18:48.000000000 +0200 @@ -133,7 +133,7 @@ \- Script for DKIM verifying messages on stdin .SH "VERSION" -0\.6\.0 +1\.1\.0 .SH "DESCRIPTION" @@ -141,6 +141,13 @@ code 0 if the signature verifies successfully. Otherwise, it returns with exit code 1. +.SH "USAGE" +usage: dkimverify.py [\-h] [\-\-index N] <message + +optional arguments: + \-h, \-\-help show this help message and exit + \-\-index N Index of DKIM signature header to verify: default=0 + .SH "AUTHORS" This version of \fBdkimverify\fR was written by Greg Hewgill <g...@hewgill.com>. .PP diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dkimpy-1.0.5/setup.py new/dkimpy-1.1.5/setup.py --- old/dkimpy-1.0.5/setup.py 2020-08-08 23:04:57.000000000 +0200 +++ new/dkimpy-1.1.5/setup.py 2023-07-28 17:59:47.000000000 +0200 @@ -25,17 +25,14 @@ import os import sys -version = "1.0.5" +version = "1.1.5" kw = {} # Work-around for lack of 'or' requires in setuptools. try: import DNS - if sys.version_info[0] == 2: - kw['install_requires'] = ['PyDNS'] - else: - kw['install_requires'] = ['Py3DNS'] + kw['install_requires'] = ['Py3DNS'] except ImportError: # If PyDNS is not installed, prefer dnspython - kw['install_requires'] = ['dnspython>=1.16.0'] + kw['install_requires'] = ['dnspython>=2.0.0'] with open("README.md", "r") as fh: long_description = fh.read() @@ -73,7 +70,6 @@ 'License :: DFSG approved', 'Natural Language :: English', 'Operating System :: OS Independent', - 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Communications :: Email :: Mail Transport Agents', 'Topic :: Communications :: Email :: Filters', ++++++ no-optional.patch ++++++ --- /var/tmp/diff_new_pack.bq8BcV/_old 2024-01-06 19:38:07.850367381 +0100 +++ /var/tmp/diff_new_pack.bq8BcV/_new 2024-01-06 19:38:07.882368551 +0100 @@ -1,14 +1,14 @@ -Index: dkimpy-1.0.3/dkim/__init__.py +Index: dkimpy-1.1.5/dkim/__init__.py =================================================================== ---- dkimpy-1.0.3.orig/dkim/__init__.py -+++ dkimpy-1.0.3/dkim/__init__.py -@@ -40,18 +40,9 @@ import sys - import time - import binascii +--- dkimpy-1.1.5.orig/dkim/__init__.py ++++ dkimpy-1.1.5/dkim/__init__.py +@@ -43,18 +43,9 @@ import binascii + # Set to False to not use async functions even though aiodns is installed. + USE_ASYNC = True -# only needed for arc -try: -- from authres import AuthenticationResultsHeader +- import authres -except ImportError: - pass - @@ -18,10 +18,19 @@ - import nacl.encoding -except ImportError: - pass -+from authres import AuthenticationResultsHeader ++import authres +import nacl.signing +import nacl.encoding from dkim.canonicalization import ( CanonicalizationPolicy, +@@ -1181,7 +1172,7 @@ class ARC(DomainSigner): + if chain_validation_status == CV_Fail: + self.headers.reverse() + if b'h' in as_fields: +- raise ValidationError("h= tag not permitted in ARC-Seal header field") ++ raise ValidationError("h= tag not permitted in ARC-Seal header field") + res = self.gen_header(as_fields, as_include_headers, canon_policy, + b"ARC-Seal", pk, standardize) +