From 3209be62d109d475bc92c8266798908afa7c47ff Mon Sep 17 00:00:00 2001 From: Scott Kitterman Date: Mon, 5 Feb 2018 11:28:05 -0500 Subject: [PATCH] Fix a= tag in ed25519 signatures (thanks to Jeremy Harris) --- dkim/__init__.py | 4 ++-- dkim/crypto.py | 2 +- dkim/tests/data/ed25519test3.msg | 13 ++++++------- dkim/tests/test_dkim_ed25519.py | 20 ++++++++++---------- dkimsign.py | 2 +- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/dkim/__init__.py b/dkim/__init__.py index 4423161..ce0a0b6 100644 --- a/dkim/__init__.py +++ b/dkim/__init__.py @@ -556,7 +556,7 @@ class DomainSigner(object): sig2 = RSASSA_PKCS1_v1_5_sign(h, pk) except DigestTooLargeError: raise ParameterError("digest too large for modulus") - elif self.signature_algorithm == b'ed25519': + 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 @@ -692,7 +692,7 @@ class DKIM(DomainSigner): pk = parse_pem_private_key(privkey) except UnparsableKeyError as e: raise KeyFormatError(str(e)) - elif self.signature_algorithm == b'ed25519': + 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): diff --git a/dkim/crypto.py b/dkim/crypto.py index 2585a85..39d89fe 100644 --- a/dkim/crypto.py +++ b/dkim/crypto.py @@ -82,7 +82,7 @@ ASN1_RSAPrivateKey = [ HASH_ALGORITHMS = { b'rsa-sha1': hashlib.sha1, b'rsa-sha256': hashlib.sha256, - b'ed25519': hashlib.sha256 + b'ed25519-sha256': hashlib.sha256 } # These values come from RFC 8017, section 9.2 Notes, page 46. diff --git a/dkim/tests/data/ed25519test3.msg b/dkim/tests/data/ed25519test3.msg index 72ab4fc..671c411 100644 --- a/dkim/tests/data/ed25519test3.msg +++ b/dkim/tests/data/ed25519test3.msg @@ -1,9 +1,9 @@ -DKIM-Signature: v=1; a=ed25519; c=relaxed/simple; d=kitterman.org; - i=@kitterman.org; q=dns/txt; s=ed25519; t=1517819503; - h=message-id : date : from : to : subject : date : from : - subject; bh=wE7NXSkgnx9PGiavN4OZhJztvkqPDlemV3OGuEnLwNo=; - b=79R3A+GEIghZIWWOfAxXUEVI/NhyOCH0QWVhYV2+sN8MPVfPfYQAMRi0 - mlXux2AHLc7yihFV5SJWrsvqm62uCw== +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: @@ -13,4 +13,3 @@ To: somebody@example.com Subject: Testing This is a test message. - diff --git a/dkim/tests/test_dkim_ed25519.py b/dkim/tests/test_dkim_ed25519.py index f549ce2..f9268cb 100644 --- a/dkim/tests/test_dkim_ed25519.py +++ b/dkim/tests/test_dkim_ed25519.py @@ -82,7 +82,7 @@ p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=""", 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), signature_algorithm=b'ed25519') + canonicalize=(header_algo, body_algo), signature_algorithm=b'ed25519-sha256') res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) self.assertTrue(res) @@ -94,7 +94,7 @@ p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=""", self.message, b"test", b"example.com", self.key, canonicalize=(header_algo, body_algo), include_headers=(b'from',) + dkim.DKIM.SHOULD, - signature_algorithm=b'ed25519') + signature_algorithm=b'ed25519-sha256') res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) self.assertTrue(res) @@ -106,7 +106,7 @@ p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=""", def test_add_body_length(self): sig = dkim.sign( self.message, b"test", b"example.com", self.key, length=True, - signature_algorithm=b'ed25519') + signature_algorithm=b'ed25519-sha256') msg = email.message_from_string(self.message.decode('utf-8')) self.assertIn('; l=%s' % len(msg.get_payload() + '\n'), sig.decode('utf-8')) res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) @@ -118,7 +118,7 @@ p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=""", for body_algo in (b"simple", b"relaxed"): sig = dkim.sign( self.message, b"test", b"example.com", self.key, - signature_algorithm=b'ed25519') + signature_algorithm=b'ed25519-sha256') res = dkim.verify( sig + self.message + b"foo", dnsfunc=self.dnsfunc) self.assertFalse(res) @@ -126,7 +126,7 @@ p=yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=""", def test_badly_encoded_domain_fails(self): # Domains should be ASCII. Bad ASCII causes verification to fail. sig = dkim.sign(self.message, b"test", b"example.com\xe9", self.key, - signature_algorithm=b'ed25519') + signature_algorithm=b'ed25519-sha256') res = dkim.verify(sig + self.message, dnsfunc=self.dnsfunc) self.assertFalse(res) @@ -155,7 +155,7 @@ yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=\ dkim_header = dkim.sign(sample_msg, b'example', b'canonical.com', sample_privkey, canonicalize=(header_mode, dkim.Relaxed), - signature_algorithm=b'ed25519') + signature_algorithm=b'ed25519-sha256') # Folding dkim_header affects b= tag only, since dkim.sign folds # sig_value with empty b= before hashing, and then appends the # signature. So folding dkim_header again adds FWS to @@ -184,7 +184,7 @@ yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=\ # bug requires a repeated header to manifest d.should_not_sign.remove(b'received') sig = d.sign(b"test", b"example.com", self.key, - signature_algorithm=b'ed25519', + signature_algorithm=b'ed25519-sha256', include_headers=d.all_sign_headers(), canonicalize=(header_algo, body_algo)) dv = dkim.DKIM(sig + message) @@ -204,7 +204,7 @@ yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=\ for body_algo in (b"simple", b"relaxed"): sig = dkim.sign( self.message, b"test", b"example.com", self.key, - signature_algorithm=b'ed25519') + signature_algorithm=b'ed25519-sha256') # adding an unknown header still verifies h1 = h+b'\r\n'+b'X-Foo: bar' message = b'\n\n'.join((h1,b)) @@ -232,14 +232,14 @@ yi50DjK5O9pqbFpNHklsv9lqaS0ArSYu02qp1S0DW1Y=\ try: sig = dkim.sign(message, selector, domain, read_test_data('ed25519test.key'), identity = identity, - signature_algorithm=b'ed25519') + signature_algorithm=b'ed25519-sha256') except dkim.ParameterError as x: sigerror = True self.assertTrue(sigerror) def test_validate_signature_fields(self): sig = {b'v': b'1', - b'a': b'ed25519', + b'a': b'ed25519-sha256', b'b': b'K/UUOt8lCtgjp3kSTogqBm9lY1Yax/NwZ+bKm39/WKzo5KYe3L/6RoIA/0oiDX4kO\n \t Qut49HCV6ZUe6dY9V5qWBwLanRs1sCnObaOGMpFfs8tU4TWpDSVXaNZAqn15XVW0WH\n \t EzOzUfVuatpa1kF4voIgSbmZHR1vN3WpRtcTBe/I=', b'bh': b'n0HUwGCP28PkesXBPH82Kboy8LhNFWU9zUISIpAez7M=', b'c': b'simple/simple', diff --git a/dkimsign.py b/dkimsign.py index 3c38a76..ff36c97 100644 --- a/dkimsign.py +++ b/dkimsign.py @@ -44,7 +44,7 @@ parser.add_argument('--hcanon', choices=['simple', 'relaxed'], parser.add_argument('--bcanon', choices=['simple', 'relaxed'], default='simple', help='Body canonicalization algorithm: default=simple') -parser.add_argument('--signalg', choices=['rsa-sha256', 'ed25519', 'rsa-sha1'], +parser.add_argument('--signalg', choices=['rsa-sha256', 'ed25519-sha256', 'rsa-sha1'], default='rsa-sha256', help='Signature algorithm: default=rsa-sha256') parser.add_argument('--identity', help='Optional value for i= tag.')