From 4df74d67d56d72d80a7ec6435c81ba864633d4e6 Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Fri, 5 Apr 2019 21:27:32 +0200 Subject: [PATCH] tools: add support for EC account keys Allows usage of pre-generated EC account keys (P-256, P-384, P-521) in addition to already supported RSA keys. --- README.md | 4 +-- acertmgr/tools.py | 35 +++++++++++++++++++++++- docs/archlinux/python-acertmgr/PKGBUILD | 6 ++-- docs/archlinux/python2-acertmgr/PKGBUILD | 6 ++-- setup.py | 5 +++- 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 49cd70c..4307b6c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Requirements ------------ * Python (2.7+ and 3.5+ should work) - * cryptography (includes the optional idna module) + * cryptography>=0.6 (includes the optional idna module) Optional packages (required to use specified features) ------------------------------------------------------ @@ -21,7 +21,7 @@ Optional packages (required to use specified features) * dnspython: used by dns.* challenge handlers * idna: to allow automatic conversion of unicode domain names to their IDNA2008 counterparts * cryptography>=2.1: for creating certificates with the OCSP must-staple flag (cert_must_staple) - + Setup ----- diff --git a/acertmgr/tools.py b/acertmgr/tools.py index 8e20c91..351197f 100644 --- a/acertmgr/tools.py +++ b/acertmgr/tools.py @@ -17,8 +17,10 @@ import traceback from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding +from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature from cryptography.x509.oid import NameOID, ExtensionOID +from cryptography.utils import int_to_bytes try: from urllib.request import urlopen, Request # Python 3 @@ -241,6 +243,24 @@ def get_key_alg_and_jwk(key): return "RS256", {"kty": "RSA", "e": bytes_to_base64url(number_to_byte_format(numbers.e)), "n": bytes_to_base64url(number_to_byte_format(numbers.n))} + elif isinstance(key, ec.EllipticCurvePrivateKey): + # See https://tools.ietf.org/html/rfc7518#section-6.2 + numbers = key.public_key().public_numbers() + if isinstance(numbers.curve, ec.SECP256R1): + alg = 'ES256' + crv = 'P-256' + elif isinstance(numbers.curve, ec.SECP384R1): + alg = 'ES384' + crv = 'P-384' + elif isinstance(numbers.curve, ec.SECP521R1): + alg = 'ES512' + crv = 'P-521' + else: + raise ValueError("Unsupported EC curve in key: {}".format(key)) + full_octets = (int(crv[2:]) + 7) // 8 + return alg, {"kty": "EC", "crv": crv, + "x": bytes_to_base64url(int_to_bytes(numbers.x, full_octets)), + "y": bytes_to_base64url(int_to_bytes(numbers.y, full_octets))} else: raise ValueError("Unsupported key: {}".format(key)) @@ -251,6 +271,19 @@ def signature_of_str(key, string): data = string.encode('utf8') if alg == 'RS256': return key.sign(data, padding.PKCS1v15(), hashes.SHA256()) + elif alg.startswith('ES'): + full_octets = (int(alg[2:]) + 7) // 8 + if alg == 'ES256': + der_sig = key.sign(data, ec.ECDSA(hashes.SHA256())) + elif alg == 'ES384': + der_sig = key.sign(data, ec.ECDSA(hashes.SHA384())) + elif alg == 'ES512': + der_sig = key.sign(data, ec.ECDSA(hashes.SHA512())) + else: + raise ValueError("Unsupported EC signature algorithm: {}".format(alg)) + # convert DER signature to RAW format (https://tools.ietf.org/html/rfc7518#section-3.4) + r, s = decode_dss_signature(der_sig) + return int_to_bytes(r, full_octets) + int_to_bytes(s, full_octets) else: raise ValueError("Unsupported signature algorithm: {}".format(alg)) diff --git a/docs/archlinux/python-acertmgr/PKGBUILD b/docs/archlinux/python-acertmgr/PKGBUILD index 578c42c..f61d6cd 100644 --- a/docs/archlinux/python-acertmgr/PKGBUILD +++ b/docs/archlinux/python-acertmgr/PKGBUILD @@ -6,11 +6,11 @@ pkgdesc='An automated certificate manager using ACME/letsencrypt' arch=('any') url='https://github.com/moepman/acertmgr' license=('ISC') -depends=('python-cryptography') +depends=('python-cryptography>=0.6') optdepends=('python-yaml: Support config files in YAML format' 'python-idna: Support conversion of unicode domains' - 'python-dnspython: Support for dns challenge handlers' - 'python-cryptography>=2.1: Support for the OCSP must-staple flag' + 'python-dnspython: Support for dns challenge handlers' + 'python-cryptography>=2.1: Support for the OCSP must-staple flag' ) makedepends=('git') conflicts=('python-acertmgr') diff --git a/docs/archlinux/python2-acertmgr/PKGBUILD b/docs/archlinux/python2-acertmgr/PKGBUILD index 79e5a7f..2d45788 100644 --- a/docs/archlinux/python2-acertmgr/PKGBUILD +++ b/docs/archlinux/python2-acertmgr/PKGBUILD @@ -6,11 +6,11 @@ pkgdesc='An automated certificate manager using ACME/letsencrypt' arch=('any') url='https://github.com/moepman/acertmgr' license=('ISC') -depends=('python2-cryptography') +depends=('python2-cryptography>=0.6') optdepends=('python2-yaml: Support config files in YAML format' 'python2-idna: Support conversion of unicode domains' - 'python2-dnspython: Support for dns challenge handlers' - 'python2-cryptography>=2.1: Support for the OCSP must-staple flag' + 'python2-dnspython: Support for dns challenge handlers' + 'python2-cryptography>=2.1: Support for the OCSP must-staple flag' ) makedepends=('git') conflicts=('python-acertmgr') diff --git a/setup.py b/setup.py index cb6b210..cfb27d9 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ setup( "License :: OSI Approved :: ISC License", ], install_requires=[ - "cryptography", + "cryptography>=0.6", ], extras_require={ "dns": [ @@ -80,6 +80,9 @@ setup( "ocsp-must-staple": [ "cryptography>=2.1", ], + "ed25519": [ + "cryptography>=2.6", + ], }, entry_points={ 'console_scripts': [