<commit_before>"""
.. module: lemur.plugins.lemur_acme.plugin
    :platform: Unix
    :synopsis: This module is responsible for communicating with an ACME CA.
    :copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
    :license: Apache, see LICENSE for more details.

    Snippets from https://raw.githubusercontent.com/alex/letsencrypt-aws/master/letsencrypt-aws.py

.. moduleauthor:: <NAME> <<EMAIL>>
.. moduleauthor:: <NAME> <<EMAIL>>
.. moduleauthor:: <NAME> <<EMAIL>>
"""
import datetime
import json
import time

import OpenSSL.crypto
import josepy as jose
from acme import challenges, messages
from acme.client import BackwardsCompatibleClientV2, ClientNetwork
from acme.errors import PollError, WildcardUnsupportedError
from acme.messages import Error as AcmeError
from botocore.exceptions import ClientError
from flask import current_app

from lemur.authorizations import service as authorization_service
from lemur.common.utils import generate_private_key
from lemur.dns_providers import service as dns_provider_service
from lemur.exceptions import InvalidAuthority, InvalidConfiguration, UnknownProvider
from lemur.extensions import metrics, sentry
from lemur.plugins import lemur_acme as acme
from lemur.plugins.bases import IssuerPlugin
from lemur.plugins.lemur_acme import cloudflare, dyn, route53


class AuthorizationRecord(object):
    def __init__(self, host, authz, dns_challenge, change_id):
        self.host = host
        self.authz = authz
        self.dns_challenge = dns_challenge
        self.change_id = change_id


class AcmeHandler(object):
    def __init__(self):
        self.dns_providers_for_domain = {}
        try:
            self.all_dns_providers = dns_provider_service.get_all_dns_providers()
        except Exception as e:
            metrics.send('AcmeHandler_init_error', 'counter', 1)
            sentry.captureException()
            current_app.logger.error(f"Unable to fetch DNS Providers: {e}")
            self.all_dns_providers = []

    def find_dns_challenge(self, authorizations):
        dns_challenges = []
        for authz in authorizations:
            for combo in authz.body.challenges:
                if isinstance(combo.chall, challenges.DNS01):
                    dns_challenges.append(combo)
        return dns_challenges

    def maybe_remove_wildcard(self, host):
        return host.replace("*.", "")

    def maybe_add_extension(self, host, dns_provider_options):
        if dns_provider_options and dns_provider_options.get("acme_challenge_extension"):
            host = host + dns_provider_options.get("acme_challenge_extension")
        return host

    def start_dns_challenge(self, acme_client, account_number, host, dns_provider, order, dns_provider_options):
        current_app.logger.debug("Starting DNS challenge for {0}".format(host))

        dns_challenges = self.find_dns_challenge(order.authorizations)
        change_ids = []

        host_to_validate = self.maybe_remove_wildcard(host)
        host_to_validate = self.maybe_add_extension(host_to_validate, dns_provider_options)

        for dns_challenge in self.find_dns_challenge(order.authorizations):
            change_id = dns_provider.create_txt_record(
                dns_challenge.validation_domain_name(host_to_validate),
                dns_challenge.validation(acme_client.client.net.key),
                account_number
            )
            change_ids.append(change_id)

        return AuthorizationRecord(
            host,
            order.authorizations,
            dns_challenges,
            change_ids
        )

    def complete_dns_challenge(self, acme_client, authz_record):
        current_app.logger.debug("Finalizing DNS challenge for {0}".format(authz_record.authz[0].body.identifier.value))
        dns_providers = self.dns_providers_for_domain.get(authz_record.host)
        if not dns_providers:
            metrics.send('complete_dns_challenge_error_no_dnsproviders', 'counter', 1)
            raise Exception("No DNS providers found for domain: {}".format(authz_record.host))

        for dns_provider in dns_providers:
            # Grab account number (For Route53)
            dns_provider_options = json.loads(dns_provider.credentials)
            account_number = dns_provider_options.get("account_id")
            dns_provider_plugin = self.get_dns_provider(dns_provider.provider_type)
            for change_id in authz_record.change_id:
                try:
                    dns_provider_plugin.wait_for_dns_change(change_id, account_number=account_number)
                except Exception:
                    metrics.send('complete_dns_challenge_error', 'counter', 1)
                    sentry.captureException()
                    current_app.logger.debug(
                        f"Unable to resolve DNS challenge for change_id: {change_id}, account_id: "
                        f"{account_number}", exc_info=True)
                    raise

            for dns_challenge in authz_record.dns_challenge:
                response = dns_challenge.response(acme_client.client.net.key)

                verified = response.simple_verify(
                    dns_challenge.chall,
                    authz_record.host,
                    acme_client.client.net.key.public_key()
                )

                if not verified:
                    metrics.send('complete_dns_challenge_verification_error', 'counter', 1)
                    raise ValueError("Failed verification")

                time.sleep(5)
                acme_client.answer_challenge(dns_challenge, response)

    def request_certificate(self, acme_client, authorizations, order):
        for authorization in authorizations:
            for authz in authorization.authz:
                authorization_resource, _ = acme_client.poll(authz)

        deadline = datetime.datetime.now() + datetime.timedelta(seconds=90)

        try:
            orderr = acme_client.finalize_order(order, deadline)
        except AcmeError:
            sentry.captureException(extra={"order_url": order.uri})
            metrics.send('request_certificate_error', 'counter', 1)
            current_app.logger.error(f"Unable to resolve Acme order: {order.uri}", exc_info=True)
            raise

        pem_certificate = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                                          OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                                                                          orderr.fullchain_pem)).decode()
        pem_certificate_chain = orderr.fullchain_pem[len(pem_certificate):].lstrip()

        current_app.logger.debug("{0} {1}".format(type(pem_certificate), type(pem_certificate_chain)))
        return pem_certificate, pem_certificate_chain

    def setup_acme_client(self, authority):<commit_msg>Process DNS Challenges appropriately (1 challenge -> 1 domain)<commit_after>"""
.. module: lemur.plugins.lemur_acme.plugin
    :platform: Unix
    :synopsis: This module is responsible for communicating with an ACME CA.
    :copyright: (c) 2018 by Netflix Inc., see AUTHORS for more
    :license: Apache, see LICENSE for more details.

    Snippets from https://raw.githubusercontent.com/alex/letsencrypt-aws/master/letsencrypt-aws.py

.. moduleauthor:: <NAME> <<EMAIL>>
.. moduleauthor:: <NAME> <<EMAIL>>
.. moduleauthor:: <NAME> <<EMAIL>>
"""
import datetime
import json
import time

import OpenSSL.crypto
import josepy as jose
from acme import challenges, messages
from acme.client import BackwardsCompatibleClientV2, ClientNetwork
from acme.errors import PollError, WildcardUnsupportedError
from acme.messages import Error as AcmeError
from botocore.exceptions import ClientError
from flask import current_app

from lemur.authorizations import service as authorization_service
from lemur.common.utils import generate_private_key
from lemur.dns_providers import service as dns_provider_service
from lemur.exceptions import InvalidAuthority, InvalidConfiguration, UnknownProvider
from lemur.extensions import metrics, sentry
from lemur.plugins import lemur_acme as acme
from lemur.plugins.bases import IssuerPlugin
from lemur.plugins.lemur_acme import cloudflare, dyn, route53


class AuthorizationRecord(object):
    def __init__(self, host, authz, dns_challenge, change_id):
        self.host = host
        self.authz = authz
        self.dns_challenge = dns_challenge
        self.change_id = change_id


class AcmeHandler(object):
    def __init__(self):
        self.dns_providers_for_domain = {}
        try:
            self.all_dns_providers = dns_provider_service.get_all_dns_providers()
        except Exception as e:
            metrics.send('AcmeHandler_init_error', 'counter', 1)
            sentry.captureException()
            current_app.logger.error(f"Unable to fetch DNS Providers: {e}")
            self.all_dns_providers = []

    def find_dns_challenge(self, host, authorizations):
        dns_challenges = []
        for authz in authorizations:
            if not authz.body.identifier.value == host:
                continue
            for combo in authz.body.challenges:
                if isinstance(combo.chall, challenges.DNS01):
                    dns_challenges.append(combo)
        return dns_challenges

    def maybe_remove_wildcard(self, host):
        return host.replace("*.", "")

    def maybe_add_extension(self, host, dns_provider_options):
        if dns_provider_options and dns_provider_options.get("acme_challenge_extension"):
            host = host + dns_provider_options.get("acme_challenge_extension")
        return host

    def start_dns_challenge(self, acme_client, account_number, host, dns_provider, order, dns_provider_options):
        current_app.logger.debug("Starting DNS challenge for {0}".format(host))

        change_ids = []

        host_to_validate = self.maybe_remove_wildcard(host)
        host_to_validate = self.maybe_add_extension(host_to_validate, dns_provider_options)
        dns_challenges = self.find_dns_challenge(host_to_validate, order.authorizations)

        for dns_challenge in dns_challenges:
            change_id = dns_provider.create_txt_record(
                dns_challenge.validation_domain_name(host_to_validate),
                dns_challenge.validation(acme_client.client.net.key),
                account_number
            )
            change_ids.append(change_id)

        return AuthorizationRecord(
            host,
            order.authorizations,
            dns_challenges,
            change_ids
        )

    def complete_dns_challenge(self, acme_client, authz_record):
        current_app.logger.debug("Finalizing DNS challenge for {0}".format(authz_record.authz[0].body.identifier.value))
        dns_providers = self.dns_providers_for_domain.get(authz_record.host)
        if not dns_providers:
            metrics.send('complete_dns_challenge_error_no_dnsproviders', 'counter', 1)
            raise Exception("No DNS providers found for domain: {}".format(authz_record.host))

        for dns_provider in dns_providers:
            # Grab account number (For Route53)
            dns_provider_options = json.loads(dns_provider.credentials)
            account_number = dns_provider_options.get("account_id")
            dns_provider_plugin = self.get_dns_provider(dns_provider.provider_type)
            for change_id in authz_record.change_id:
                try:
                    dns_provider_plugin.wait_for_dns_change(change_id, account_number=account_number)
                except Exception:
                    metrics.send('complete_dns_challenge_error', 'counter', 1)
                    sentry.captureException()
                    current_app.logger.debug(
                        f"Unable to resolve DNS challenge for change_id: {change_id}, account_id: "
                        f"{account_number}", exc_info=True)
                    raise

            for dns_challenge in authz_record.dns_challenge:
                response = dns_challenge.response(acme_client.client.net.key)

                verified = response.simple_verify(
                    dns_challenge.chall,
                    authz_record.host,
                    acme_client.client.net.key.public_key()
                )

                if not verified:
                    metrics.send('complete_dns_challenge_verification_error', 'counter', 1)
                    raise ValueError("Failed verification")

                time.sleep(5)
                acme_client.answer_challenge(dns_challenge, response)

    def request_certificate(self, acme_client, authorizations, order):
        for authorization in authorizations:
            for authz in authorization.authz:
                authorization_resource, _ = acme_client.poll(authz)

        deadline = datetime.datetime.now() + datetime.timedelta(seconds=90)

        try:
            orderr = acme_client.poll_and_finalize(order, deadline)
        except AcmeError:
            sentry.captureException(extra={"order_url": order.uri})
            metrics.send('request_certificate_error', 'counter', 1)
            current_app.logger.error(f"Unable to resolve Acme order: {order.uri}", exc_info=True)
            raise

        pem_certificate = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                                          OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
                                                                                          orderr.fullchain_pem)).decode()
        pem_certificate_chain = orderr.fullchain_pem[len(pem_certificate):].lstrip()

        current_app.logger.debug("{0} {1}".format(type(pem_certificate), type(pem_certificate_chain)))
        return pem_certificate, pem_certificate_chain

    def setup_acme_client(self, authority):
{'fraction_non_alphanumeric': 0.0782984859408796, 'fraction_numerical': 0.003604902667627974, 'mean_word_length': 2.6493028150486713, 'pattern_counts': {'":': 2, '<': 21, '<?xml version=': 0, '>': 22, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2536225', 'n_tokens_mistral': 4107, 'n_tokens_neox': 3821, 'n_words': 777}