- Add generation of rsafp DKIM signatures per

draft-ietf-dcrup-dkim-crypto-02
This commit is contained in:
Scott Kitterman
2017-06-23 18:29:37 -04:00
parent 80f663f02c
commit c7782addd5
5 changed files with 35 additions and 7 deletions
+2 -1
View File
@@ -9,7 +9,8 @@ UNRELEASED Version 0.7.0
- Update dknewkey.py to use argparse. Add --ktype option to specify
different key type options in anticipation of the DCRUP WG output.
- Add generation of rsafp DNS records per draft-ietf-dcrup-dkim-crypto-02
- Add generation of rsafp DKIM signatures per
draft-ietf-dcrup-dkim-crypto-02
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
+16 -5
View File
@@ -49,6 +49,7 @@ from dkim.crypto import (
HASH_ALGORITHMS,
parse_pem_private_key,
parse_public_key,
get_rsa_pubkey,
RSASSA_PKCS1_v1_5_sign,
RSASSA_PKCS1_v1_5_verify,
UnparsableKeyError,
@@ -361,7 +362,7 @@ class DomainSigner(object):
#: @param logger: a logger to which debug info will be written (default None)
#: @param signature_algorithm: the signing algorithm to use when signing
def __init__(self,message=None,logger=None,signature_algorithm=b'rsa-sha256',
minkey=1024):
minkey=1024,ktype=b'rsa'):
self.set_message(message)
if logger is None:
logger = get_default_logger()
@@ -382,6 +383,8 @@ class DomainSigner(object):
#: Minimum public key size. Shorter keys raise KeyFormatError. The
#: default is 1024
self.minkey = minkey
#: Key type to use. rsa, rsafp (DCRUP)
self.ktype = ktype
#: Header fields to protect from additions by default.
#:
@@ -620,12 +623,16 @@ class DKIM(DomainSigner):
#: @raise DKIMException: when the message, include_headers, or key are badly
#: formed.
def sign(self, selector, domain, privkey, identity=None,
canonicalize=(b'relaxed',b'simple'), include_headers=None, length=False):
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 self.ktype == b'rsafp':
pubkey = get_rsa_pubkey(privkey)
else:
pubkey = False
if identity is not None and not identity.endswith(domain):
raise ParameterError("identity must end with domain")
@@ -653,10 +660,13 @@ class DKIM(DomainSigner):
h = self.hasher()
h.update(body)
bodyhash = base64.b64encode(h.digest())
if self.ktype == b'rsafp':
atag = b'rsafp-sha256'
else:
atag = self.signature_algorithm
sigfields = [x for x in [
(b'v', b"1"),
(b'a', self.signature_algorithm),
(b'a', atag),
(b'c', canon_policy.to_c_value()),
(b'd', domain),
(b'i', identity or b"@"+domain),
@@ -665,6 +675,7 @@ class DKIM(DomainSigner):
(b's', selector),
(b't', str(int(time.time())).encode('ascii')),
(b'h', b" : ".join(include_headers)),
pubkey and (b'k', pubkey),
(b'bh', bodyhash),
# Force b= to fold onto it's own line so that refolding after
# adding sig doesn't change whitespace for previous tags.
+10
View File
@@ -25,6 +25,7 @@ __all__ = [
'parse_pem_private_key',
'parse_private_key',
'parse_public_key',
'get_rsa_pubkey',
'RSASSA_PKCS1_v1_5_sign',
'RSASSA_PKCS1_v1_5_verify',
'UnparsableKeyError',
@@ -33,6 +34,7 @@ __all__ = [
import base64
import hashlib
import re
import rsa
from dkim.asn1 import (
ASN1FormatError,
@@ -233,6 +235,14 @@ def rsa_decrypt(message, pk, mlen):
return int2str(m2 + h * pk['prime2'], mlen)
def get_rsa_pubkey(privkey):
"""Extract RSA public key from PEM encoded RSA private key for use with
rsafp. Returns base64 encoded public key suitable for use in DKIM key
records. Using python-rsa instead of making a stack of custom code.
@since: 0.7"""
pkobj = rsa.PrivateKey.load_pkcs1(privkey, 'PEM')
pubobj = rsa.key.PublicKey(pkobj.n, pkobj.e)
return(base64.b64encode(rsa.PublicKey.save_pkcs1(pubobj, 'DER')))
def rsa_encrypt(message, pk, mlen):
"""Perform RSA encryption/verification
+2
View File
@@ -28,6 +28,7 @@ def test_suite():
test_canonicalization,
test_crypto,
test_dkim,
test_rsafp,
test_util,
test_arc,
test_dnsplug,
@@ -36,6 +37,7 @@ def test_suite():
test_canonicalization,
test_crypto,
test_dkim,
test_rsafp,
test_util,
test_arc,
test_dnsplug,
+5 -1
View File
@@ -47,6 +47,9 @@ parser.add_argument('--bcanon', choices=['simple', 'relaxed'],
parser.add_argument('--signalg', choices=['rsa-sha256', 'rsa-sha1'],
default='rsa-sha256',
help='Signature algorithm: default=rsa-sha256')
parser.add_argument('--ktype', choices=['rsa', 'rsafp'],
default='rsa',
help='DKIM key type: Default is rsa')
parser.add_argument('--identity', help='Optional value for i= tag.')
args=parser.parse_args(arguments)
include_headers = None
@@ -61,6 +64,7 @@ if sys.version_info[0] >= 3:
args.hcanon = bytes(args.hcanon, encoding='UTF-8')
args.bcanon = bytes(args.bcanon, encoding='UTF-8')
args.signalg = bytes(args.signalg, encoding='UTF-8')
args.ktype = bytes(args.ktype, encoding='UTF-8')
# Make sys.stdin and stdout binary streams.
sys.stdin = sys.stdin.detach()
sys.stdout = sys.stdout.detach()
@@ -69,7 +73,7 @@ canonicalize = (args.hcanon, args.bcanon)
message = sys.stdin.read()
try:
d = dkim.DKIM(message,logger=logger,
signature_algorithm=args.signalg)
signature_algorithm=args.signalg, ktype=args.ktype)
sig = d.sign(args.selector, args.domain, open(
args.privatekeyfile, "rb").read(), identity = args.identity,
canonicalize=canonicalize, include_headers=include_headers,