- Add generation of rsafp DKIM signatures per
draft-ietf-dcrup-dkim-crypto-02
This commit is contained in:
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user