Commit 474f4b63 authored by Adrien Dorsaz's avatar Adrien Dorsaz

Merge branch 'fix-regexp' into 'master'

Accept critical SAN extension and improve a bit regexp

Closes #9 et #10

See merge request !22
parents e84912fd 5df02405
Pipeline #274 passed with stages
in 23 minutes and 5 seconds
......@@ -64,20 +64,20 @@ compile:
lint:
extends: .check
script:
- pylint3 acme_dns_tiny.py
- pylint3 tools/acme_account_deactivate.py
- pylint3 tools/acme_account_rollover.py
- pylint3 tests/config_factory.py
- pylint3 tests/staging_test_acme_dns_tiny.py
- pylint3 tests/unit_test_acme_dns_tiny.py
- pylint3 tests/staging_test_acme_account_deactivate.py
- pylint3 tests/staging_test_acme_account_rollover.py
- pylint3 --max-line-length=99 acme_dns_tiny.py
- pylint3 --max-line-length=99 tools/acme_account_deactivate.py
- pylint3 --max-line-length=99 tools/acme_account_rollover.py
- pylint3 --max-line-length=99 tests/config_factory.py
- pylint3 --max-line-length=99 tests/staging_test_acme_dns_tiny.py
- pylint3 --max-line-length=99 tests/unit_test_acme_dns_tiny.py
- pylint3 --max-line-length=99 tests/staging_test_acme_account_deactivate.py
- pylint3 --max-line-length=99 tests/staging_test_acme_account_rollover.py
pep8:
extends: .check
script:
- pycodestyle --max-line-length=100 --ignore=E401,W503 --exclude=tests .
- pycodestyle --max-line-length=100 --ignore=E722 tests
- pycodestyle --max-line-length=99 --ignore=E401,W503 --exclude=tests .
- pycodestyle --max-line-length=99 --ignore=E722 tests
jessie-ut:
extends: .unit_test
......
......@@ -9,7 +9,7 @@ validation.
Since it has to have access to your private ACME account key and the
rights to update the DNS records of your DNS server, this code has been designed
to be as tiny as possible (currently less than 300 lines).
to be as tiny as possible (currently less than 400 lines).
The only prerequisites are Python 3, OpenSSL and the dnspython module.
......@@ -20,7 +20,7 @@ code) or any release of dnspython module (pyhton2 and python3 merged code) since
1.15.0.
**PLEASE READ THE SOURCE CODE! YOU MUST TRUST IT!
IT HANDLES YOUR ACCOUNT PRIVATE KEYS!**
IT HANDLES YOUR ACCOUNT PRIVATE KEY AND UPDATE SOME OF YOUR DNS RESOURCES !**
Note: this script is a fork of the [acme-tiny project](https://github.com/diafygi/acme-tiny)
which uses ACME HTTP verification to create signed certificates.
......
......@@ -90,8 +90,9 @@ def get_crt(config, log=LOGGER):
common_name = re.search(r"Subject:.*?\s+?CN\s*?=\s*?([^\s,;/]+)", csr)
if common_name is not None:
domains.add(common_name.group(1))
subject_alt_names = re.search(r"X509v3 Subject Alternative Name: \r?\n +([^\r\n]+)\r?\n", csr,
re.MULTILINE | re.DOTALL)
subject_alt_names = re.search(
r"X509v3 Subject Alternative Name: (?:critical)?\s+([^\r\n]+)\r?\n",
csr, re.MULTILINE)
if subject_alt_names is not None:
for san in subject_alt_names.group(1).split(", "):
if san.startswith("DNS:"):
......@@ -122,8 +123,8 @@ def get_crt(config, log=LOGGER):
accountkey = _openssl("rsa", ["-in", config["acmednstiny"]["AccountKeyFile"],
"-noout", "-text"])
pub_hex, pub_exp = re.search(
r"modulus:\r?\n\s+00:([a-f0-9\:\s]+?)\r?\npublicExponent: ([0-9]+)",
accountkey.decode("utf8"), re.MULTILINE | re.DOTALL).groups()
r"modulus:\s+?00:([a-f0-9\:\s]+?)\r?\npublicExponent: ([0-9]+)",
accountkey.decode("utf8"), re.MULTILINE).groups()
pub_exp = "{0:x}".format(int(pub_exp))
pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp
# That signature is used to authenticate with the ACME server, it needs to be safely kept
......@@ -168,7 +169,8 @@ def get_crt(config, log=LOGGER):
log.info("Update contact information if needed.")
if ("contact" in account_request
and set(account_request["contact"]) != set(account_info["contact"])):
http_response, result = _send_signed_request(private_acme_signature["kid"], account_request)
http_response, result = _send_signed_request(private_acme_signature["kid"],
account_request)
if http_response.status_code == 200:
log.debug(" - Account updated with latest contact informations.")
else:
......@@ -189,7 +191,8 @@ def get_crt(config, log=LOGGER):
and order["type"] == "urn:ietf:params:acme:error:userActionRequired"):
raise ValueError(("Order creation failed ({0}). Read Terms of Service ({1}), then follow "
"your CA instructions: {2}")
.format(order["detail"], http_response.headers['Link'], order["instance"]))
.format(order["detail"],
http_response.headers['Link'], order["instance"]))
else:
raise ValueError("Error getting new Order: {0} {1}"
.format(http_response.status_code, order))
......@@ -239,7 +242,8 @@ def get_crt(config, log=LOGGER):
while challenge_verified is False:
try:
log.debug(('Self test (try: %s): Check resource with value "%s" exits on '
'nameservers: %s'), number_check_fail, keydigest64, resolver.nameservers)
'nameservers: %s'), number_check_fail, keydigest64,
resolver.nameservers)
for response in resolver.query(dnsrr_domain, rdtype="TXT").rrset:
log.debug(" - Found value %s", response.to_text())
challenge_verified = (challenge_verified
......@@ -280,7 +284,8 @@ def get_crt(config, log=LOGGER):
_update_dns(dnsrr_set, "delete")
log.info("Request to finalize the order (all chalenge have been completed)")
csr_der = _base64(_openssl("req", ["-in", config["acmednstiny"]["CSRFile"], "-outform", "DER"]))
csr_der = _base64(_openssl("req", ["-in", config["acmednstiny"]["CSRFile"],
"-outform", "DER"]))
http_response, result = _send_signed_request(order["finalize"], {"csr": csr_der})
if http_response.status_code != 200:
raise ValueError("Error while sending the CSR: {0} {1}"
......
......@@ -141,7 +141,8 @@ def generate_acme_dns_tiny_config(): # pylint: disable=too-many-locals,too-many
wild_san_conf = NamedTemporaryFile(delete=False)
with open("/etc/ssl/openssl.cnf", 'r') as opensslcnf:
wild_san_conf.write(opensslcnf.read().encode("utf8"))
wild_san_conf.write("\n[SAN]\nsubjectAltName=DNS:{0},DNS:*.{0}\n".format(DOMAIN).encode("utf8"))
wild_san_conf.write("\n[SAN]\nsubjectAltName=DNS:{0},DNS:*.{0}\n"
.format(DOMAIN).encode("utf8"))
wild_san_conf.seek(0)
Popen(["openssl", "req", "-new", "-sha256", "-key", domain_key,
"-subj", "/", "-reqexts", "SAN", "-config", wild_san_conf.name,
......
......@@ -206,13 +206,15 @@ class TestACMEDNSTiny(unittest.TestCase):
"""Can't use the account key for the CSR."""
self.assertRaisesRegex(ValueError,
"certificate public key must be different than account key",
acme_dns_tiny.main, [self.configs['account_as_domain'], "--verbose"])
acme_dns_tiny.main, [self.configs['account_as_domain'],
"--verbose"])
def test_failure_dns_update_tsigkeyname(self):
"""Fail to update DNS records by invalid TSIG Key name."""
self.assertRaisesRegex(ValueError,
"Error updating DNS",
acme_dns_tiny.main, [self.configs['invalid_tsig_name'], "--verbose"])
acme_dns_tiny.main, [self.configs['invalid_tsig_name'],
"--verbose"])
if __name__ == "__main__": # pragma: no cover
......
......@@ -79,8 +79,8 @@ def account_deactivate(accountkeypath, acme_directory, log=LOGGER):
log.info("Get private signature from account key.")
accountkey = _openssl("rsa", ["-in", accountkeypath, "-noout", "-text"])
pub_hex, pub_exp = re.search(
r"modulus:\r?\n\s+00:([a-f0-9\:\s]+?)\r?\npublicExponent: ([0-9]+)",
accountkey.decode("utf8"), re.MULTILINE | re.DOTALL).groups()
r"modulus:\s+?00:([a-f0-9\:\s]+?)\r?\npublicExponent: ([0-9]+)",
accountkey.decode("utf8"), re.MULTILINE).groups()
pub_exp = "{0:x}".format(int(pub_exp))
pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp
# That signature is used to authenticate with the ACME server, it needs to be safely kept
......
......@@ -37,8 +37,8 @@ def account_rollover(old_accountkeypath, new_accountkeypath, acme_directory, log
"""Read the account key to get the signature to authenticate with the ACME server."""
accountkey = _openssl("rsa", ["-in", accountkeypath, "-noout", "-text"])
pub_hex, pub_exp = re.search(
r"modulus:\r?\n\s+00:([a-f0-9\:\s]+?)\r?\npublicExponent: ([0-9]+)",
accountkey.decode("utf8"), re.MULTILINE | re.DOTALL).groups()
r"modulus:\s+?00:([a-f0-9\:\s]+?)\r?\npublicExponent: ([0-9]+)",
accountkey.decode("utf8"), re.MULTILINE).groups()
pub_exp = "{0:x}".format(int(pub_exp))
pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp
return {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment