Esempio n. 1
0
def perform(
    certificate: Certificate,
    storage: "StorageProtocol",
    acme_account_email: str,
    acme_directory_url: str,
    authenticators: typing.Sequence[AuthenticatorProtocol],
) -> None:
    client = setup_client(
        storage=storage,
        directory_url=acme_directory_url,
        account_email=acme_account_email,
    )

    orderr = client.new_order(
        crypto_util.make_csr(certificate.private_key, certificate.domains))
    auth_challs = select_challs(orderr, authenticators)
    for authenticator, challs in auth_challs:
        account_key = client.net.key
        authenticator.perform(challs, account_key)
        for challb, _ in challs:
            client.answer_challenge(challb, challb.response(account_key))
    try:
        finalized_orderr = client.poll_and_finalize(orderr)
        fullchain_pem = finalized_orderr.fullchain_pem.encode("utf8")
        certificate.set_fullchain(fullchain_pem)
        storage.save_certificate(certificate)
    finally:
        for authenticator, challs in auth_challs:
            authenticator.cleanup(challs, account_key)
Esempio n. 2
0
    def _handle_challenges(self, client, account_key, authzr):
        """Handle challenges from the Letsencrypt provider.

        For each requested domain name we receive a list of challenges.
        We only have to do one from each list.
        HTTP challenges are the easiest, so do one of those if possible.
        We can do DNS challenges too. There are other types that we don't
        support.
        """
        pending_responses = []
        prefer_dns = (self.env["ir.config_parameter"].get_param(
            "letsencrypt.prefer_dns") == "True")
        for authorizations in authzr.authorizations:
            http_challenges = [
                challenge for challenge in authorizations.body.challenges
                if challenge.chall.typ == TYPE_CHALLENGE_HTTP
            ]
            other_challenges = [
                challenge for challenge in authorizations.body.challenges
                if challenge.chall.typ != TYPE_CHALLENGE_HTTP
            ]
            if prefer_dns:
                ordered_challenges = other_challenges + http_challenges
            else:
                ordered_challenges = http_challenges + other_challenges
            for challenge in ordered_challenges:
                if challenge.chall.typ == TYPE_CHALLENGE_HTTP:
                    self._respond_challenge_http(challenge, account_key)
                    client.answer_challenge(challenge,
                                            acme.challenges.HTTP01Response())
                    break
                elif challenge.chall.typ == TYPE_CHALLENGE_DNS:
                    domain = authorizations.body.identifier.value
                    token = challenge.validation(account_key)
                    self._respond_challenge_dns(domain, token)
                    # We delay this because we wait for each domain.
                    # That takes less time if they've all already been changed.
                    pending_responses.append(
                        DNSUpdate(challenge=challenge,
                                  domain=domain,
                                  token=token))
                    break
            else:
                raise UserError(
                    _("Could not respond to letsencrypt challenges."))

        if pending_responses:
            for update in pending_responses:
                self._wait_for_record(update.domain, update.token)
            # 1 minute was not always enough during testing, even once records
            # were visible locally
            _logger.info(
                "All TXT records found, waiting 5 minutes more to make sure.")
            time.sleep(300)
            for update in pending_responses:
                client.answer_challenge(update.challenge,
                                        acme.challenges.DNSResponse())
Esempio n. 3
0
def request_new_certificate(hostname, accnt_key, priv_key, tmp_chall_dict,
                            directory_url):
    """Runs the entire process of ACME registration and certificate request"""

    client = create_v2_client(directory_url, accnt_key)

    try:
        client.net.account = client.new_account(
            messages.NewRegistration.from_data(terms_of_service_agreed=True))

    except errors.ConflictError as error:
        existing_reg = messages.RegistrationResource(uri=error.location)
        existing_reg = client.query_registration(existing_reg)
        client.update_registration(existing_reg)

    csr = crypto_util.make_csr(priv_key, [hostname], False)
    order = client.new_order(csr)

    log.info('Created a new order for the issuance of a certificate for %s',
             hostname)

    challb = select_http01_chall(order)

    _, chall_tok = challb.response_and_validation(client.net.key)
    v = challb.chall.encode("token")
    log.info('Exposing challenge on %s', v)
    tmp_chall_dict.set(v, ChallTok(chall_tok))

    cr = client.answer_challenge(challb, challb.response(client.net.key))
    log.debug('Acme CA responded to challenge request with: %s', cr)

    order = client.poll_and_finalize(order)

    return split_certificate_chain(order.fullchain_pem)
def answer_challenge_http(domain, chall, validation_method, client, account, challg_body, log):
    # Create a challenge response object.
    resp = chall.response(account)

    # See if we've already installed the file at the right location
    # and the response validates. If it validates locally, submit
    # it to the ACME server.

    if validation_method.verify_first:
        try:
            # the 'port' argument is for unit testing only
            ok = resp.simple_verify(chall, domain, account.public_key(), port=validation_method.port)
        except:
            # assume any untrapped errors means something failed
            ok = False
    else:
        ok = True

    file_url = chall.uri(domain)

    if ok:
        log("Submitting challenge response file at %s." % file_url)
        return client.answer_challenge(challg_body, resp)

    else:
        log("Validation file is not present --- a file must be installed on the web server.")
        raise NeedToInstallFile(
            file_url,
            chall.validation(account),
            chall.encode("token"), # the filename
        )
Esempio n. 5
0
def answer_challenge_http(domain, chall, validation_method, client, account, challg_body, log):
    # Create a challenge response object.
    resp = chall.response(account)

    # See if we've already installed the file at the right location
    # and the response validates. If it validates locally, submit
    # it to the ACME server.

    if validation_method.verify_first:
        try:
            # the 'port' argument is for unit testing only
            ok = resp.simple_verify(chall, domain, account.public_key(), port=validation_method.port)
        except:
            # assume any untrapped errors means something failed
            ok = False
    else:
        ok = True

    file_url = chall.uri(domain)

    if ok:
        log("Submitting challenge response file at %s." % file_url)
        return client.answer_challenge(challg_body, resp)

    else:
        log("Validation file is not present --- a file must be installed on the web server.")
        raise NeedToInstallFile(
            file_url,
            chall.validation(account),
            chall.encode("token"), # the filename
        )
Esempio n. 6
0
    def new_authorization(self, authz, client, key, domain):
        for combination in authz.combinations:
            if len(combination) == 1:
                challenger = authz.challenges[combination[0]]
                challenge = challenger.chall
                if isinstance(challenge, acme.challenges.DNS):
                    # store (and deliver) needed response for challenge
                    content = base64.b64encode(challenge.gen_validation(key).signature.signature)

                    self.add_entry(challenge.validation_domain_name(domain) + '.', content.decode('utf-8'))

                    # answer challenges / give ACME server go to check challenge
                    resp = challenge.gen_response(key)
                    client.answer_challenge(challenger, resp)

                    # we can wait until this challenge is first requested ...
                    raise exceptions.AuthorizationNotYetProcessed(datetime.now() + timedelta(seconds=2))
        else:
            return False
Esempio n. 7
0
def answer_challenges(client, challenger, *domains):
    """Answer the challenges for all domains
    """
    authorizations = []
    all_challenges = []

    # first setup all challenges
    logger.info("Setting up challenges")
    for domain in domains:
        authorization = get_authorization(client, domain)
        authorizations.append(authorization)
        challb = get_challenge(authorization,
                               challenge_type=challenger.challenge_type)
        response, validation = challb.response_and_validation(client.key)
        challenger.accomplish(domain, challb, validation)

        all_challenges.append({
            'authorization': authorization,
            'response': response,
            'challb': challb,
            'domain': domain,
        })

    challenger.all_accomplished()

    # then verify all responses
    logger.info("Verify all responses")
    for item in all_challenges:
        verified = item['response'].simple_verify(
            item['challb'].chall, item['domain'], client.key.public_key())
        if not verified:
            raise RuntimeError(
                "Couldn't self-verify that validation "
                "is in place for %s" % item['domain'])

    # then answer all challenges
    logger.info("Answer challenges")
    for item in all_challenges:
        # tell acme server our validations are in place
        client.answer_challenge(item['challb'], item['response'])

    return authorizations
Esempio n. 8
0
    def new_authorization(self, authz, client, key, domain):
        for combination in authz.combinations:
            if len(combination) == 1:
                challenger = authz.challenges[combination[0]]
                challenge = challenger.chall
                if isinstance(challenge, acme.challenges.HTTP01):
                    # store (and deliver) needed response for challenge
                    content = challenge.validation(key)
                    event = Event()
                    self.responses.setdefault(domain, {})
                    self.responses[domain][challenge.path] = (content, event)

                    # answer challenges / give ACME server go to check challenge
                    resp = challenge.response(key)
                    client.answer_challenge(challenger, resp)

                    # we can wait until this challenge is first requested ...
                    raise exceptions.AuthorizationNotYetRequested(event)
        else:
            return False
Esempio n. 9
0
def run_acme_reg_to_finish(domain, accnt_key, priv_key, hostname, tmp_chall_dict, directory_url):
    """Runs the entire process of ACME registeration"""

    client = create_v2_client(directory_url, accnt_key)

    # First we need to create a registration with the email address provided
    # and accept the terms of service
    log.info("Using boulder server %s", directory_url)

    client.net.account = client.new_account(
        messages.NewRegistration.from_data(
            terms_of_service_agreed=True
        )
    )

    # Now we need to open an order and request our certificate

    # NOTE: We'll let ACME generate a CSR for our private key as there's
    # a lot of utility code it uses to generate the CSR in a specific
    # fashion. Better to use what LE provides than to roll our own as we
    # we doing with the v1 code
    #
    # This will also let us support multi-domain certificat requests in the
    # future, as well as mandate OCSP-Must-Staple if/when GL's HTTPS server
    # supports it
    csr = crypto_util.make_csr(priv_key, [hostname], False)
    order = client.new_order(csr)
    authzr = order.authorizations

    log.info('Created a new order for %s', hostname)

    # authrz is a list of Authorization resources, we need to find the
    # HTTP-01 challenge and use it
    for auth_req in authzr:  # pylint: disable=not-an-iterable
        for chall_body in auth_req.body.challenges:
            if isinstance(chall_body.chall, challenges.HTTP01):
                challb = chall_body
                break

    if challb is None:
        raise Exception("HTTP01 challenge unavailable!")

    _, chall_tok = challb.response_and_validation(client.net.key)
    v = chall_body.chall.encode("token")
    log.info('Exposing challenge on %s', v)
    tmp_chall_dict.set(v, ChallTok(chall_tok))

    cr = client.answer_challenge(challb, challb.response(client.net.key))
    log.debug('Acme CA responded to challenge request with: %s', cr)

    order = client.poll_and_finalize(order)

    return split_certificate_chain(order.fullchain_pem)
Esempio n. 10
0
def run_acme_reg_to_finish(domain, accnt_key, priv_key, hostname, tmp_chall_dict, directory_url):
    """Runs the entire process of ACME registeration"""

    client = create_v2_client(directory_url, accnt_key)

    # First we need to create a registration with the email address provided
    # and accept the terms of service
    log.info("Using boulder server %s", directory_url)

    client.net.account = client.new_account(
        messages.NewRegistration.from_data(
            terms_of_service_agreed=True
        )
    )

    # Now we need to open an order and request our certificate

    # NOTE: We'll let ACME generate a CSR for our private key as there's
    # a lot of utility code it uses to generate the CSR in a specific
    # fashion. Better to use what LE provides than to roll our own as we
    # we doing with the v1 code
    #
    # This will also let us support multi-domain certificat requests in the
    # future, as well as mandate OCSP-Must-Staple if/when GL's HTTPS server
    # supports it
    csr = crypto_util.make_csr(priv_key, [hostname], False)
    order = client.new_order(csr)
    authzr = order.authorizations

    log.info('Created a new order for %s', hostname)

    # authrz is a list of Authorization resources, we need to find the
    # HTTP-01 challenge and use it
    for auth_req in authzr: # pylint: disable=not-an-iterable
       for chall_body in auth_req.body.challenges:
            if isinstance(chall_body.chall, challenges.HTTP01):
                challb = chall_body
                break

    if challb is None:
        raise Exception("HTTP01 challenge unavailable!")

    response, chall_tok = challb.response_and_validation(client.net.key)
    v = chall_body.chall.encode("token")
    log.info('Exposing challenge on %s', v)
    tmp_chall_dict.set(v, ChallTok(chall_tok))

    cr = client.answer_challenge(challb, challb.response(client.net.key))
    log.debug('Acme CA responded to challenge request with: %s', cr)

    order = client.poll_and_finalize(order)

    return split_certificate_chain(order.fullchain_pem)
Esempio n. 11
0
def answer_dns_challenge(client, domain, challenge):
    """
    Compute the required answer and set it in the DNS record
    for the domain.
    """
    authorization = "{}.{}".format(
        base64.urlsafe_b64encode(
            challenge.get("token")).decode("ascii").replace("=", ""),
        base64.urlsafe_b64encode(
            client.key.thumbprint()).decode("ascii").replace("=", ""))

    dns_response = base64.urlsafe_b64encode(
        hashlib.sha256(
            authorization.encode()).digest()).decode("ascii").replace("=", "")

    # Let's update the DNS on our R53 account
    r53 = route53.connect_to_region(exec_region)
    zone = r53.get_zone(domain['r53_zone'])
    if zone == None:
        LOG.error("Cannot find R53 zone {}, are you controling it ?".format(
            domain['r53_zone']))
        exit(1)

    acme_domain = "_acme-challenge.{}".format(domain['name'])
    record = zone.find_records(name=acme_domain, type="TXT")
    if record:
        delete_status = zone.delete_record(record)

    add_status = zone.add_record("TXT", acme_domain, '"' + dns_response + '"')
    dns_updated = wait_until_sync(add_status)

    if dns_updated == False:
        LOG.error(
            "We updated R53 but the servers didn't sync within 10 seconds. Bailing out."
        )
        exit(1)

    ## Now, let's tell the ACME server that we are ready
    challenge_response = challenges.DNS01Response(
        key_authorization=authorization)
    challenge_resource = client.answer_challenge(challenge, challenge_response)
Esempio n. 12
0
def answer_challenge_simplehttp(domain, chall, client, account, challg_body, log):
    # Create a challenge response.
    resp = acme.challenges.SimpleHTTPResponse(tls=True)

    # See if we've already installed the file at the right location
    # and the response validates. If it validates locally, submit
    # it to the ACME server.
    try:
        ok = resp.simple_verify(chall, domain, account.public_key())
    except:
        # invalid JSON data yields untrapped errors
        ok = False

    if ok:
        log("Submitting challenge response at %s." % resp.uri(domain, chall))
        return client.answer_challenge(challg_body, resp)

    else:
        log("Validation file is not present.")
        raise NeedToInstallFile(
            resp.uri(domain, chall),
            resp.gen_validation(chall, account).json_dumps(),
            resp.CONTENT_TYPE,
        )
Esempio n. 13
0
    def _cron(self):
        ir_config_parameter = self.env['ir.config_parameter']
        domains = self._get_altnames()
        domain = domains[0]
        cert_file = os.path.join(_get_data_dir(), 'domain.crt')

        domains = self._cascade_domains(domains)
        for dom in domains:
            self._validate_domain(dom)

        if not self._should_run(cert_file, domains):
            return

        account_key = josepy.JWKRSA.load(self._get_key('account.key'))
        domain_key = self._get_key('domain.key')

        client = self._create_client(account_key)
        new_reg = acme.messages.NewRegistration(key=account_key.public_key(),
                                                terms_of_service_agreed=True)
        try:
            client.new_account(new_reg)
            _logger.info("Successfully registered.")
        except acme.errors.ConflictError as err:
            reg = acme.messages.Registration(key=account_key.public_key())
            reg_res = acme.messages.RegistrationResource(body=reg,
                                                         uri=err.location)
            client.query_registration(reg_res)
            _logger.info("Reusing existing account.")

        _logger.info('Making CSR for the following domains: %s', domains)
        csr = acme.crypto_util.make_csr(private_key_pem=domain_key,
                                        domains=domains)
        authzr = client.new_order(csr)

        # For each requested domain name we receive a list of challenges.
        # We only have to do one from each list.
        # HTTP challenges are the easiest, so do one of those if possible.
        # We can do DNS challenges too. There are other types that we don't
        # support.
        pending_responses = []

        prefer_dns = (self.env["ir.config_parameter"].get_param(
            "letsencrypt.prefer_dns") == "True")
        for authorizations in authzr.authorizations:
            http_challenges = [
                challenge for challenge in authorizations.body.challenges
                if challenge.chall.typ == TYPE_CHALLENGE_HTTP
            ]
            other_challenges = [
                challenge for challenge in authorizations.body.challenges
                if challenge.chall.typ != TYPE_CHALLENGE_HTTP
            ]
            if prefer_dns:
                ordered_challenges = other_challenges + http_challenges
            else:
                ordered_challenges = http_challenges + other_challenges
            for challenge in ordered_challenges:
                if challenge.chall.typ == TYPE_CHALLENGE_HTTP:
                    self._respond_challenge_http(challenge, account_key)
                    client.answer_challenge(challenge,
                                            acme.challenges.HTTP01Response())
                    break
                elif challenge.chall.typ == TYPE_CHALLENGE_DNS:
                    domain = authorizations.body.identifier.value
                    token = challenge.validation(account_key)
                    self._respond_challenge_dns(domain, token)
                    # We delay this because we wait for each domain.
                    # That takes less time if they've all already been changed.
                    pending_responses.append(
                        DNSUpdate(challenge=challenge,
                                  domain=domain,
                                  token=token))
                    break
            else:
                raise UserError(
                    _('Could not respond to letsencrypt challenges.'))

        if pending_responses:
            for update in pending_responses:
                self._wait_for_record(update.domain, update.token)
            # 1 minute was not always enough during testing, even once records
            # were visible locally
            _logger.info(
                "All TXT records found, waiting 5 minutes more to make sure.")
            time.sleep(300)
            for update in pending_responses:
                client.answer_challenge(update.challenge,
                                        acme.challenges.DNSResponse())

        # let them know we are done and they should check
        backoff = int(ir_config_parameter.get_param('letsencrypt.backoff', 3))
        deadline = datetime.now() + timedelta(minutes=backoff)
        try:
            order_resource = client.poll_and_finalize(authzr, deadline)
        except acme.errors.ValidationError as error:
            _logger.error("Let's Encrypt validation failed!")
            for authz in error.failed_authzrs:
                for challenge in authz.body.challenges:
                    _logger.error(str(challenge.error))
            raise

        with open(cert_file, 'w') as crt:
            crt.write(order_resource.fullchain_pem)
        _logger.info('SUCCESS: Certificate saved: %s', cert_file)
        reload_cmd = ir_config_parameter.get_param(
            'letsencrypt.reload_command', '')
        if reload_cmd.strip():
            self._call_cmdline(reload_cmd)
        else:
            _logger.warning("No reload command defined.")
Esempio n. 14
0
def create_or_update_cert(
        kv_cert_name,
        *domains,
        use_prod=False,
        keyvault_url='https://ponti-certs-kvjwxwal2p6n.vault.azure.net/',
        dns_zone_resource_group='damienpontifex.com-rg',
        dns_zone_name='damienpontifex.com',
        registration_email='*****@*****.**',
        dns_subscription_id="fa2cbf67-1293-4f9c-8884-a0379a9e0c64"):

    # Get directory
    if use_prod:
        directory_url = 'https://acme-v02.api.letsencrypt.org/directory'
        user_key_name = 'acme'
        issuance_period_months = 3
    else:
        directory_url = 'https://acme-staging-v02.api.letsencrypt.org/directory'
        user_key_name = 'acme-staging'
        issuance_period_months = 1

    credential = DefaultAzureCredential()

    challenge_handler = functools.partial(
        dns_challenge_handler,
        credential=credential,
        subscription_id=dns_subscription_id,
        dns_zone_resource_group=dns_zone_resource_group,
        dns_zone_name=dns_zone_name)

    cert_client = CertificateClient(vault_url=keyvault_url,
                                    credential=credential)

    #%%
    key = KeyVaultRSAKey(credential, keyvault_url, user_key_name)

    account_key = josepy.JWKRSA(key=key)
    client_network = acme.client.ClientNetwork(account_key)

    directory = messages.Directory.from_json(
        client_network.get(directory_url).json())

    client = acme.client.ClientV2(directory, client_network)

    new_regr = acme.messages.Registration.from_data(
        key=account_key,
        email=registration_email,
        terms_of_service_agreed=True)

    # Register or fetch account
    try:
        regr = client.new_account(new_regr)
        logger.info('Created new account')
    except acme.errors.ConflictError as e:
        regr = acme.messages.RegistrationResource(uri=e.location,
                                                  body=new_regr)
        regr = client.query_registration(regr)
        logger.info('Got existing account')

    cert_policy = CertificatePolicy(
        issuer_name='Unknown',
        subject_name=f'CN={domains[0]}',
        exportable=True,
        key_type=KeyType.rsa,
        key_size=2048,
        content_type=CertificateContentType.pkcs12,
        san_dns_names=domains[1:] if len(domains) > 1 else [],
        validity_in_months=issuance_period_months)

    try:
        # Check an existing certificate operation isn't in progress
        cert_op = cert_client.get_certificate_operation(
            certificate_name=kv_cert_name)
        logger.info('Existing cert operation in progress')
    except ResourceNotFoundError:
        cert_op = cert_client.begin_create_certificate(
            certificate_name=kv_cert_name, policy=cert_policy)
        logger.info('New cert operation')

    # cert_op = kvclient.create_certificate(KEYVAULT_URL, certificate_name=kv_cert_name, certificate_policy=cert_policy)
    cert_op_res = cert_op.result()
    cert_op_r = cert_client.get_certificate_operation(kv_cert_name)

    logger.info('Created certificate request in key vault')

    # Wrap with header and footer for pem to show certificate request
    csr_pem = "-----BEGIN CERTIFICATE REQUEST-----\n" + base64.b64encode(
        cert_op_r.csr).decode() + "\n-----END CERTIFICATE REQUEST-----\n"

    # Submit order
    order_resource = client.new_order(csr_pem)
    logger.info('Submitted order')

    # Challenges from order
    # Respond to challenges
    challenges_to_respond_to = list(
        challenge_handler(authorizations=order_resource.authorizations,
                          account_key=account_key))

    for dns_challenge in challenges_to_respond_to:
        # Perform challenge
        auth_response = client.answer_challenge(
            dns_challenge, dns_challenge.chall.response(account_key))

    logger.info('Answered challenges')

    # Poll for status
    # Finalize order
    # Download certificate
    final_order = client.poll_and_finalize(order_resource)

    logger.info('Finalised order')

    # Strip header and footer of BEGIN/END CERTIFICATE
    # with open('cert.pem', 'w') as f:
    #     f.write(final_order.fullchain_pem)

    certificate_vals = [
        val.replace('\n', '').encode()
        for val in final_order.fullchain_pem.split('-----')
        if 'CERTIFICATE' not in val and len(val.replace('\n', '')) != 0
    ]

    cert_client.merge_certificate(name=kv_cert_name,
                                  x509_certificates=certificate_vals)

    logger.info('Merged certificate back to key vault')
Esempio n. 15
0
def run_acme_reg_to_finish(domain, accnt_key, priv_key, hostname,
                           tmp_chall_dict, directory_url):
    '''Runs the entire process of ACME registeration'''

    client = create_v2_client(directory_url, accnt_key)

    # First we need to create a registration with the email address provided
    # and accept the terms of service
    log.info("Using boulder server %s", directory_url)

    client.net.account = client.new_account(
        messages.NewRegistration.from_data(terms_of_service_agreed=True))

    # Now we need to open an order and request our certificate

    # NOTE: We'll let ACME generate a CSR for our private key as there's
    # a lot of utility code it uses to generate the CSR in a specific
    # fashion. Better to use what LE provides than to roll our own as we
    # we doing with the v1 code
    #
    # This will also let us support multi-domain certificat requests in the
    # future, as well as mandate OCSP-Must-Staple if/when GL's HTTPS server
    # supports it
    csr = crypto_util.make_csr(priv_key, [hostname], False)
    order = client.new_order(csr)
    authzr = order.authorizations

    log.info('Created a new order for %s', hostname)

    # authrz is a list of Authorization resources, we need to find the
    # HTTP-01 challenge and use it
    for auth_req in authzr:  # pylint: disable=not-an-iterable
        for chall_body in auth_req.body.challenges:
            if isinstance(chall_body.chall, challenges.HTTP01):
                challb = chall_body
                break

    if challb is None:
        raise Exception("HTTP01 challenge unavailable!")

    response, chall_tok = challb.response_and_validation(client.net.key)
    v = chall_body.chall.encode("token")
    log.info('Exposing challenge on %s', v)
    tmp_chall_dict.set(v, ChallTok(chall_tok))

    cr = client.answer_challenge(challb, challb.response(client.net.key))
    log.debug('Acme CA responded to challenge request with: %s', cr)

    # Wrap this step and log the failure particularly here because this is
    # the expected point of failure for applications that are not reachable
    # from the public internet.
    try:
        order = client.poll_and_finalize(order)

    except messages.Error as error:
        log.err("Failed in request issuance step %s", error)
        raise

    # ACME V2 returns a full chain certificate, and ACME doesn't ship with
    # helper functions out of the box. Fortunately, searching through cerbot
    # this is easily enough to do with pyOpenSSL

    cert = load_certificate(FILETYPE_PEM, order.fullchain_pem)
    cert_str = dump_certificate(FILETYPE_PEM, cert).decode()
    chain_str = order.fullchain_pem[len(cert_str):].lstrip()

    # pylint: disable=no-member
    expr_date = convert_asn1_date(cert.get_notAfter())
    log.info('Retrieved cert using ACME that expires on %s', expr_date)

    return cert_str, chain_str
Esempio n. 16
0
def answer_dns_challenge(conf, client, domain, challenge):
    """
    Compute the required answer and set it in the DNS record
    for the domain.
    """
    authorization = "{}.{}".format(
        base64.urlsafe_b64encode(
            challenge.get("token")).decode("ascii").replace("=", ""),
        base64.urlsafe_b64encode(
            client.key.thumbprint()).decode("ascii").replace("=", ""))

    dns_response = base64.urlsafe_b64encode(
        hashlib.sha256(
            authorization.encode()).digest()).decode("ascii").replace("=", "")

    # Let's update the DNS on our R53 account
    zone_id = get_route53_zone_id(conf, domain['r53_zone'])
    if zone_id == None:
        LOG.error("Cannot determine zone id for zone '{0}'".format(
            domain['r53_zone']))
        return None

    LOG.info("Domain '{0}' has '{1}' for Id".format(domain['r53_zone'],
                                                    zone_id))

    zone_id = get_route53_zone_id(conf, domain['r53_zone'])
    if zone_id == None:
        LOG.error("Cannot find R53 zone {}, are you controling it ?".format(
            domain['r53_zone']))
        return None

    acme_domain = "_acme-challenge.{}".format(domain['name'])

    res = reset_route53_letsencrypt_record(conf, zone_id, domain['name'],
                                           acme_domain)
    if res == None:
        LOG.error(
            "An error occured while trying to remove a previous resource record. Skipping domain {0}"
            .format(domain['name']))
        return None

    add_status = create_route53_letsencrypt_record(conf, zone_id,
                                                   domain['name'], acme_domain,
                                                   'TXT',
                                                   '"' + dns_response + '"')
    if add_status == None:
        LOG.error(
            "An error occured while creating the dns record. Skipping domain {0}"
            .format(domain['name']))
        return None

    add_status = wait_letsencrypt_record_insync(conf, add_status)
    if add_status == None:
        LOG.error(
            "Cannot determine if the dns record has been correctly created. Skipping domain {0}"
            .format(domain['name']))
        return None

    if add_status == False:
        LOG.error(
            "We updated R53 but the servers didn't sync within 60 seconds. Skipping domain {0}"
            .format(domain['name']))
        return None

    if add_status is not True:
        LOG.error(
            "An unexpected result code has been returned. Please report this bug. Skipping domain {0}"
            .format(domain['name']))
        LOG.error("add_status={0}".format(add_status))
        return None

    ## Now, let's tell the ACME server that we are ready
    challenge_response = challenges.DNS01Response(
        key_authorization=authorization)
    challenge_resource = client.answer_challenge(challenge, challenge_response)

    if challenge_resource.body.error != None:
        return False

    return True
Esempio n. 17
0
def answer_dns_challenge(conf, client, domain, challenge):
    """
    Compute the required answer and set it in the DNS record
    for the domain.
    """
    zone_id = conf['r53_zone_id']
    account_key = client.net.key

    authorization = "{}.{}".format(
        base64.urlsafe_b64encode(
            challenge.get("token")).decode("ascii").replace("=", ""),
        base64.urlsafe_b64encode(
            account_key.thumbprint()).decode("ascii").replace("=", ""))

    dns_response = base64.urlsafe_b64encode(
        hashlib.sha256(
            authorization.encode()).digest()).decode("ascii").replace("=", "")

    LOG.info("authorization ='{0}' dns_response= '{1}' for Id".format(
        authorization, dns_response))

    #domain_name = '.'.join(domain['name'].split('.')[1:])
    domain_name = domain['name'].replace(
        '*.', '') if domain['name'].startswith('*.') else domain['name']
    acme_domain = "_acme-challenge.%s.%s" % (domain_name, conf['r53_zone'])

    res = reset_route53_letsencrypt_record(zone_id, acme_domain)

    if res is None:
        LOG.error(
            "An error occured while trying to remove a "
            "previous resource record. Skipping domain %s", domain_name)
        return None

    add_status = create_route53_letsencrypt_record(zone_id, acme_domain,
                                                   '"' + dns_response + '"')
    if add_status is None:
        LOG.error(
            "An error occured while creating the dns record. "
            "Skipping domain %s", domain_name)
        return None

    add_status = wait_letsencrypt_record_insync(add_status)
    if add_status is None:
        LOG.error(
            "Cannot determine if the dns record has been correctly created. "
            "Skipping domain {0}".format(domain_name))
        return None

    if add_status is False:
        LOG.error(
            "We updated R53 but the servers didn't sync within 60 seconds. "
            "Skipping domain {0}".format(domain_name))
        return None

    if add_status is not True:
        LOG.error(
            "An unexpected result code has been returned. Please report this bug. "
            "Skipping domain {0}".format(domain_name))
        LOG.error("add_status={0}".format(add_status))
        return None

    challenge_response = challenges.DNS01Response(
        key_authorization=authorization)
    challenge_resource = client.answer_challenge(challenge, challenge_response)

    if challenge_resource.body.error is not None:
        return False

    return True