def test_wildcard_authz_reuse(): """ Test that an authorization for a base domain obtained via HTTP-01 isn't reused when issuing a wildcard for that base domain later on. """ # Create one client to reuse across multiple issuances client = chisel2.make_client(None) # Pick a random domain to issue for domains = [ random_domain() ] csr_pem = chisel2.make_csr(domains) # Submit an order for the name order = client.new_order(csr_pem) # Complete the order via an HTTP-01 challenge cleanup = chisel2.do_http_challenges(client, order.authorizations) try: order = client.poll_and_finalize(order) finally: cleanup() # Now try to issue a wildcard for the random domain domains[0] = "*." + domains[0] csr_pem = chisel2.make_csr(domains) order = client.new_order(csr_pem) # We expect all of the returned authorizations to be pending status for authz in order.authorizations: if authz.body.status != Status("pending"): raise Exception("order for %s included a non-pending authorization (status: %s) from a previous HTTP-01 order" % ((domains), str(authz.body.status)))
def test_wildcard_authz_reuse(): """ Test that an authorization for a base domain obtained via HTTP-01 isn't reused when issuing a wildcard for that base domain later on. """ # Create one client to reuse across multiple issuances client = chisel2.make_client(None) # Pick a random domain to issue for domains = [random_domain()] csr_pem = chisel2.make_csr(domains) # Submit an order for the name order = client.new_order(csr_pem) # Complete the order via an HTTP-01 challenge cleanup = chisel2.do_http_challenges(client, order.authorizations) try: order = client.poll_and_finalize(order) finally: cleanup() # Now try to issue a wildcard for the random domain domains[0] = "*." + domains[0] csr_pem = chisel2.make_csr(domains) order = client.new_order(csr_pem) # We expect all of the returned authorizations to be pending status for authz in order.authorizations: if authz.body.status != Status("pending"): raise Exception( "order for %s included a non-pending authorization (status: %s) from a previous HTTP-01 order" % ((domains), str(authz.body.status)))
def test_failed_validation_limit(): """ Fail a challenge repeatedly for the same domain, with the same account. Once we reach the rate limit we should get a rateLimitedError. Note that this depends on the specific threshold configured in rate-limit-policies.yml. This also incidentally tests a fix for https://github.com/letsencrypt/boulder/issues/4329. We expect to get ValidationErrors, eventually followed by a rate limit error. """ domain = "fail." + random_domain() csr_pem = chisel2.make_csr([domain]) client = chisel2.make_client() threshold = 3 for _ in range(threshold): order = client.new_order(csr_pem) chall = order.authorizations[0].body.challenges[0] client.answer_challenge(chall, chall.response(client.net.key)) try: client.poll_and_finalize(order) except errors.ValidationError as e: pass chisel2.expect_problem( "urn:ietf:params:acme:error:rateLimited", lambda: chisel2.auth_and_issue([domain], client=client))
def test_revoke_by_privkey(): client = chisel2.make_client(None) domains = [random_domain()] key = OpenSSL.crypto.PKey() key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) csr_pem = chisel2.make_csr(domains) order = client.new_order(csr_pem) cleanup = chisel2.do_http_challenges(client, order.authorizations) try: order = client.poll_and_finalize(order) finally: cleanup() # Create a new client with the JWK as the cert private key jwk = josepy.JWKRSA(key=key) net = acme_client.ClientNetwork(key, user_agent="Boulder integration tester") directory = Directory.from_json(net.get(chisel2.DIRECTORY_V2).json()) new_client = acme_client.ClientV2(directory, net) cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, order.fullchain_pem) client.revoke(josepy.ComparableX509(cert), 0)
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_new_order_policy_errs(): """ Test that creating an order with policy blocked identifiers returns a problem with subproblems. """ client = chisel2.make_client(None) # 'in-addr.arpa' is present in `test/hostname-policy.yaml`'s # HighRiskBlockedNames list. csr_pem = chisel2.make_csr( ["out-addr.in-addr.arpa", "between-addr.in-addr.arpa"]) # With two policy blocked names in the order we expect to get back a top # level rejectedIdentifier with a detail message that references # subproblems. # # TODO(@cpu): After https://github.com/certbot/certbot/issues/7046 is # implemented in the upstream `acme` module this test should also ensure the # subproblems are properly represented. ok = False try: order = client.new_order(csr_pem) except messages.Error as e: ok = True if e.typ != "urn:ietf:params:acme:error:rejectedIdentifier": raise (Exception( 'Expected rejectedIdentifier type problem, got {0}'.format( e.typ))) if e.detail != 'Error creating new order :: Cannot issue for "out-addr.in-addr.arpa": Policy forbids issuing for name (and 1 more problems. Refer to sub-problems for more information.)': raise (Exception('Order problem detail did not match expected')) if not ok: raise Exception('Expected problem, got no error')
def test_revoke_by_privkey(): client = chisel2.make_client(None) domains = [random_domain()] key = OpenSSL.crypto.PKey() key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) csr_pem = chisel2.make_csr(domains) order = client.new_order(csr_pem) cleanup = chisel2.do_http_challenges(client, order.authorizations) try: order = client.poll_and_finalize(order) finally: cleanup() # Create a new client with the JWK as the cert private key jwk = josepy.JWKRSA(key=key) net = acme_client.ClientNetwork(key, user_agent="Boulder integration tester") directory = Directory.from_json(net.get(chisel2.DIRECTORY_V2).json()) new_client = acme_client.ClientV2(directory, net) cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, order.fullchain_pem) reset_akamai_purges() client.revoke(josepy.ComparableX509(cert), 0) cert_file_pem = os.path.join(tempdir, "revokeme.pem") with open(cert_file_pem, "w") as f: f.write(OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_PEM, cert).decode()) ee_ocsp_url = "http://localhost:4002" verify_revocation(cert_file_pem, "test/test-ca2.pem", ee_ocsp_url) verify_akamai_purge()
def test_revoke_by_privkey(): client = chisel2.make_client(None) domains = [random_domain()] key = OpenSSL.crypto.PKey() key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048) key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key) csr_pem = chisel2.make_csr(domains) order = client.new_order(csr_pem) cleanup = chisel2.do_http_challenges(client, order.authorizations) try: order = client.poll_and_finalize(order) finally: cleanup() # Create a new client with the JWK as the cert private key jwk = josepy.JWKRSA(key=key) net = acme_client.ClientNetwork(key, user_agent="Boulder integration tester") directory = Directory.from_json(net.get(chisel2.DIRECTORY_V2).json()) new_client = acme_client.ClientV2(directory, net) cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, order.fullchain_pem) reset_akamai_purges() client.revoke(josepy.ComparableX509(cert), 0) cert_file_pem = os.path.join(tempdir, "revokeme.pem") with open(cert_file_pem, "w") as f: f.write( OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert).decode()) ee_ocsp_url = "http://localhost:4002" verify_ocsp(cert_file_pem, "test/test-ca2.pem", ee_ocsp_url, "revoked") verify_akamai_purge()
def multiva_setup(client, guestlist): """ Create a testing hostname and the multiva server setup. This will block until the server is ready. The returned cleanup function should be used to stop the server. The first bounceFirst requests to the server will be sent to the real challtestsrv for a good answer, the rest will get a bad answer. """ hostname = random_domain() csr_pem = chisel2.make_csr([hostname]) order = client.new_order(csr_pem) authz = order.authorizations[0] chall = None for c in authz.body.challenges: if isinstance(c.chall, challenges.HTTP01): chall = c.chall if chall is None: raise Exception("No HTTP-01 challenge found for random domain authz") token = chall.encode("token") # Calculate the challenge's keyauth so we can add a good keyauth response on # the real challtestsrv that we redirect VIP requests to. resp = chall.response(client.net.key) keyauth = resp.key_authorization challSrv.add_http01_response(token, keyauth) # Add an A record for the domains to ensure the VA's requests are directed # to the interface that we bound the HTTPServer to. challSrv.add_a_record(hostname, ["10.88.88.88"]) # Add an A record for the redirect target that sends it to the real chall # test srv for a valid HTTP-01 response. redirHostname = "pebble-challtestsrv.example.com" challSrv.add_a_record(redirHostname, ["10.77.77.77"]) # Start a simple python HTTP server on port 5002 in its own thread. # NOTE(@cpu): The pebble-challtestsrv binds 10.77.77.77:5002 for HTTP-01 # challenges so we must use the 10.88.88.88 address for the throw away # server for this test and add a mock DNS entry that directs the VA to it. redirect = "http://{0}/.well-known/acme-challenge/{1}".format( redirHostname, token) httpd = HTTPServer(('10.88.88.88', 5002), BouncerHTTPRequestHandler(redirect, guestlist)) thread = threading.Thread(target=httpd.serve_forever) thread.daemon = False thread.start() def cleanup(): # Remove the challtestsrv mocks challSrv.remove_a_record(hostname) challSrv.remove_a_record(redirHostname) challSrv.remove_http01_response(token) # Shut down the HTTP server gracefully and join on its thread. httpd.shutdown() httpd.server_close() thread.join() return hostname, cleanup
def multiva_setup(client, guestlist): """ Create a testing hostname and the multiva server setup. This will block until the server is ready. The returned cleanup function should be used to stop the server. The first bounceFirst requests to the server will be sent to the real challtestsrv for a good answer, the rest will get a bad answer. """ hostname = random_domain() csr_pem = chisel2.make_csr([hostname]) order = client.new_order(csr_pem) authz = order.authorizations[0] chall = None for c in authz.body.challenges: if isinstance(c.chall, challenges.HTTP01): chall = c.chall if chall is None: raise Exception("No HTTP-01 challenge found for random domain authz") token = chall.encode("token") # Calculate the challenge's keyauth so we can add a good keyauth response on # the real challtestsrv that we redirect VIP requests to. resp = chall.response(client.net.key) keyauth = resp.key_authorization challSrv.add_http01_response(token, keyauth) # Add an A record for the domains to ensure the VA's requests are directed # to the interface that we bound the HTTPServer to. challSrv.add_a_record(hostname, ["10.88.88.88"]) # Add an A record for the redirect target that sends it to the real chall # test srv for a valid HTTP-01 response. redirHostname = "pebble-challtestsrv.example.com" challSrv.add_a_record(redirHostname, ["10.77.77.77"]) # Start a simple python HTTP server on port 5002 in its own thread. # NOTE(@cpu): The pebble-challtestsrv binds 10.77.77.77:5002 for HTTP-01 # challenges so we must use the 10.88.88.88 address for the throw away # server for this test and add a mock DNS entry that directs the VA to it. redirect = "http://{0}/.well-known/acme-challenge/{1}".format( redirHostname, token) httpd = HTTPServer(('10.88.88.88', 5002), BouncerHTTPRequestHandler(redirect, guestlist)) thread = threading.Thread(target = httpd.serve_forever) thread.daemon = False thread.start() def cleanup(): # Remove the challtestsrv mocks challSrv.remove_a_record(hostname) challSrv.remove_a_record(redirHostname) challSrv.remove_http01_response(token) # Shut down the HTTP server gracefully and join on its thread. httpd.shutdown() httpd.server_close() thread.join() return hostname, cleanup
def test_order_reuse_failed_authz(): """ Test that creating an order for a domain name, failing an authorization in that order, and submitting another new order request for the same name doesn't reuse a failed authorizaton in the new order. """ client = chisel2.make_client(None) domains = [random_domain()] csr_pem = chisel2.make_csr(domains) order = client.new_order(csr_pem) firstOrderURI = order.uri # Pick the first authz's first challenge, doesn't matter what type it is chall_body = order.authorizations[0].body.challenges[0] # Answer it, but with nothing set up to solve the challenge request client.answer_challenge(chall_body, chall_body.response(client.net.key)) deadline = datetime.datetime.now() + datetime.timedelta(seconds=60) authzFailed = False try: # Poll the order's authorizations until they are non-pending, a timeout # occurs, or there is an invalid authorization status. client.poll_authorizations(order, deadline) except acme_errors.ValidationError as e: # We expect there to be a ValidationError from one of the authorizations # being invalid. authzFailed = True # If the poll ended and an authz's status isn't invalid then we reached the # deadline, fail the test if not authzFailed: raise Exception("timed out waiting for order %s to become invalid" % firstOrderURI) # Make another order with the same domains order = client.new_order(csr_pem) # It should not be the same order as before if order.uri == firstOrderURI: raise Exception("new-order for %s returned a , now-invalid, order" % domains) # We expect all of the returned authorizations to be pending status for authz in order.authorizations: if authz.body.status != Status("pending"): raise Exception( "order for %s included a non-pending authorization (status: %s) from a previous order" % ((domains), str(authz.body.status))) # We expect the new order can be fulfilled cleanup = chisel2.do_http_challenges(client, order.authorizations) try: order = client.poll_and_finalize(order) finally: cleanup()
def rand_http_chall(client): d = random_domain() csr_pem = chisel2.make_csr([d]) order = client.new_order(csr_pem) authzs = order.authorizations for a in authzs: for c in a.body.challenges: if isinstance(c.chall, challenges.HTTP01): return d, c.chall raise Exception("No HTTP-01 challenge found for random domain authz")
def test_order_reuse_failed_authz(): """ Test that creating an order for a domain name, failing an authorization in that order, and submitting another new order request for the same name doesn't reuse a failed authorizaton in the new order. """ client = make_client(None) domains = [random_domain()] csr_pem = make_csr(domains) order = client.new_order(csr_pem) firstOrderURI = order.uri # Pick the first authz's first challenge, doesn't matter what type it is chall_body = order.authorizations[0].body.challenges[0] # Answer it, but with nothing set up to solve the challenge request client.answer_challenge(chall_body, chall_body.response(client.key)) # Poll for a fixed amount of time checking for the order to become invalid # from the authorization attempt initiated above failing deadline = datetime.datetime.now() + datetime.timedelta(seconds=60) while datetime.datetime.now() < deadline: time.sleep(1) updatedOrder = requests.get(firstOrderURI).json() if updatedOrder['status'] == "invalid": break # If the loop ended and the status isn't invalid then we reached the # deadline waiting for the order to become invalid, fail the test if updatedOrder['status'] != "invalid": raise Exception("timed out waiting for order %s to become invalid" % firstOrderURI) # Make another order with the same domains order = client.new_order(csr_pem) # It should not be the same order as before if order.uri == firstOrderURI: raise Exception("new-order for %s returned a , now-invalid, order" % domains) # We expect all of the returned authorizations to be pending status for authz in order.authorizations: if authz.body.status != Status("pending"): raise Exception( "order for %s included a non-pending authorization (status: %s) from a previous order" % ((domains), str(authz.body.status))) # We expect the new order can be fulfilled cleanup = do_http_challenges(client, order.authorizations) try: order = client.poll_order_and_request_issuance(order) finally: cleanup()
def test_auth_deactivation_v2(): client = chisel2.make_client(None) csr_pem = chisel2.make_csr([random_domain()]) order = client.new_order(csr_pem) resp = client.deactivate_authorization(order.authorizations[0]) if resp.body.status is not messages.STATUS_DEACTIVATED: raise Exception("unexpected authorization status") order = chisel2.auth_and_issue([random_domain()], client=client) resp = client.deactivate_authorization(order.authorizations[0]) if resp.body.status is not messages.STATUS_DEACTIVATED: raise Exception("unexpected authorization status")
def test_order_reuse_failed_authz(): """ Test that creating an order for a domain name, failing an authorization in that order, and submitting another new order request for the same name doesn't reuse a failed authorizaton in the new order. """ client = chisel2.make_client(None) domains = [ random_domain() ] csr_pem = chisel2.make_csr(domains) order = client.new_order(csr_pem) firstOrderURI = order.uri # Pick the first authz's first challenge, doesn't matter what type it is chall_body = order.authorizations[0].body.challenges[0] # Answer it, but with nothing set up to solve the challenge request client.answer_challenge(chall_body, chall_body.response(client.net.key)) # Poll for a fixed amount of time checking for the order to become invalid # from the authorization attempt initiated above failing deadline = datetime.datetime.now() + datetime.timedelta(seconds=60) while datetime.datetime.now() < deadline: time.sleep(1) updatedOrder = requests.get(firstOrderURI).json() if updatedOrder['status'] == "invalid": break # If the loop ended and the status isn't invalid then we reached the # deadline waiting for the order to become invalid, fail the test if updatedOrder['status'] != "invalid": raise Exception("timed out waiting for order %s to become invalid" % firstOrderURI) # Make another order with the same domains order = client.new_order(csr_pem) # It should not be the same order as before if order.uri == firstOrderURI: raise Exception("new-order for %s returned a , now-invalid, order" % domains) # We expect all of the returned authorizations to be pending status for authz in order.authorizations: if authz.body.status != Status("pending"): raise Exception("order for %s included a non-pending authorization (status: %s) from a previous order" % ((domains), str(authz.body.status))) # We expect the new order can be fulfilled cleanup = chisel2.do_http_challenges(client, order.authorizations) try: order = client.poll_and_finalize(order) finally: cleanup()
def test_z2_disable(): """Test the DisableAuthz2Orders feature flag. Only runs when that flag is set (that is, not in CONFIG_NEXT mode).""" if CONFIG_NEXT: return response = requests.get(z2_disable_authz.uri) if response.status_code != 404: raise Exception("Expected authorization to be disabled. Got %s" % response) response = requests.get(z2_disable_order.uri) if response.status_code != 404: raise Exception("Expected order to be disabled. Got %s" % response) o = z2_disable_client.new_order( chisel2.make_csr([z2_disable_authz.body.identifier.value])) if o.authorizations[0].uri == z2_disable_authz.uri: raise Exception("Expected authzv2 authorization not to be reused")
def test_order_finalize_early(): """ Test that finalizing an order before its fully authorized results in the order having an error set and the status being invalid. """ # Create a client client = chisel2.make_client(None) # Create a random domain and a csr domains = [random_domain()] csr_pem = chisel2.make_csr(domains) # Create an order for the domain order = client.new_order(csr_pem) deadline = datetime.datetime.now() + datetime.timedelta(seconds=5) # Finalizing an order early should generate an orderNotReady error. chisel2.expect_problem("urn:ietf:params:acme:error:orderNotReady", lambda: client.finalize_order(order, deadline))
def test_order_finalize_early(): """ Test that finalizing an order before its fully authorized results in the order having an error set and the status being invalid. """ # Create a client client = chisel2.make_client(None) # Create a random domain and a csr domains = [ random_domain() ] csr_pem = chisel2.make_csr(domains) # Create an order for the domain order = client.new_order(csr_pem) deadline = datetime.datetime.now() + datetime.timedelta(seconds=5) # Finalizing an order early should generate an orderNotReady error. chisel2.expect_problem("urn:ietf:params:acme:error:orderNotReady", lambda: client.finalize_order(order, deadline))
def test_order_finalize_early(): """ Test that finalizing an order before its fully authorized results in the order having an error set and the status being invalid. """ # Create a client client = make_client(None) # Create a random domain and a csr domains = [random_domain()] csr_pem = make_csr(domains) # Create an order for the domain order = client.new_order(csr_pem) # Finalize the order without doing anything with the authorizations. YOLO # We expect this to generate an unauthorized error. chisel2.expect_problem( "urn:ietf:params:acme:error:unauthorized", lambda: client.net.post( order.body.finalize, CertificateRequest(csr=order.csr))) # Poll for a fixed amount of time checking for the order to become invalid # from the early finalization attempt initiated above failing deadline = datetime.datetime.now() + datetime.timedelta(seconds=5) while datetime.datetime.now() < deadline: time.sleep(1) updatedOrder = requests.get(order.uri).json() if updatedOrder['status'] == "invalid": break # If the loop ended and the status isn't invalid then we reached the # deadline waiting for the order to become invalid, fail the test if updatedOrder['status'] != "invalid": raise Exception("timed out waiting for order %s to become invalid" % order.uri) # The order should have an error with the expected type if updatedOrder['error'][ 'type'] != 'urn:ietf:params:acme:error:unauthorized': raise Exception("order %s has incorrect error field type: \"%s\"" % (order.uri, updatedOrder['error']['type']))
def test_overlapping_wildcard(): """ Test issuance for a random domain and a wildcard version of the same domain using DNS-01. This should result in *two* distinct authorizations. """ domain = random_domain() domains = [ domain, "*."+domain ] client = chisel2.make_client(None) csr_pem = chisel2.make_csr(domains) order = client.new_order(csr_pem) authzs = order.authorizations if len(authzs) != 2: raise Exception("order for %s had %d authorizations, expected 2" % (domains, len(authzs))) cleanup = chisel2.do_dns_challenges(client, authzs) try: order = client.poll_and_finalize(order) finally: cleanup()
def test_overlapping_wildcard(): """ Test issuance for a random domain and a wildcard version of the same domain using DNS-01. This should result in *two* distinct authorizations. """ domain = random_domain() domains = [domain, "*." + domain] client = chisel2.make_client(None) csr_pem = chisel2.make_csr(domains) order = client.new_order(csr_pem) authzs = order.authorizations if len(authzs) != 2: raise Exception("order for %s had %d authorizations, expected 2" % (domains, len(authzs))) cleanup = chisel2.do_dns_challenges(client, authzs) try: order = client.poll_and_finalize(order) finally: cleanup()
def test_order_finalize_early(): """ Test that finalizing an order before its fully authorized results in the order having an error set and the status being invalid. """ # Create a client client = chisel2.make_client(None) # Create a random domain and a csr domains = [ random_domain() ] csr_pem = chisel2.make_csr(domains) # Create an order for the domain order = client.new_order(csr_pem) deadline = datetime.datetime.now() + datetime.timedelta(seconds=5) # Finalizing an order early should generate an unauthorized error and we # should check that the order is invalidated. chisel2.expect_problem("urn:ietf:params:acme:error:unauthorized", lambda: client.finalize_order(order, deadline)) # Poll for a fixed amount of time checking for the order to become invalid # from the early finalization attempt initiated above failing while datetime.datetime.now() < deadline: time.sleep(1) updatedOrder = requests.get(order.uri).json() if updatedOrder['status'] == "invalid": break # If the loop ended and the status isn't invalid then we reached the # deadline waiting for the order to become invalid, fail the test if updatedOrder['status'] != "invalid": raise Exception("timed out waiting for order %s to become invalid" % order.uri) # The order should have an error with the expected type if updatedOrder['error']['type'] != 'urn:ietf:params:acme:error:unauthorized': raise Exception("order %s has incorrect error field type: \"%s\"" % (order.uri, updatedOrder['error']['type']))