def check_challenge_dns_err(chalType): """ check_challenge_dns_err tests that performing an ACME challenge of the specified type to a hostname that is configured to return SERVFAIL for all queries produces the correct problem type and detail message. """ client = chisel2.make_client() # Create a random domains. d = random_domain() # Configure the chall srv to SERVFAIL all queries for that domain. challSrv.add_servfail_response(d) # Expect a DNS problem with a detail that matches a regex expectedProbType = "dns" expectedProbRegex = re.compile( r"DNS problem: SERVFAIL looking up (A|AAAA|TXT|CAA) for {0}".format(d)) # Try and issue for the domain with the given challenge type. failed = False try: chisel2.auth_and_issue([d], client=client, chall_type=chalType) except acme_errors.ValidationError as e: # Mark that the auth_and_issue failed failed = True # Extract the failed challenge from each failed authorization for authzr in e.failed_authzrs: c = None if chalType == "http-01": c = chisel2.get_chall(authzr, challenges.HTTP01) elif chalType == "dns-01": c = chisel2.get_chall(authzr, challenges.DNS01) elif chalType == "tls-alpn-01": c = chisel2.get_chall(authzr, challenges.TLSALPN01) else: raise Exception( "Invalid challenge type requested: {0}".format(challType)) # The failed challenge's error should match expected error = c.error if error is None or error.typ != "urn:ietf:params:acme:error:{0}".format( expectedProbType): raise Exception("Expected {0} prob, got {1}".format( expectedProbType, error.typ)) if not expectedProbRegex.match(error.detail): raise Exception( "Prob detail did not match expectedProbRegex, got \"{0}\"". format(error.detail)) finally: challSrv.remove_servfail_response(d) # If there was no exception that means something went wrong. The test should fail. if failed is False: raise Exception( "No problem generated issuing for broken DNS identifier")
def test_delete_unused_challenges(): order = chisel2.auth_and_issue([random_domain()], chall_type="dns-01") a = order.authorizations[0] if len(a.body.challenges) != 1: raise Exception("too many challenges (%d) left after validation" % len(a.body.challenges)) if not isinstance(a.body.challenges[0].chall, challenges.DNS01): raise Exception("wrong challenge type left after validation") # intentionally fail a challenge client = chisel2.make_client() csr_pem = chisel2.make_csr([random_domain()]) order = client.new_order(csr_pem) c = chisel2.get_chall(order.authorizations[0], challenges.DNS01) client.answer_challenge(c, c.response(client.net.key)) for _ in range(5): a, _ = client.poll(order.authorizations[0]) if a.body.status == Status("invalid"): break time.sleep(1) if len(a.body.challenges) != 1: raise Exception( "too many challenges (%d) left after failed validation" % len(a.body.challenges)) if not isinstance(a.body.challenges[0].chall, challenges.DNS01): raise Exception("wrong challenge type left after validation")
def test_http2_http01_challenge(): """ test_http2_http01_challenge tests that an HTTP-01 challenge made to a HTTP/2 server fails with a specific error message for this case. """ client = chisel2.make_client() hostname = "fake.h2.example.com" # Add an A record for the test server to ensure the VA's requests are directed # to the interface that we bind the FakeH2ServerHandler to. challSrv.add_a_record(hostname, ["10.88.88.88"]) # Allow socket address reuse on the base TCPServer class. Failing to do this # causes subsequent integration tests to fail with "Address in use" errors even # though this test _does_ call shutdown() and server_close(). Even though the # server was shut-down Python's socket will be in TIME_WAIT because of prev. client # connections. Having the TCPServer set SO_REUSEADDR on the socket solves # the problem. socketserver.TCPServer.allow_reuse_address = True # Create, start, and wait for a fake HTTP/2 server. server = socketserver.TCPServer(('10.88.88.88', 5002), FakeH2ServerHandler) thread = threading.Thread(target=server.serve_forever) thread.daemon = False thread.start() wait_for_tcp_server('10.88.88.88', 5002) # Issuing an HTTP-01 challenge for this hostname should produce a connection # problem with an error specific to the HTTP/2 misconfiguration. expectedError = "Server is speaking HTTP/2 over HTTP" try: chisel2.auth_and_issue([hostname], client=client, chall_type="http-01") except acme_errors.ValidationError as e: for authzr in e.failed_authzrs: c = chisel2.get_chall(authzr, challenges.HTTP01) error = c.error if error is None or error.typ != "urn:ietf:params:acme:error:connection": raise Exception("Expected connection prob, got %s" % (error.__str__())) if not error.detail.endswith(expectedError): raise Exception("Expected prob detail ending in %s, got %s" % (expectedError, error.detail)) finally: server.shutdown() server.server_close() thread.join()
def test_http2_http01_challenge(): """ test_http2_http01_challenge tests that an HTTP-01 challenge made to a HTTP/2 server fails with a specific error message for this case. """ client = chisel2.make_client() hostname = "fake.h2.example.com" # Add an A record for the test server to ensure the VA's requests are directed # to the interface that we bind the FakeH2ServerHandler to. challSrv.add_a_record(hostname, ["10.88.88.88"]) # Allow socket address reuse on the base TCPServer class. Failing to do this # causes subsequent integration tests to fail with "Address in use" errors even # though this test _does_ call shutdown() and server_close(). Even though the # server was shut-down Python's socket will be in TIME_WAIT because of prev. client # connections. Having the TCPServer set SO_REUSEADDR on the socket solves # the problem. socketserver.TCPServer.allow_reuse_address = True # Create, start, and wait for a fake HTTP/2 server. server = socketserver.TCPServer(('10.88.88.88', 5002), FakeH2ServerHandler) thread = threading.Thread(target = server.serve_forever) thread.daemon = False thread.start() wait_for_tcp_server('10.88.88.88', 5002) # Issuing an HTTP-01 challenge for this hostname should produce a connection # problem with an error specific to the HTTP/2 misconfiguration. expectedError = "Server is speaking HTTP/2 over HTTP" try: chisel2.auth_and_issue([hostname], client=client, chall_type="http-01") except acme_errors.ValidationError as e: for authzr in e.failed_authzrs: c = chisel2.get_chall(authzr, challenges.HTTP01) error = c.error if error is None or error.typ != "urn:ietf:params:acme:error:connection": raise Exception("Expected connection prob, got %s" % (error.__str__())) if not error.detail.endswith(expectedError): raise Exception("Expected prob detail ending in %s, got %s" % (expectedError, error.detail)) finally: server.shutdown() server.server_close() thread.join()
def test_http_challenge_broken_redirect(): """ test_http_challenge_broken_redirect tests that a common webserver mis-configuration receives the correct specialized error message when attempting an HTTP-01 challenge. """ client = chisel2.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a broken HTTP redirect similar to a sort we see frequently "in the wild" challengePath = "/.well-known/acme-challenge/{0}".format(token) redirect = "http://{0}.well-known/acme-challenge/bad-bad-bad".format(d) challSrv.add_http_redirect(challengePath, redirect) # Expect the specialized error message expectedError = "Fetching {0}: Invalid host in redirect target \"{1}.well-known\". Check webserver config for missing '/' in redirect target.".format( redirect, d) # NOTE(@cpu): Can't use chisel2.expect_problem here because it doesn't let # us interrogate the detail message easily. try: chisel2.auth_and_issue([d], client=client, chall_type="http-01") except acme_errors.ValidationError as e: for authzr in e.failed_authzrs: c = chisel2.get_chall(authzr, challenges.HTTP01) error = c.error if error is None or error.typ != "urn:ietf:params:acme:error:connection": raise Exception("Expected connection prob, got %s" % (error.__str__())) if error.detail != expectedError: raise Exception("Expected prob detail %s, got %s" % (expectedError, error.detail)) challSrv.remove_http_redirect(challengePath)
def test_http_challenge_broken_redirect(): """ test_http_challenge_broken_redirect tests that a common webserver mis-configuration receives the correct specialized error message when attempting an HTTP-01 challenge. """ client = chisel2.make_client() # Create an authz for a random domain and get its HTTP-01 challenge token d, chall = rand_http_chall(client) token = chall.encode("token") # Create a broken HTTP redirect similar to a sort we see frequently "in the wild" challengePath = "/.well-known/acme-challenge/{0}".format(token) redirect = "http://{0}.well-known/acme-challenge/bad-bad-bad".format(d) challSrv.add_http_redirect( challengePath, redirect) # Expect the specialized error message expectedError = "Fetching {0}: Invalid host in redirect target \"{1}.well-known\". Check webserver config for missing '/' in redirect target.".format(redirect, d) # NOTE(@cpu): Can't use chisel2.expect_problem here because it doesn't let # us interrogate the detail message easily. try: chisel2.auth_and_issue([d], client=client, chall_type="http-01") except acme_errors.ValidationError as e: for authzr in e.failed_authzrs: c = chisel2.get_chall(authzr, challenges.HTTP01) error = c.error if error is None or error.typ != "urn:ietf:params:acme:error:connection": raise Exception("Expected connection prob, got %s" % (error.__str__())) if error.detail != expectedError: raise Exception("Expected prob detail %s, got %s" % (expectedError, error.detail)) challSrv.remove_http_redirect(challengePath)