[Python-modules-commits] [dkimpy] 01/04: Imported Upstream version 0.7

Scott Kitterman kitterman at moszumanska.debian.org
Wed Feb 7 06:44:04 UTC 2018


This is an automated email from the git hooks/post-receive script.

kitterman pushed a commit to branch debian/master
in repository dkimpy.

commit f88feebe9e5edde2e613ab1ab957dcaadb7cda06
Author: Scott Kitterman <scott at kitterman.com>
Date:   Wed Feb 7 01:24:18 2018 -0500

    Imported Upstream version 0.7
---
 ChangeLog                                          |  21 ++
 PKG-INFO                                           |   2 +-
 README                                             |  43 +++-
 dkim/__init__.py                                   | 221 ++++++++++++++++-----
 dkim/canonicalization.py                           |  11 +-
 dkim/crypto.py                                     |   2 +
 dkim/tests/__init__.py                             |   3 +
 dkim/tests/data/ed25519test.dns                    |   1 +
 dkim/tests/data/ed25519test.key                    |   1 +
 dkim/tests/data/{test.message => ed25519test.msg}  |   1 +
 dkim/tests/data/ed25519test.verify.msg             |  16 ++
 dkim/tests/data/ed25519test2.msg                   |  14 ++
 dkim/tests/data/ed25519test3.msg                   |  15 ++
 .../{test.message => ed25519test3.unsigned.msg}    |   1 +
 dkim/tests/data/eximtest.dns                       |   1 +
 dkim/tests/data/test.message                       |   1 +
 dkim/tests/data/test2.private                      |  15 ++
 dkim/tests/data/test2.txt                          |   1 +
 dkim/tests/test_arc.py                             |  71 +------
 dkim/tests/test_canonicalization.py                |  40 ++++
 dkim/tests/test_dkim.py                            |  34 ++++
 dkim/tests/{test_dkim.py => test_dkim_ed25519.py}  |  84 ++++----
 dkimsign.py                                        |  51 +++--
 dkimverify.py                                      |   3 +-
 dknewkey.py                                        |  71 +++++--
 man/dkimsign.1                                     |  29 ++-
 man/dknewkey.1                                     |  39 ++--
 setup.py                                           |   2 +-
 28 files changed, 577 insertions(+), 217 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 98550bf..6b26243 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2018-02-07 Version 0.7.0
+    - Initial ed25519 implementation based on draft-ietf-dcrup-dkim-crypto
+      experimental - IETF draft, design not finalized, See README for details
+    - Port dkimsign.py to use argparse; now gives standard usage message and
+      is more extensible
+    - Add command line options to dkimsign.py to select header and body
+      canonicalization algorithmns (LP: #1272724)
+    - Add command line option to dkimsign.py to select signing algorithm
+    - For dknewkey.py make default to include h=sha256 in the DNS record to
+      exclude usage with sha1.  Can be overriden
+    - Update ARC processing to current draft
+    - Fix arcverify tag requirements (LP: #1710312)
+    - Fix empty body canonicalization for relaxed canonicalization (LP: #1727319)
+       * Thanks to Matthew Palmer for the report and the proposed fix
+    - Add new test, test_implicit_k, to verify that RSA processing is still
+      correct when the optional k= tag is not present in the DKIM public key
+      record
+    - Fix -v verbose reporting in dkimverify.py
+    - Fix unbound local variable error when processing signatures with an x
+      tag, but no t tag (LP: #1739637)
+
 2017-05-30 Version 0.6.2
     - Fixed problem with header folding that caused the first line to be
       folded too long (Updated test test_add_body_length since l= tag is no
diff --git a/PKG-INFO b/PKG-INFO
index 9a01abd..88fe993 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: dkimpy
-Version: 0.6.2
+Version: 0.7.0
 Summary: DKIM (DomainKeys Identified Mail)
 Home-page: https://launchpad.net/dkimpy
 Author: Scott Kitterman
diff --git a/README b/README
index 1807c39..f388a3b 100644
--- a/README
+++ b/README
@@ -11,7 +11,7 @@ signing and verification.
 
 VERSION
 
-This is dkimpy 0.6.2.
+This is dkimpy 0.7.0.
 
 REQUIREMENTS
 
@@ -19,6 +19,9 @@ REQUIREMENTS
    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.
+ - argparse.  Standard library in python2.7 and later.
+ - authres.  Needed for ARC.
+ - nacl.  Needed for use of experimental ed25519 capability.
 
 INSTALLATION
 
@@ -62,9 +65,8 @@ The reason for the test failure is that the ARC specification (as of 20170120)
 sets the minimum key size to 512 bits.  This is operationally inappropriate,
 so dkimpy sets the default minkey=1024, the same as is used for DKIM.  This
 can be overridden, but that is not recommended.  The minimum key size
-requirement for DKIM (and thus ARC) is in the process of being updated to
-require at least a 1024 bit key.  Information about the status of this effort
-is at https://datatracker.ietf.org/doc/draft-ietf-dcrup-dkim-usage/
+requirement for DKIM (and thus ARC) has recently been updated to require at
+least a 1024 bit key.  See RFC 8301.
 
 USAGE
 
@@ -75,7 +77,38 @@ function takes an RFC822 formatted message, and returns True or False depending
 on whether the signature verifies correctly.  There is also a DKIM class which
 can be used to perform these functions in a more modern way.
 
-Two helper programs are also supplied: dkimsign.py and dkimverify.py.
+RFC8301 updated DKIM requirements in two ways:
+
+1.  It set the minimum valid RSA key size to 1024 bits.
+2.  It removed use of rsa-sha1.
+
+As of version 0.7, the dkimpy defaults largely support these requirements.
+
+It is possible to override the minimum key size to a lower value, but this is
+strongly discouraged.  As of 2018, keys much smaller than the minimum are not
+difficult to factor.
+
+The code for rsa-sha1 signing and verification is retained, but not used for
+signing by default.  Future releases will raise warnings and then errors when
+verifying rsa-sha1 signatures.  There are still some significant users of
+rsa-sha1 signatures, so operationally it's premature to disable verification
+of rsa-sha1.
+
+As of version 0.7, experimental signing and verifying of DKIM Ed25519
+signatures is supported as described in draft-ietf-dcrup-dkim-crypto:
+
+https://datatracker.ietf.org/doc/draft-ietf-dcrup-dkim-crypto/
+
+The dkimpy 0.7 implementation matches the -08 revision of the draft, except it
+uses Ed25519 vice Ed25519ph (a change to Ed25519 is planned for -09, but that
+had not been published yet as of the release of dkimpy 0.7).
+
+Three helper programs are also supplied: dknewkey.py, dkimsign.py and
+dkimverify.py.
+
+dknewkey.py 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.
 
 dkimsign.py is a filter that reads an RFC822 message on standard input, and
 writes the same message on standard output with a DKIM-Signature line
diff --git a/dkim/__init__.py b/dkim/__init__.py
index 7d3b75b..ce0a0b6 100644
--- a/dkim/__init__.py
+++ b/dkim/__init__.py
@@ -24,7 +24,7 @@
 # Contact: Brandon Long <blong at google.com>
 #
 # This has been modified from the original software.
-# Copyright (c) 2016, 2017 Scott Kitterman <scott at kitterman.com>
+# Copyright (c) 2016, 2017, 2018 Scott Kitterman <scott at kitterman.com>
 #
 # This has been modified from the original software.
 # Copyright (c) 2017 Valimail Inc
@@ -38,6 +38,19 @@ import logging
 import re
 import time
 
+# only needed for arc
+try:
+  from authres import AuthenticationResultsHeader
+except:
+  pass
+
+# only needed for ed25519 signing/verification
+try:
+    import nacl.signing
+    import nacl.encoding
+except:
+    pass
+
 from dkim.canonicalization import (
     CanonicalizationPolicy,
     InvalidCanonicalizationPolicyError,
@@ -71,6 +84,8 @@ __all__ = [
     "MessageFormatError",
     "ParameterError",
     "ValidationError",
+    "AuthresNotFoundError",
+    "NaClNotFoundError",
     "CV_Pass",
     "CV_Fail",
     "CV_None",
@@ -141,6 +156,17 @@ class ValidationError(DKIMException):
     """Validation error."""
     pass
 
+class AuthresNotFoundError(DKIMException):
+    """ Authres Package not installed, needed for ARC """
+    pass
+
+class NaClNotFoundError(DKIMException):
+    """ Nacl package not installed, needed for ed25119 signatures """
+    pass
+
+class UnknownKeyTypeError(DKIMException):
+    """ Key type (k tag) is not known (rsa/ed25519) """
+
 def select_headers(headers, include_headers):
     """Select message header fields to be signed/verified.
 
@@ -186,6 +212,20 @@ def hash_headers(hasher, canonicalize_headers, headers, include_headers,
         hasher.update(y)
     return sign_headers
 
+def hash_headers_ed25519(pk, canonicalize_headers, headers, include_headers,
+                 sigheader, sig):
+    """Update hash for signed message header fields."""
+    hash_header = ''
+    sign_headers = select_headers(headers,include_headers)
+    # The call to _remove() assumes that the signature b= only appears
+    # once in the signature header
+    cheaders = canonicalize_headers.canonicalize_headers(
+        [(sigheader[0], RE_BTAG.sub(b'\\1',sigheader[1]))])
+    # the dkim sig is hashed with no trailing crlf, even if the
+    # canonicalization algorithm would add one.
+    for x,y in sign_headers + [(x, y.rstrip()) for x,y in cheaders]:
+        hash_header += x + y
+    return sign_headers, hash_header
 
 def validate_signature_fields(sig, mandatory_fields=[b'v', b'a', b'b', b'bh', b'd', b'h', b's'], arc=False):
     """Validate DKIM or ARC Signature fields.
@@ -249,6 +289,8 @@ def validate_signature_fields(sig, mandatory_fields=[b'v', b'a', b'b', b'bh', b'
             raise ValidationError(
               "x= value is not a decimal integer (%s)" % sig[b'x'])
         x_sign = int(sig[b'x'])
+        now = int(time.time())
+        slop = 36000 # 10H leeway for mailers with inaccurate clocks
         if x_sign < now - slop:
             raise ValidationError(
                 "x= value is past (%s)" % sig[b'x'])
@@ -343,13 +385,22 @@ def load_pk_from_dns(name, dnsfunc=get_txt):
   except InvalidTagValueList as e:
       raise KeyFormatError(e)
   try:
-      pk = parse_public_key(base64.b64decode(pub[b'p']))
-      keysize = bitsize(pk['modulus'])
+      if pub[b'k'] == b'ed25519':
+          pk = nacl.signing.VerifyKey(pub[b'p'], encoder=nacl.encoding.Base64Encoder)
+          keysize = 256
+          ktag = b'ed25519'
   except KeyError:
-      raise KeyFormatError("incomplete public key: %s" % s)
-  except (TypeError,UnparsableKeyError) as e:
-      raise KeyFormatError("could not parse public key (%s): %s" % (pub[b'p'],e))
-  return pk, keysize
+      pub[b'k'] = b'rsa'
+  if pub[b'k'] == b'rsa':
+      try:
+          pk = parse_public_key(base64.b64decode(pub[b'p']))
+          keysize = bitsize(pk['modulus'])
+      except KeyError:
+          raise KeyFormatError("incomplete public key: %s" % s)
+      except (TypeError,UnparsableKeyError) as e:
+          raise KeyFormatError("could not parse public key (%s): %s" % (pub[b'p'],e))
+      ktag = b'rsa'
+  return pk, keysize, ktag
 
 #: Abstract base class for holding messages and options during DKIM/ARC signing and verification.
 class DomainSigner(object):
@@ -500,10 +551,14 @@ class DomainSigner(object):
         h, canon_policy, headers, include_headers, header, sig)
     self.logger.debug("sign %s headers: %r" % (header_name, h.hashed()))
 
-    try:
-        sig2 = RSASSA_PKCS1_v1_5_sign(h, pk)
-    except DigestTooLargeError:
-        raise ParameterError("digest too large for modulus")
+    if self.signature_algorithm == b'rsa-sha256' or self.signature_algorithm == b'rsa-sha1':
+        try:
+            sig2 = RSASSA_PKCS1_v1_5_sign(h, pk)
+        except DigestTooLargeError:
+            raise ParameterError("digest too large for modulus")
+    elif self.signature_algorithm == b'ed25519-sha256':
+        sigobj = pk.sign(h.digest())
+        sig2 = sigobj.signature
     # Folding b= is explicity allowed, but yahoo and live.com are broken
     #header_value += base64.b64encode(bytes(sig2))
     # Instead of leaving unfolded (which lets an MTA fold it later and still
@@ -526,7 +581,7 @@ class DomainSigner(object):
   def verify_sig(self, sig, include_headers, sig_header, dnsfunc):
     name = sig[b's'] + b"._domainkey." + sig[b'd'] + b"."
     try:
-      pk, self.keysize = load_pk_from_dns(name, dnsfunc)
+      pk, self.keysize, ktag = load_pk_from_dns(name, dnsfunc)
     except KeyFormatError as e:
       self.logger.error("%s" % e)
       return False
@@ -571,16 +626,25 @@ class DomainSigner(object):
     self.signed_headers = hash_headers(
         h, canon_policy, headers, include_headers, sig_header, sig)
     self.logger.debug("signed for %s: %r" % (sig_header[0], h.hashed()))
-
-    try:
-        signature = base64.b64decode(re.sub(br"\s+", b"", sig[b'b']))
-        res = RSASSA_PKCS1_v1_5_verify(h, signature, pk)
-        self.logger.debug("%s valid: %s" % (sig_header[0], res))
-        if res and self.keysize < self.minkey:
-          raise KeyFormatError("public key too small: %d" % self.keysize)
-        return res
-    except (TypeError,DigestTooLargeError) as e:
-        raise KeyFormatError("digest too large for modulus: %s"%e)
+    signature = base64.b64decode(re.sub(br"\s+", b"", sig[b'b']))
+    if ktag == b'rsa':
+        try:
+            res = RSASSA_PKCS1_v1_5_verify(h, signature, pk)
+            self.logger.debug("%s valid: %s" % (sig_header[0], res))
+            if res and self.keysize < self.minkey:
+                raise KeyFormatError("public key too small: %d" % self.keysize)
+            return res
+        except (TypeError,DigestTooLargeError) as e:
+            raise KeyFormatError("digest too large for modulus: %s"%e)
+    elif ktag == b'ed25519':
+        try:
+            pk.verify(h.digest(), signature)
+            self.logger.debug("%s valid" % (sig_header[0]))
+            return True
+        except (nacl.exceptions.BadSignatureError) as e:
+            return False
+    else:
+        raise UnknownKeyTypeError(ktag)
 
 #: Hold messages and options during DKIM signing and verification.
 class DKIM(DomainSigner):
@@ -619,12 +683,17 @@ class DKIM(DomainSigner):
   #: @return: DKIM-Signature header field terminated by '\r\n'
   #: @raise DKIMException: when the message, include_headers, or key are badly
   #: formed.
-  def sign(self, selector, domain, privkey, identity=None,
+  def sign(self, selector, domain, privkey, signature_algorithm=None, identity=None,
         canonicalize=(b'relaxed',b'simple'), include_headers=None, length=False):
-    try:
-        pk = parse_pem_private_key(privkey)
-    except UnparsableKeyError as e:
-        raise KeyFormatError(str(e))
+    if signature_algorithm:
+        self.signature_algorithm = signature_algorithm
+    if self.signature_algorithm == b'rsa-sha256' or self.signature_algorithm == b'rsa-sha1':
+        try:
+            pk = parse_pem_private_key(privkey)
+        except UnparsableKeyError as e:
+            raise KeyFormatError(str(e))
+    elif self.signature_algorithm == b'ed25519-sha256':
+        pk = nacl.signing.SigningKey(privkey, encoder=nacl.encoding.Base64Encoder)
 
     if identity is not None and not identity.endswith(domain):
         raise ParameterError("identity must end with domain")
@@ -765,27 +834,59 @@ class ARC(DomainSigner):
   #: @param selector: the DKIM selector value for the signature
   #: @param domain: the DKIM domain value for the signature
   #: @param privkey: a PKCS#1 private key in base64-encoded text form
-  #: @param auth_results: RFC 7601 Authentication-Results header value for the message
-  #: @param chain_validation_status: CV_Pass, CV_Fail, CV_None
+  #: @param srv_id: an srv_id for identitfying AR headers to sign & extract cv from
   #: @param include_headers: a list of strings indicating which headers
   #: are to be signed (default rfc4871 recommended headers)
   #: @return: list of ARC set header fields
   #: @raise DKIMException: when the message, include_headers, or key are badly
   #: formed.
-  def sign(self, selector, domain, privkey, auth_results, chain_validation_status,
-           include_headers=None, timestamp=None, standardize=False):
+  def sign(self, selector, domain, privkey, srv_id, include_headers=None,
+           timestamp=None, standardize=False):
+
+    # check if authres has been imported
+    try:
+        AuthenticationResultsHeader
+    except:
+        self.logger.debug("authres package not installed")
+        raise AuthresNotFoundError
+
     try:
         pk = parse_pem_private_key(privkey)
     except UnparsableKeyError as e:
         raise KeyFormatError(str(e))
 
+    # 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]
+    auth_headers = [res for res in grouped_headers if res[1].authserv_id == srv_id.decode('utf-8')]
+
+    if len(auth_headers) == 0:
+      self.logger.debug("no AR headers found, chain terminated")
+      return b''
+
+    # consolidate headers
+    results_lists = [raw.replace(srv_id + b';', b'').strip() for (raw, parsed) in auth_headers]
+    results_lists = [tags.split(b';') for tags in results_lists]
+    results = [tag.strip() for sublist in results_lists for tag in sublist]
+    auth_results = srv_id + b'; ' + b';\r\n  '.join(results)
+
+    # extract cv
+    parsed_auth_results = 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:
+      self.logger.debug("no AR arc stamps found, chain terminated")
+      return b''
+    elif len(arc_results) != 1:
+      self.logger.debug("multiple AR arc stamps found, failing chain")
+      chain_validation_status = CV_Fail
+    else:
+      chain_validation_status = arc_results[0].result.lower().encode('utf-8')
+
     # Setup headers
     if include_headers is None:
         include_headers = self.default_sign_headers()
 
-    if b'arc-authentication-results' not in include_headers:
-        include_headers.append(b'arc-authentication-results')
-
     include_headers = tuple([x.lower() for x in include_headers])
 
     # record what verify should extract
@@ -811,7 +912,10 @@ class ARC(DomainSigner):
       raise ParameterError("cv=none not allowed on instance %d" % instance)
 
     new_arc_set = []
-    arc_headers = [y for x,y in arc_headers_w_instance]
+    if chain_validation_status != CV_Fail:
+      arc_headers = [y for x,y in arc_headers_w_instance]
+    else: # don't include previous sets for a failed/invalid chain
+      arc_headers = []
 
     # Compute ARC-Authentication-Results
     aar_value = ("i=%d; " % instance).encode('utf-8') + auth_results
@@ -869,6 +973,11 @@ class ARC(DomainSigner):
     as_include_headers = [x[0].lower() for x in arc_headers]
     as_include_headers.reverse()
 
+    # if our chain is failing or invalid, we only grab the most recent set
+    # reversing the order of the headers accomplishes this
+    if chain_validation_status == CV_Fail:
+      self.headers.reverse()
+
     res = self.gen_header(as_fields, as_include_headers, canon_policy,
                            b"ARC-Seal", pk, standardize)
 
@@ -887,7 +996,7 @@ class ARC(DomainSigner):
   #: @param dnsfunc: an optional function to lookup TXT resource records
   #: for a DNS domain.  The default uses dnspython or pydns.
   #: @return: True if signature verifies or False otherwise
-  #: @return: three-tuple of (CV Result (CV_Pass, CV_Fail or CV_None), list of
+  #: @return: three-tuple of (CV Result (CV_Pass, CV_Fail, CV_None or None, for a chain that has ended), list of
   #: result dictionaries, result reason)
   #: @raise DKIMException: when the message, signature, or key are badly formed
   def verify(self,dnsfunc=get_txt):
@@ -907,10 +1016,10 @@ class ARC(DomainSigner):
     if not result_data[0]['ams-valid']:
         return CV_Fail, result_data, "Most recent ARC-Message-Signature did not validate"
     for result in result_data:
-      if not result['as-valid']:
-        return CV_Fail, result_data, "ARC-Seal[%d] did not validate" % result['instance']
       if result['cv'] == CV_Fail:
-        return CV_Fail, result_data, "ARC-Seal[%d] reported failure" % result['instance']
+        return None, result_data, "ARC-Seal[%d] reported failure, the chain is terminated" % result['instance']
+      elif not result['as-valid']:
+        return CV_Fail, result_data, "ARC-Seal[%d] did not validate" % result['instance']
       elif (result['instance'] == 1) and (result['cv'] != CV_None):
         return CV_Fail, result_data, "ARC-Seal[%d] reported invalid status %s" % (result['instance'], result['cv'])
       elif (result['instance'] != 1) and (result['cv'] == CV_None):
@@ -968,7 +1077,7 @@ class ARC(DomainSigner):
 
     self.logger.debug("ams sig[%d]: %r" % (instance, sig))
 
-    validate_signature_fields(sig, [b'i', b'a', b'b', b'c', b'bh', b'd', b'h', b's'], True)
+    validate_signature_fields(sig, [b'i', b'a', b'b', b'bh', b'd', b'h', b's'], True)
     output['ams-domain'] = sig[b'd']
     output['ams-selector'] = sig[b's']
 
@@ -977,7 +1086,18 @@ class ARC(DomainSigner):
         raise ParameterError("The Arc-Message-Signature MUST NOT sign ARC-Seal")
 
     ams_header = (b'ARC-Message-Signature', b' ' + ams_value)
-    ams_valid = self.verify_sig(sig, include_headers, ams_header, dnsfunc)
+
+
+    # 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]
+
+    try:
+      ams_valid = self.verify_sig(sig, include_headers, raw_ams_header, dnsfunc)
+    except DKIMException as e:
+      self.logger.error("%s" % e)
+      ams_valid = False
 
     output['ams-valid'] = ams_valid
     self.logger.debug("ams valid: %r" % ams_valid)
@@ -990,7 +1110,7 @@ class ARC(DomainSigner):
 
     self.logger.debug("as sig[%d]: %r" % (instance, sig))
 
-    validate_signature_fields(sig, [b'i', b'a', b'b', b'cv', b'd', b's', b't'], True)
+    validate_signature_fields(sig, [b'i', b'a', b'b', b'cv', b'd', b's'], True)
     output['as-domain'] = sig[b'd']
     output['as-selector'] = sig[b's']
     output['cv'] = sig[b'cv']
@@ -998,7 +1118,11 @@ class ARC(DomainSigner):
     as_include_headers = [x[0].lower() for x in arc_headers]
     as_include_headers.reverse()
     as_header = (b'ARC-Seal', b' ' + as_value)
-    as_valid = self.verify_sig(sig, as_include_headers[:-1], as_header, dnsfunc)
+    try:
+      as_valid = self.verify_sig(sig, as_include_headers[:-1], as_header, dnsfunc)
+    except DKIMException as e:
+      self.logger.error("%s" % e)
+      as_valid = False
 
     output['as-valid'] = as_valid
     self.logger.debug("as valid: %r" % as_valid)
@@ -1045,8 +1169,7 @@ dkim_sign = sign
 dkim_verify = verify
 
 def arc_sign(message, selector, domain, privkey,
-             auth_results, chain_validation_status,
-             signature_algorithm=b'rsa-sha256',
+             srv_id, signature_algorithm=b'rsa-sha256',
              include_headers=None, timestamp=None,
              logger=None, standardize=False):
     """Sign an RFC822 message and return the ARC set header lines for the next instance
@@ -1054,19 +1177,19 @@ def arc_sign(message, selector, domain, privkey,
     @param selector: the DKIM selector value for the signature
     @param domain: the DKIM domain value for the signature
     @param privkey: a PKCS#1 private key in base64-encoded text form
-    @param auth_results: the RFC 7601 authentication-results header field value for this instance
-    @param chain_validation_status: the validation status of the existing chain on the message (P (pass), F (fail)) or N (none) for no existing chain
+    @param srv_id: the authserv_id used to identify the ADMD's AR headers
     @param signature_algorithm: the signing algorithm to use when signing
     @param include_headers: a list of strings indicating which headers are to be signed (default all headers not listed as SHOULD NOT sign)
     @param logger: a logger to which debug info will be written (default None)
     @return: A list containing the ARC set of header fields for the next instance
     @raise DKIMException: when the message, include_headers, or key are badly formed.
     """
+
     a = ARC(message,logger=logger,signature_algorithm=signature_algorithm)
     if not include_headers:
         include_headers = a.default_sign_headers()
-    return a.sign(selector, domain, privkey, auth_results, chain_validation_status,
-                  include_headers=include_headers, timestamp=timestamp, standardize=standardize)
+    return a.sign(selector, domain, privkey, srv_id, include_headers=include_headers,
+                  timestamp=timestamp, standardize=standardize)
 
 def arc_verify(message, logger=None, dnsfunc=get_txt, minkey=1024):
     """Verify the ARC chain on an RFC822 formatted message.
diff --git a/dkim/canonicalization.py b/dkim/canonicalization.py
index a674e25..c69cd23 100644
--- a/dkim/canonicalization.py
+++ b/dkim/canonicalization.py
@@ -48,6 +48,13 @@ def unfold_header_value(content):
     return re.sub(b"\r\n", b"", content)
 
 
+def correct_empty_body(content):
+    if content == b"\r\n":
+        return b""
+    else:
+        return content
+
+
 class Simple:
     """Class that represents the "simple" canonicalization algorithm."""
 
@@ -85,8 +92,8 @@ class Relaxed:
         # Remove all trailing WSP at end of lines.
         # Compress non-line-ending WSP to single space.
         # Ignore all empty lines at the end of the message body.
-        return strip_trailing_lines(
-            compress_whitespace(strip_trailing_whitespace(body)))
+        return correct_empty_body(strip_trailing_lines(
+            compress_whitespace(strip_trailing_whitespace(body))))
 
 
 class CanonicalizationPolicy:
diff --git a/dkim/crypto.py b/dkim/crypto.py
index c46c97a..39d89fe 100644
--- a/dkim/crypto.py
+++ b/dkim/crypto.py
@@ -18,6 +18,7 @@
 #
 # This has been modified from the original software.
 # Copyright (c) 2011 William Grant <me at williamgrant.id.au>
+# Copyright (c) 2018 Scott Kitterman <scott at kitterman.com>
 
 __all__ = [
     'DigestTooLargeError',
@@ -81,6 +82,7 @@ ASN1_RSAPrivateKey = [
 HASH_ALGORITHMS = {
     b'rsa-sha1': hashlib.sha1,
     b'rsa-sha256': hashlib.sha256,
+    b'ed25519-sha256': hashlib.sha256
     }
 
 # These values come from RFC 8017, section 9.2 Notes, page 46.
diff --git a/dkim/tests/__init__.py b/dkim/tests/__init__.py
index 40cb64a..e923974 100644
--- a/dkim/tests/__init__.py
+++ b/dkim/tests/__init__.py
@@ -19,6 +19,7 @@
 # This has been modified from the original software.
 # Copyright (c) 2016 Google, Inc.
 # Contact: Brandon Long <blong at google.com>
+# Copyright (c) 2018 Scott Kitterman <scott at kitterman.com>
 
 import unittest
 
@@ -28,6 +29,7 @@ def test_suite():
         test_canonicalization,
         test_crypto,
         test_dkim,
+        test_dkim_ed25519,
         test_util,
         test_arc,
         test_dnsplug,
@@ -36,6 +38,7 @@ def test_suite():
         test_canonicalization,
         test_crypto,
         test_dkim,
+        test_dkim_ed25519,
         test_util,
         test_arc,
         test_dnsplug,
diff --git a/dkim/tests/data/ed25519test.dns b/dkim/tests/data/ed25519test.dns
new file mode 100644
index 0000000..8272844
--- /dev/null
+++ b/dkim/tests/data/ed25519test.dns
@@ -0,0 +1 @@
+k=ed25519; p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=
diff --git a/dkim/tests/data/ed25519test.key b/dkim/tests/data/ed25519test.key
new file mode 100644
index 0000000..f12a3d1
--- /dev/null
+++ b/dkim/tests/data/ed25519test.key
@@ -0,0 +1 @@
+fL+5V9EquCZAovKik3pA6Lk9zwCzoEtjIuIqK9ZXHHA=
diff --git a/dkim/tests/data/test.message b/dkim/tests/data/ed25519test.msg
similarity index 57%
copy from dkim/tests/data/test.message
copy to dkim/tests/data/ed25519test.msg
index 8a7254e..82f3c0f 100644
--- a/dkim/tests/data/test.message
+++ b/dkim/tests/data/ed25519test.msg
@@ -1,3 +1,4 @@
+Authentication-Results: lists.example.org; arc=none; spf=pass smtp.mfrom=jqd at d1.example; dkim=pass (1024-bit key) header.i=@d1.example; dmarc=pass
 Received: from localhost
 Message-ID: <example at example.com>
 Date: Mon, 01 Jan 2011 01:02:03 +0400
diff --git a/dkim/tests/data/ed25519test.verify.msg b/dkim/tests/data/ed25519test.verify.msg
new file mode 100644
index 0000000..7f9a8e5
--- /dev/null
+++ b/dkim/tests/data/ed25519test.verify.msg
@@ -0,0 +1,16 @@
+DKIM-Signature: v=1; a=ed25519; c=relaxed/simple; d=example.com;
+ i=@example.com; q=dns/txt; s=test; t=5; h=message-id :
+ date : from : to : subject : date : from : subject;
+ bh=wE7NXSkgnx9PGiavN4OZhJztvkqPDlemV3OGuEnLwNo=;
+ b=wt7P+9DoBwcln1RKE3LN7069ZEEiSyVE/NH1YXnqnJy4JcrSCZUbeIEh
+ vXssPHelX4yNSXG9eTGTwwk5NxYqBw==
+Authentication-Results: lists.example.org; arc=none; spf=pass smtp.mfrom=jqd at d1.example; dkim=pass (1024-bit key) header.i=@d1.example; dmarc=pass
+Received: from localhost
+Message-ID: <example at example.com>
+Date: Mon, 01 Jan 2011 01:02:03 +0400
+From: Test User <test at example.com>
+To: somebody at example.com
+Subject: Testing
+
+This is a test message.
+
diff --git a/dkim/tests/data/ed25519test2.msg b/dkim/tests/data/ed25519test2.msg
new file mode 100644
index 0000000..0904120
--- /dev/null
+++ b/dkim/tests/data/ed25519test2.msg
@@ -0,0 +1,14 @@
+DKIM-Signature: v=1; a=ed25519-sha256; q=dns/txt; c=relaxed/relaxed; d=test.ex
+	; s=sed; h=From:To:Subject; bh=/Ab0giHZitYQbDhFszoqQRUkgqueaX9zatJttIU/plc=;
+	 b=5fhyD3EILDrnL4DnkD4hDaeis7+GSzL9GMHrhIDZJjuJ00WD5iI8SQ1q9rDfzFL/Kdw0VIyB4R
+	Dq0a4H6HI+Bw==;
+Received: from jgh by myhost.test.ex with local (Exim x.yz)
+	envelope-from <jgh at myhost.test.ex>)
+	 1dtXln-0000YP-Hb
+	 a at test.ex; Sun, 17 Sep 2017 12:29:51 +0100
+From: nobody at example.com
+Message-Id: <E1dtXln-0000YP-Hb at myhost.test.ex>
+Sender: CALLER_NAME <jgh at myhost.test.ex>
+Date: Sun, 17 Sep 2017 12:29:51 +0100
+
+content
diff --git a/dkim/tests/data/ed25519test3.msg b/dkim/tests/data/ed25519test3.msg
new file mode 100644
index 0000000..671c411
--- /dev/null
+++ b/dkim/tests/data/ed25519test3.msg
@@ -0,0 +1,15 @@
+DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/simple; d=kitterman.org; 
+ i=@kitterman.org; q=dns/txt; s=ed25519; t=1517847601; 
+ h=message-id : date : from : to : subject : date : from : 
+ subject; bh=wE7NXSkgnx9PGiavN4OZhJztvkqPDlemV3OGuEnLwNo=; 
+ b=sEnnE99Xsjpcqa/cNf8k/KQCEgjJ/4tswIKoNvq2q0fFQL6XBORJ2fQb
+ Fvt34Tb4sOxlZtBYu01kEJlmGz4uCw==
+Authentication-Results: lists.example.org; arc=none; spf=pass smtp.mfrom=example.com; dmarc=pass
+Received: from localhost
+Message-ID: <example at example.com>
+Date: Mon, 01 Jan 2011 01:02:03 +0400
+From: Test User <test at example.com>
+To: somebody at example.com
+Subject: Testing
+
+This is a test message.
diff --git a/dkim/tests/data/test.message b/dkim/tests/data/ed25519test3.unsigned.msg
similarity index 67%
copy from dkim/tests/data/test.message
copy to dkim/tests/data/ed25519test3.unsigned.msg
index 8a7254e..99f5c2c 100644
--- a/dkim/tests/data/test.message
+++ b/dkim/tests/data/ed25519test3.unsigned.msg
@@ -1,3 +1,4 @@
+Authentication-Results: lists.example.org; arc=none; spf=pass smtp.mfrom=example.com; dmarc=pass
 Received: from localhost
 Message-ID: <example at example.com>
 Date: Mon, 01 Jan 2011 01:02:03 +0400
diff --git a/dkim/tests/data/eximtest.dns b/dkim/tests/data/eximtest.dns
new file mode 100644
index 0000000..ceb1d2a
--- /dev/null
+++ b/dkim/tests/data/eximtest.dns
@@ -0,0 +1 @@
+v=DKIM1; k=ed25519; p=sPs07Vu29FpHT/80UXUcYHFOHifD4o2ZlP2+XUh9g6E=
diff --git a/dkim/tests/data/test.message b/dkim/tests/data/test.message
index 8a7254e..82f3c0f 100644
--- a/dkim/tests/data/test.message
+++ b/dkim/tests/data/test.message
@@ -1,3 +1,4 @@
+Authentication-Results: lists.example.org; arc=none; spf=pass smtp.mfrom=jqd at d1.example; dkim=pass (1024-bit key) header.i=@d1.example; dmarc=pass
 Received: from localhost
 Message-ID: <example at example.com>
 Date: Mon, 01 Jan 2011 01:02:03 +0400
diff --git a/dkim/tests/data/test2.private b/dkim/tests/data/test2.private
new file mode 100644
index 0000000..3800be0
--- /dev/null
+++ b/dkim/tests/data/test2.private
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQi
+Y/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqM
+KrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB
+AoGAH0cxOhFZDgzXWhDhnAJDw5s4roOXN4OhjiXa8W7Y3rhX3FJqmJSPuC8N9vQm
+6SVbaLAE4SG5mLMueHlh4KXffEpuLEiNp9Ss3O4YfLiQpbRqE7Tm5SxKjvvQoZZe
+zHorimOaChRL2it47iuWxzxSiRMv4c+j70GiWdxXnxe4UoECQQDzJB/0U58W7RZy
+6enGVj2kWF732CoWFZWzi1FicudrBFoy63QwcowpoCazKtvZGMNlPWnC7x/6o8Gc
+uSe0ga2xAkEA8C7PipPm1/1fTRQvj1o/dDmZp243044ZNyxjg+/OPN0oWCbXIGxy
+WvmZbXriOWoSALJTjExEgraHEgnXssuk7QJBALl5ICsYMu6hMxO73gnfNayNgPxd
+WFV6Z7ULnKyV7HSVYF0hgYOHjeYe9gaMtiJYoo0zGN+L3AAtNP9huqkWlzECQE1a
+licIeVlo1e+qJ6Mgqr0Q7Aa7falZ448ccbSFYEPD6oFxiOl9Y9se9iYHZKKfIcst
+o7DUw1/hz2Ck4N5JrgUCQQCyKveNvjzkkd8HjYs0SwM0fPjK16//5qDZ2UiDGnOe
+uEzxBDAr518Z8VFbR41in3W4Y3yCDgQlLlcETrS+zYcL
+-----END RSA PRIVATE KEY-----
diff --git a/dkim/tests/data/test2.txt b/dkim/tests/data/test2.txt
new file mode 100644
index 0000000..f8f0230
--- /dev/null
+++ b/dkim/tests/data/test2.txt
@@ -0,0 +1 @@
+v=DKIM1; g=*; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB
diff --git a/dkim/tests/test_arc.py b/dkim/tests/test_arc.py
index 1aea21f..e2f5e4e 100644
--- a/dkim/tests/test_arc.py
+++ b/dkim/tests/test_arc.py
@@ -68,79 +68,18 @@ Y+vtSBczUiKERHv1yRbcaQtZFh5wtiRrN04BLUTD21MycBX5jYchHjPY/wIDAQAB"""
         self.assertTrue(domain in _dns_responses,domain)
         return _dns_responses[domain]
 
-    def test_verifies(self):
+    def test_signs_and_verifies(self):
         # A message verifies after being signed.
         sig_lines = dkim.arc_sign(
-            self.message, b"test", b"example.com", self.key,
-            b"test.domain: none", dkim.CV_None)
-        (cv, res, reason) = dkim.arc_verify(b''.join(sig_lines) + self.message, dnsfunc=self.dnsfunc)
-        self.assertEquals(cv, dkim.CV_Pass)
+            self.message, b"test", b"example.com", self.key, b"lists.example.org", timestamp="12345")
 
-    """def test_multiple_instances_verify(self):
-        # A message verifies after being signed multiple times.
-        message = self.message
-        sig_lines = dkim.arc_sign(
-            message, b"test", b"example.com", self.key,
-            "test.domain: none", dkim.CV_None)
-        message = ''.join(sig_lines) + message
-        (cv, res, reason) = dkim.arc_verify(message, dnsfunc=self.dnsfunc)
-        self.assertEquals(cv, dkim.CV_Pass)
+        expected_sig = [b'ARC-Seal: i=1; cv=none; a=rsa-sha256; d=example.com; s=test; t=12345; \r\n b=3jOfBfTKcq+3r3Xv158DybT4mWFxrGcop+cgyLUX2ETCMHqNXYwGx2h+NY46tr\r\n k0Lg6R8i+560+KC8PLcCURYYJNJUHLHPIifhddy1aMNL9l4CoI+Oz+rocd2IZeb/\r\n I9V5amOUOWnAlOvyrSt0XfzLJRTS8qJW3Is1CRkkgyLoI=\r\n', b'ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; \r\n d=example.com; s=test; t=12345; h=message-id : \r\n date : from : to : subject : date : from : \r\n subject; \r\n bh=wE7NXSkgnx9PGia [...]
 
-        for x in range(10):
-          sig_lines = dkim.arc_sign(
-              message, b"test", b"example.com", self.key,
-              "test.domain: arc=pass", dkim.CV_Pass)
-          message = ''.join(sig_lines) + message
-          (cv, res, reason) = dkim.arc_verify(message, dnsfunc=self.dnsfunc)
-          self.assertEquals(cv, dkim.CV_Pass)
+        self.assertEquals(expected_sig, sig_lines)
 
-    def test_multiple_instances_verify_fail(self):
-        # A message return CV_Fail if signed as failure.
-        message = self.message
-        sig_lines = dkim.arc_sign(
-            message, b"test", b"example.com", self.key,
-            "test.domain: none", dkim.CV_None)
-        message = ''.join(sig_lines) + message
-        (cv, res, reason) = dkim.arc_verify(message, dnsfunc=self.dnsfunc)
+        (cv, res, reason) = dkim.arc_verify(b''.join(sig_lines) + self.message, dnsfunc=self.dnsfunc)
         self.assertEquals(cv, dkim.CV_Pass)
 
-        sig_lines = dkim.arc_sign(
-            message, b"test", b"example.com", self.key,
-            "test.domain: arc=pass", dkim.CV_Fail)
-        message = ''.join(sig_lines) + message
-        # A conforming signer wouldn't sign as pass after a fail.
-        sig_lines = dkim.arc_sign(
-            message, b"test", b"example.com", self.key,
-            "test.domain: arc=pass", dkim.CV_Pass)
-        message = ''.join(sig_lines) + message
-
-        (cv, res, reason) = dkim.arc_verify(message, dnsfunc=self.dnsfunc)
-        self.assertEquals(cv, dkim.CV_Fail)
-
-    def test_altered_body_fails(self):
-        # An altered body fails verification.
-        sig_lines = dkim.arc_sign(
-            self.message, b"test", b"example.com", self.key,
-            "test.domain: none", dkim.CV_None)
-        (cv, res, reason) = dkim.arc_verify(''.join(sig_lines) + self.message + b"foo", dnsfunc=self.dnsfunc)
-        self.assertEquals(cv, dkim.CV_Fail)
-
-    def test_dns_pk_mismatch_fails(self):
-        # DNS public key doesn't match signing private key.
-        sig_lines = dkim.arc_sign(
-            self.message, b"example", b"canonical.com", self.key,
-            "test.domain: none", dkim.CV_None)
-        (cv, res, reason) = dkim.arc_verify(''.join(sig_lines) + self.message, dnsfunc=self.dnsfunc)
-        self.assertEquals(cv, dkim.CV_Fail)
-
-    def test_dns_missing_fails(self):
-        # DNS public key missing fails verify
-        sig_lines = dkim.arc_sign(
-            self.message, b"missing", b"example.com", self.key,
-            "test.domain: none", dkim.CV_None)
-        (cv, res, reason) = dkim.arc_verify(''.join(sig_lines) + self.message, dnsfunc=self.dnsfunc)
-        self.assertEquals(cv, dkim.CV_Fail)"""
-
 def test_suite():
     from unittest import TestLoader
     return TestLoader().loadTestsFromName(__name__)
diff --git a/dkim/tests/test_canonicalization.py b/dkim/tests/test_canonicalization.py
index 84505e7..e4d6d04 100644
--- a/dkim/tests/test_canonicalization.py
+++ b/dkim/tests/test_canonicalization.py
@@ -53,6 +53,26 @@ class TestSimpleAlgorithmBody(BaseCanonicalizationTest):
             b'Foo  \tbar    \r\n',
             b'Foo  \tbar    \r\n\r\n')
 
+    def test_adds_crlf(self):
+        self.assertCanonicalForm(
+            b'Foo bar\r\n',
+            b'Foo bar')
+
+    def test_empty_body(self):
+        self.assertCanonicalForm(
+            b'\r\n',
+            b'')
+
+    def test_single_crlf_body(self):
+        self.assertCanonicalForm(
+            b'\r\n',
+            b'\r\n')
+
+    def test_multiple_crlf_body(self):
+        self.assertCanonicalForm(
+            b'\r\n',
+            b'\r\n\r\n')
+
 
 class TestRelaxedAlgorithmHeaders(BaseCanonicalizationTest):
 
@@ -98,6 +118,26 @@ class TestRelaxedAlgorithmBody(BaseCanonicalizationTest):
             b'Foo\r\nbar\r\n',
             b'Foo\r\nbar\r\n\r\n\r\n')
 
+    def test_adds_crlf(self):
+        self.assertCanonicalForm(
+            b'Foo bar\r\n',
+            b'Foo bar')
+
+    def test_empty_body(self):
+        self.assertCanonicalForm(
+            b'',
+            b'')
+
+    def test_single_crlf_body(self):
+        self.assertCanonicalForm(
+            b'',
+            b'\r\n')
+
+    def test_multiple_crlf_body(self):
+        self.assertCanonicalForm(
+            b'',
+            b'\r\n\r\n')
+
 
 class TestCanonicalizationPolicyFromCValue(unittest.TestCase):
 
diff --git a/dkim/tests/test_dkim.py b/dkim/tests/test_dkim.py
index 3962a72..1c567b0 100644
--- a/dkim/tests/test_dkim.py
+++ b/dkim/tests/test_dkim.py
@@ -78,6 +78,30 @@ Y+vtSBczUiKERHv1yRbcaQtZFh5wtiRrN04BLUTD21MycBX5jYchHjPY/wIDAQAB"""
         self.assertTrue(domain in _dns_responses,domain)
         return _dns_responses[domain]
 
+    def dnsfunc2(self, domain):
+        sample_dns = """\
+k=rsa; \
+p=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANmBe10IgY+u7h3enWTukkqtUD5PR52T\
+b/mPfjC0QJTocVBq6Za/PlzfV+Py92VaCak19F4WrbVTK5Gg5tW220MCAwEAAQ=="""
+
+        _dns_responses = {
+          'example._domainkey.canonical.com.': sample_dns,
+          'test._domainkey.example.com.': read_test_data("test2.txt"),
+          '20120113._domainkey.gmail.com.': """\
+p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Kd87/UeJjenpabgbFwh\
++eBCsSTrqmwIYYvywlbhbqoo2DymndFkbjOVIPIldNs/m40KF+yzMn1skyoxcTUGCQ\
+s8g3FgD2Ap3ZB5DekAo5wMmk4wimDO+U8QzI3SD07y2+07wlNWwIt8svnxgdxGkVbb\
+hzY8i+RQ9DpSVpPbF7ykQxtKXkv/ahW3KjViiAH+ghvvIhkx4xYSIc9oSwVmAl5Oct\
+MEeWUwg8Istjqz8BZeTWbf41fbNhte7Y+YqZOwq1Sd0DbvYAD9NOZK9vlfuac0598H\
+Y+vtSBczUiKERHv1yRbcaQtZFh5wtiRrN04BLUTD21MycBX5jYchHjPY/wIDAQAB"""
+        }
+        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"):
@@ -88,6 +112,16 @@ Y+vtSBczUiKERHv1yRbcaQtZFh5wtiRrN04BLUTD21MycBX5jYchHjPY/wIDAQAB"""
                 res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc)
                 self.assertTrue(res)
 
+    def test_implicit_k(self):
+        # A message verifies after being signed when k= tag is not provided.
+        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", self.key,
+                    canonicalize=(header_algo, body_algo))
+                res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc2)
+                self.assertTrue(res)
+
     def test_simple_signature(self):
         # A message verifies after being signed with SHOULD headers
         for header_algo in (b"simple", b"relaxed"):
diff --git a/dkim/tests/test_dkim.py b/dkim/tests/test_dkim_ed25519.py
similarity index 82%
copy from dkim/tests/test_dkim.py
copy to dkim/tests/test_dkim_ed25519.py
index 3962a72..6c95570 100644
--- a/dkim/tests/test_dkim.py
+++ b/dkim/tests/test_dkim_ed25519.py
@@ -15,6 +15,7 @@
 # 3. This notice may not be removed or altered from any source distribution.
 #
 # Copyright (c) 2011 William Grant <me at williamgrant.id.au>
+# Copyright (c) 2018 Scott Kitterman <scott at kitterman.com>
 
 import email
 import os.path
@@ -51,25 +52,22 @@ class TestSignAndVerify(unittest.TestCase):
     """End-to-end signature and verification tests."""
 
     def setUp(self):
-        self.message = read_test_data("test.message")
-        self.key = read_test_data("test.private")
+        self.message = read_test_data("ed25519test.msg")
... 527 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/dkimpy.git



More information about the Python-modules-commits mailing list