From 0386e52897f076fba9cfe6c49aba66223a410e56 Mon Sep 17 00:00:00 2001 From: Adrien Ferrand Date: Thu, 18 Nov 2021 20:17:30 +0100 Subject: [PATCH] Add ECDSA support (#578) * Add ECDSA support * Add changelog * FIx doc --- CHANGELOG.md | 13 +++++ docs/configuration_reference.rst | 6 +++ poetry.lock | 56 +++++++++++----------- src/dnsrobocert/core/certbot.py | 3 ++ src/dnsrobocert/core/main.py | 2 + src/dnsrobocert/schema.yml | 3 ++ test/integration_tests/dnsrobocert_test.py | 3 ++ 7 files changed, 59 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39052a1e..f5830dea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog ## master - CURRENT +## Added +* Support ECDSA keys when creating new certificates with the `key_type` string parameter + in the certificate section: set to `rsa` to use RSA keys (default if not set) or `ecdsa` + to use ECDSA keys. Example: + ```yaml + profiles: + - name: dummy + ... + certificates: + - domains: [example.org] + profile: dummy + key_type: ecdsa + ``` ## 3.14.0 - 12/11/2021 ### Added diff --git a/docs/configuration_reference.rst b/docs/configuration_reference.rst index d612e55f..8cede060 100644 --- a/docs/configuration_reference.rst +++ b/docs/configuration_reference.rst @@ -237,6 +237,7 @@ be defined in each relevant certificate configuration. force_renew: false follow_cnames: false reuse_key: false + key_type: ecdsa ``certificate`` properties -------------------------- @@ -301,6 +302,11 @@ be defined in each relevant certificate configuration. * *type*: ``boolean`` * *default*: ``false`` (the private key is never reused for certificate renewal) +``key_type`` + * Type of key to use when the certificate is generated. Must be ``rsa`` or ``ecdsa``. + * *type*: ``string`` + * *default*: ``rsa`` (a RSA-type key will be used) + .. _link: https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme.html#the-advantages-of-a-cname diff --git a/poetry.lock b/poetry.lock index af499229..13e29a11 100644 --- a/poetry.lock +++ b/poetry.lock @@ -117,14 +117,14 @@ webencodings = "*" [[package]] name = "boto3" -version = "1.20.5" +version = "1.20.8" description = "The AWS SDK for Python" category = "main" optional = false python-versions = ">= 3.6" [package.dependencies] -botocore = ">=1.23.5,<1.24.0" +botocore = ">=1.23.8,<1.24.0" jmespath = ">=0.7.1,<1.0.0" s3transfer = ">=0.5.0,<0.6.0" @@ -133,7 +133,7 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.23.5" +version = "1.23.8" description = "Low-level, data-driven core of boto 3." category = "main" optional = false @@ -394,7 +394,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "filelock" -version = "3.3.2" +version = "3.4.0" description = "A platform independent file lock." category = "main" optional = false @@ -523,7 +523,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "josepy" -version = "1.10.0" +version = "1.11.0" description = "JOSE protocol implementation in Python" category = "main" optional = false @@ -650,14 +650,14 @@ pytz = ">=2016.10" [[package]] name = "packaging" -version = "21.2" +version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2,<3" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "parsedatetime" @@ -814,11 +814,14 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] name = "pyparsing" -version = "2.4.7" +version = "3.0.6" description = "Python parsing module" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pyreadline" @@ -1238,11 +1241,11 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.0.0" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "urllib3" @@ -1436,12 +1439,12 @@ bleach = [ {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"}, ] boto3 = [ - {file = "boto3-1.20.5-py3-none-any.whl", hash = "sha256:81ca80fbb3d551819c35c809cb159fd0bec6701d3d8f0e5906a22da7558d098e"}, - {file = "boto3-1.20.5.tar.gz", hash = "sha256:cc620c289b12d7bf7c2706b517c9f8950f9be4622aacc9e7580b8b4ee0d3bc73"}, + {file = "boto3-1.20.8-py3-none-any.whl", hash = "sha256:c0ac23cc36dc484edd1edd28903b5712cb07507af1ae19b2e8d6db176416d9e2"}, + {file = "boto3-1.20.8.tar.gz", hash = "sha256:81ebdcabc534a52e2b7a2bfcbe1a1d7f1e34f028f7fe1cb16ccd80e34cea867a"}, ] botocore = [ - {file = "botocore-1.23.5-py3-none-any.whl", hash = "sha256:c8eaeee0bac356396386aa9165043808fe736fb9e03ac0dedb1dfd82f41ad1a3"}, - {file = "botocore-1.23.5.tar.gz", hash = "sha256:49d1f012dc8467577a5fe603fc87cc13af816dd926b2bc2e28a3b2999ab14d36"}, + {file = "botocore-1.23.8-py3-none-any.whl", hash = "sha256:a0c7cfea155a0202ab197a016736dd4e6a26f9e416bdd9cdd2c9a3fb88ffa5a8"}, + {file = "botocore-1.23.8.tar.gz", hash = "sha256:ae4ed9666199020a9e53c3d3efc0a7d417315cd2313b70cb013282afe70ac358"}, ] cached-property = [ {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, @@ -1630,8 +1633,8 @@ docutils = [ {file = "docutils-0.18.tar.gz", hash = "sha256:c1d5dab2b11d16397406a282e53953fe495a46d69ae329f55aa98a5c4e3c5fbb"}, ] filelock = [ - {file = "filelock-3.3.2-py3-none-any.whl", hash = "sha256:bb2a1c717df74c48a2d00ed625e5a66f8572a3a30baacb7657add1d7bac4097b"}, - {file = "filelock-3.3.2.tar.gz", hash = "sha256:7afc856f74fa7006a289fd10fa840e1eebd8bbff6bffb69c26c54a0512ea8cf8"}, + {file = "filelock-3.4.0-py3-none-any.whl", hash = "sha256:2e139a228bcf56dd8b2274a65174d005c4a6b68540ee0bdbb92c76f43f29f7e8"}, + {file = "filelock-3.4.0.tar.gz", hash = "sha256:93d512b32a23baf4cac44ffd72ccf70732aeff7b8050fcaf6d3ec406d954baf4"}, ] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, @@ -1674,8 +1677,8 @@ jmespath = [ {file = "jmespath-0.10.0.tar.gz", hash = "sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9"}, ] josepy = [ - {file = "josepy-1.10.0-py2.py3-none-any.whl", hash = "sha256:0c91623287db4cde4fe77c38be6bc9122faf137f78680978aa3e96bb0f10f2e8"}, - {file = "josepy-1.10.0.tar.gz", hash = "sha256:e9bcaf605411cadaec04841ae2d5f77ebb178b7b6df7c9aed1d97399ac18685b"}, + {file = "josepy-1.11.0-py2.py3-none-any.whl", hash = "sha256:8473f6674497383627621b40aacb4955afdd4e7b205dca45a68486aa989bab60"}, + {file = "josepy-1.11.0.tar.gz", hash = "sha256:40ef59f2f537ec01bafe698dad66281f6ccf4642f747411647db403ab8fa9a2d"}, ] jsonschema = [ {file = "jsonschema-4.0.0-py3-none-any.whl", hash = "sha256:c773028c649441ab980015b5b622f4cd5134cf563daaf0235ca4b73cc3734f20"}, @@ -1789,8 +1792,8 @@ oci = [ {file = "oci-2.9.0.tar.gz", hash = "sha256:82be4b64456de3b0f7f8051e52d48c0c8b6c499c9c22661b454df459ab7a5632"}, ] packaging = [ - {file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"}, - {file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] parsedatetime = [ {file = "parsedatetime-2.6-py3-none-any.whl", hash = "sha256:cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b"}, @@ -1851,8 +1854,8 @@ pyopenssl = [ {file = "pyOpenSSL-21.0.0.tar.gz", hash = "sha256:5e2d8c5e46d0d865ae933bef5230090bdaf5506281e9eec60fa250ee80600cb3"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, + {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, ] pyreadline = [ {file = "pyreadline-2.1.win-amd64.exe", hash = "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b"}, @@ -2137,9 +2140,8 @@ types-setuptools = [ {file = "types_setuptools-57.4.2-py3-none-any.whl", hash = "sha256:9c96aab47fdcf066fef83160b2b9ddbfab3d2c8fdc89053579d0b306837bf22a"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, + {file = "typing_extensions-4.0.0-py3-none-any.whl", hash = "sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9"}, + {file = "typing_extensions-4.0.0.tar.gz", hash = "sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed"}, ] urllib3 = [ {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, diff --git a/src/dnsrobocert/core/certbot.py b/src/dnsrobocert/core/certbot.py index c9ad1b97..025a484b 100644 --- a/src/dnsrobocert/core/certbot.py +++ b/src/dnsrobocert/core/certbot.py @@ -69,6 +69,7 @@ def certonly( domains: Optional[List[str]] = None, force_renew: bool = False, reuse_key: bool = False, + key_type: str = "rsa", ): if not domains: return @@ -80,6 +81,8 @@ def certonly( additional_params.append("--force-renew") if reuse_key: additional_params.append("--reuse-key") + if key_type: + additional_params.extend(["--key-type", key_type]) for domain in domains: additional_params.append("-d") diff --git a/src/dnsrobocert/core/main.py b/src/dnsrobocert/core/main.py index 0b411f54..6895e495 100644 --- a/src/dnsrobocert/core/main.py +++ b/src/dnsrobocert/core/main.py @@ -52,6 +52,7 @@ def _process_config( domains = certificate["domains"] force_renew = certificate.get("force_renew", False) reuse_key = certificate.get("reuse_key", False) + key_type = certificate.get("key_type", "rsa") LOGGER.info(f"Handling the certificate for domain(s): {', '.join(domains)}") certbot.certonly( runtime_config_path, @@ -61,6 +62,7 @@ def _process_config( domains, force_renew=force_renew, reuse_key=reuse_key, + key_type=key_type, ) except BaseException as error: LOGGER.error( diff --git a/src/dnsrobocert/schema.yml b/src/dnsrobocert/schema.yml index c8fd2b37..f11bec2f 100644 --- a/src/dnsrobocert/schema.yml +++ b/src/dnsrobocert/schema.yml @@ -127,6 +127,9 @@ properties: type: boolean reuse_key: type: boolean + key_type: + type: string + enum: [rsa, ecdsa] required: [domains, profile] additionalProperties: false additionalProperties: false diff --git a/test/integration_tests/dnsrobocert_test.py b/test/integration_tests/dnsrobocert_test.py index 3fdbfecd..db4b4a91 100644 --- a/test/integration_tests/dnsrobocert_test.py +++ b/test/integration_tests/dnsrobocert_test.py @@ -165,6 +165,9 @@ def test_it(tmp_path): - test1.example.net - test2.example.net profile: dummy + follow_cnames: true + reuse_key: true + key_type: ecdsa """ )