Пример #1
0
    def test_skip_valid_authorizations(self):
        """ Authorizations that are already valid should be skipped """
        # issue a valid cert
        self.test_success_domain()

        # add a logging handler that captures the info log output
        log_output = StringIO()
        debug_handler = logging.StreamHandler(log_output)
        acme_tiny.LOGGER.addHandler(debug_handler)

        # issue the cert again, where challenges should already be valid
        old_stdout = sys.stdout
        sys.stdout = StringIO()
        result = acme_tiny.main([
            "--account-key", self.KEYS['account_key'].name,
            "--csr", self.KEYS['domain_csr'].name,
            "--acme-dir", self.tempdir,
            "--directory-url", self.DIR_URL,
            "--check-port", self.check_port,
        ])
        sys.stdout.seek(0)
        crt = sys.stdout.read().encode("utf8")
        sys.stdout = old_stdout
        log_output.seek(0)
        log_string = log_output.read().encode("utf8")

        # remove logging capture
        acme_tiny.LOGGER.removeHandler(debug_handler)

        # should say the domain is already verified
        self.assertIn("Already verified: {0}, skipping...".format(DOMAIN), log_string.decode("utf8"))
Пример #2
0
 def test_contact(self):
     """ Make sure optional contact details can be set """
     # add a logging handler that captures the info log output
     log_output = StringIO()
     debug_handler = logging.StreamHandler(log_output)
     acme_tiny.LOGGER.addHandler(debug_handler)
     # call acme_tiny with new contact details
     old_stdout = sys.stdout
     sys.stdout = StringIO()
     result = acme_tiny.main([
         "--account-key", self.KEYS['account_key'].name,
         "--csr", self.KEYS['domain_csr'].name,
         "--acme-dir", self.tempdir,
         "--directory-url", self.DIR_URL,
         "--check-port", self.check_port,
         "--contact", "mailto:[email protected]", "mailto:[email protected]",
     ])
     sys.stdout.seek(0)
     crt = sys.stdout.read().encode("utf8")
     sys.stdout = old_stdout
     log_output.seek(0)
     log_string = log_output.read().encode("utf8")
     # make sure the certificate was issued and the contact details were updated
     out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(crt)
     self.assertIn(self.ca_issued_string, out.decode("utf8"))
     self.assertTrue((  # can be in either order
         "Updated contact details:\nmailto:[email protected]\nmailto:[email protected]" in log_string.decode("utf8")
         or "Updated contact details:\nmailto:[email protected]\nmailto:[email protected]" in log_string.decode("utf8")
     ))
     # remove logging capture
     acme_tiny.LOGGER.removeHandler(debug_handler)
Пример #3
0
 def test_contact(self):
     """ Make sure optional contact details can be set """
     # add a logging handler that captures the info log output
     log_output = StringIO()
     debug_handler = logging.StreamHandler(log_output)
     acme_tiny.LOGGER.addHandler(debug_handler)
     # call acme_tiny with new contact details
     old_stdout = sys.stdout
     sys.stdout = StringIO()
     result = acme_tiny.main([
         "--account-key", KEYS['account_key'].name,
         "--csr", KEYS['domain_csr'].name,
         "--acme-dir", self.tempdir,
         "--directory-url", self.DIR_URL,
         "--contact", "mailto:[email protected]", "mailto:[email protected]",
     ])
     sys.stdout.seek(0)
     crt = sys.stdout.read().encode("utf8")
     sys.stdout = old_stdout
     log_output.seek(0)
     log_string = log_output.read().encode("utf8")
     # make sure the certificate was issued and the contact details were updated
     out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(crt)
     self.assertIn("Issuer: CN=Fake LE Intermediate", out.decode("utf8"))
     self.assertIn("Updated contact details:\nmailto:[email protected]\nmailto:[email protected]", log_string.decode("utf8"))
     # remove logging capture
     acme_tiny.LOGGER.removeHandler(debug_handler)
Пример #4
0
    def test_malicious_challenge_token(self):
        """ Raises error if malicious challenge token is provided by the CA """

        # assume the CA wants to try to fool you into serving up your password file
        malicious_token = "../../../../etc/passwd"
        cleaned_token = "____________etc_passwd"

        # man-in-the-middle ACME requests to modify the challenge token to something malicious
        def urlopenMITM(*args, **kwargs):
            resp = urlopenOriginal(*args, **kwargs)
            resp._orig_read = resp.read()
            try:
                resp_json = json.loads(resp._orig_read.decode("utf8"))
                if len([c for c in resp_json.get("challenges", []) if c['type'] == "http-01"]) == 1:
                    challenge = [c for c in resp_json['challenges'] if c['type'] == "http-01"][0]
                    challenge['token'] = malicious_token
                    resp._orig_read = json.dumps(resp_json).encode("utf8")
            except ValueError:
                pass
            # serve up modified response when read
            def multi_read():
                return resp._orig_read
            resp.read = multi_read
            return resp

        # call acme-tiny with MITM'd urlopen
        urlopenOriginal = acme_tiny.urlopen
        acme_tiny.urlopen = urlopenMITM
        try:
            acme_tiny.main([
                "--account-key", self.KEYS['account_key'].name,
                "--csr", self.KEYS['domain_csr'].name,
                "--acme-dir", self.tempdir,
                "--directory-url", self.DIR_URL,
                "--check-port", self.check_port,
            ])
        except ValueError as e:
            result = e
        acme_tiny.urlopen = urlopenOriginal

        # should raise error that challenge didn't pass
        self.assertIn("Challenge did not pass for", result.args[0])

        # challenge file actually saved as a cleaned version
        resp = urlopen(Request("http://{0}:{1}/.well-known/acme-challenge/{2}".format(DOMAIN, self.check_port, cleaned_token)))
        token_data = resp.read().decode("utf8")
        self.assertIn(cleaned_token, token_data)
Пример #5
0
    def test_challenge_failure(self):
        """ Raises error if challenge doesn't pass """
        # man-in-the-middle ACME requests to modify valid challenges so we raise that exception
        def urlopenMITM(*args, **kwargs):
            resp = urlopenOriginal(*args, **kwargs)
            resp._orig_read = resp.read()
            # modify valid challenges and authorizations to invalid
            try:
                resp_json = json.loads(resp._orig_read.decode("utf8"))
                if (
                    len(resp_json.get("challenges", [])) == 1
                    and resp_json['challenges'][0]['status'] == "valid"
                    and resp_json['status'] == "valid"
                ):
                    resp_json['challenges'][0]['status'] = "invalid"
                    resp_json['status'] = "invalid"
                    resp._orig_read = json.dumps(resp_json).encode("utf8")
            except ValueError:
                pass
            # serve up modified response when read
            def multi_read():
                return resp._orig_read
            resp.read = multi_read
            return resp

        # call acme-tiny with MITM'd urlopen
        urlopenOriginal = acme_tiny.urlopen
        acme_tiny.urlopen = urlopenMITM
        try:
            acme_tiny.main([
                "--account-key", self.KEYS['account_key'].name,
                "--csr", self.KEYS['domain_csr'].name,
                "--acme-dir", self.tempdir,
                "--directory-url", self.DIR_URL,
                "--check-port", self.check_port,
            ])
        except ValueError as e:
            result = e
        acme_tiny.urlopen = urlopenOriginal

        # should raise error that challenge didn't pass
        self.assertIn("Challenge did not pass for", result.args[0])
Пример #6
0
 def test_missing_account_key(self):
     """ OpenSSL throws an error when the account key is missing """
     try:
         result = acme_tiny.main([
             "--account-key", "/foo/bar",
             "--csr", KEYS['domain_csr'].name,
             "--acme-dir", self.tempdir,
             "--directory-url", self.DIR_URL,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, IOError)
     self.assertIn("Error opening Private Key", result.args[0])
Пример #7
0
 def test_missing_csr(self):
     """ OpenSSL throws an error when the CSR is missing """
     try:
         result = acme_tiny.main([
             "--account-key", KEYS['account_key'].name,
             "--csr", "/foo/bar",
             "--acme-dir", self.tempdir,
             "--ca", self.CA,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, IOError)
     self.assertIn("Error loading /foo/bar", result.args[0])
Пример #8
0
 def test_account_key_domain(self):
     """ Can't use the account key for the CSR """
     try:
         result = acme_tiny.main([
             "--account-key", KEYS['account_key'].name,
             "--csr", KEYS['account_csr'].name,
             "--acme-dir", self.tempdir,
             "--ca", self.CA,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn("Certificate public key must be different than account key", result.args[0])
Пример #9
0
 def test_invalid_domain(self):
     """ Let's Encrypt rejects invalid domains """
     try:
         result = acme_tiny.main([
             "--account-key", KEYS['account_key'].name,
             "--csr", KEYS['invalid_csr'].name,
             "--acme-dir", self.tempdir,
             "--ca", self.CA,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn("Invalid character in DNS name", result.args[0])
Пример #10
0
 def test_weak_key(self):
     """ Let's Encrypt rejects weak keys """
     try:
         result = acme_tiny.main([
             "--account-key", KEYS['weak_key'].name,
             "--csr", KEYS['domain_csr'].name,
             "--acme-dir", self.tempdir,
             "--ca", self.CA,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn("Key too small", result.args[0])
Пример #11
0
 def test_weak_key(self):
     """ Let's Encrypt rejects weak keys """
     try:
         result = acme_tiny.main([
             "--account-key", KEYS['weak_key'].name,
             "--csr", KEYS['domain_csr'].name,
             "--acme-dir", self.tempdir,
             "--ca", self.CA,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn("Key too small", result.args[0])
Пример #12
0
 def test_nonexistant_domain(self):
     """ Should be unable verify a nonexistent domain """
     try:
         result = acme_tiny.main([
             "--account-key", KEYS['account_key'].name,
             "--csr", KEYS['nonexistent_csr'].name,
             "--acme-dir", self.tempdir,
             "--ca", self.CA,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn("but couldn't download", result.args[0])
Пример #13
0
 def test_invalid_domain(self):
     """ Let's Encrypt rejects invalid domains """
     try:
         result = acme_tiny.main([
             "--account-key", KEYS['account_key'].name,
             "--csr", KEYS['invalid_csr'].name,
             "--acme-dir", self.tempdir,
             "--ca", self.CA,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn("Invalid character in DNS name", result.args[0])
Пример #14
0
 def test_account_key_domain(self):
     """ Can't use the account key for the CSR """
     try:
         result = acme_tiny.main([
             "--account-key", KEYS['account_key'].name,
             "--csr", KEYS['account_csr'].name,
             "--acme-dir", self.tempdir,
             "--ca", self.CA,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn("Certificate public key must be different than account key", result.args[0])
Пример #15
0
 def test_missing_csr(self):
     """ OpenSSL throws an error when the CSR is missing """
     try:
         result = acme_tiny.main([
             "--account-key", KEYS['account_key'].name,
             "--csr", "/foo/bar",
             "--acme-dir", self.tempdir,
             "--ca", self.CA,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, IOError)
     self.assertIn("Error loading /foo/bar", result.args[0])
Пример #16
0
 def test_nonexistant_domain(self):
     """ Should be unable verify a nonexistent domain """
     try:
         result = acme_tiny.main([
             "--account-key", KEYS['account_key'].name,
             "--csr", KEYS['nonexistent_csr'].name,
             "--acme-dir", self.tempdir,
             "--ca", self.CA,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn("but couldn't download", result.args[0])
Пример #17
0
 def test_invalid_domain(self):
     """ Let's Encrypt rejects invalid domains """
     try:
         result = acme_tiny.main([
             "--account-key", self.KEYS['account_key'].name,
             "--csr", self.KEYS['invalid_csr'].name,
             "--acme-dir", self.tempdir,
             "--directory-url", self.DIR_URL,
             "--check-port", self.check_port,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn(self.bad_character_error, result.args[0])
Пример #18
0
 def test_pebble_doesnt_support_cn_domains(self):
     """ Test that pebble server doesn't support CN subject domains """
     try:
         result = acme_tiny.main([
             "--account-key", self.KEYS['account_key'].name,
             "--csr", self.KEYS['cn_csr'].name,
             "--acme-dir", self.tempdir,
             "--directory-url", self.DIR_URL,
             "--check-port", self.check_port,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn("Order includes different number of DNSnames identifiers than CSR specifies", result.args[0])
Пример #19
0
 def test_missing_account_key(self):
     """ OpenSSL throws an error when the account key is missing """
     try:
         result = acme_tiny.main([
             "--account-key", "/foo/bar",
             "--csr", self.KEYS['domain_csr'].name,
             "--acme-dir", self.tempdir,
             "--directory-url", self.DIR_URL,
             "--check-port", self.check_port,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, IOError)
     self.assertIn("unable to load Private Key", result.args[0])
Пример #20
0
 def test_weak_key(self): # pragma: no cover
     """ Let's Encrypt rejects weak keys """
     try:
         result = acme_tiny.main([
             "--account-key", self.KEYS['weak_key'].name,
             "--csr", self.KEYS['domain_csr'].name,
             "--acme-dir", self.tempdir,
             "--directory-url", self.DIR_URL,
             #"--check-port", self.check_port, # defaults to port 80 anyway, so test that the default works
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn("key too small", result.args[0])
Пример #21
0
 def test_account_key_domain(self):
     """ Can't use the account key for the CSR """
     try:
         result = acme_tiny.main([
             "--account-key", self.KEYS['account_key'].name,
             "--csr", self.KEYS['account_csr'].name,
             "--acme-dir", self.tempdir,
             "--directory-url", self.DIR_URL,
             "--check-port", self.check_port,
         ])
     except Exception as e:
         result = e
     self.assertIsInstance(result, ValueError)
     self.assertIn(self.account_key_error, result.args[0])
Пример #22
0
 def test_success_san(self):
     """ Successfully issue a certificate via subject alt name """
     old_stdout = sys.stdout
     sys.stdout = StringIO()
     result = acme_tiny.main([
         "--account-key", KEYS['account_key'].name,
         "--csr", KEYS['san_csr'].name,
         "--acme-dir", self.tempdir,
         "--directory-url", self.DIR_URL,
     ])
     sys.stdout.seek(0)
     crt = sys.stdout.read().encode("utf8")
     sys.stdout = old_stdout
     out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(crt)
     self.assertIn("Issuer: CN=Fake LE Intermediate", out.decode("utf8"))
Пример #23
0
 def test_success_cn(self):
     """ Successfully issue a certificate via common name """
     old_stdout = sys.stdout
     sys.stdout = StringIO()
     result = acme_tiny.main([
         "--account-key", KEYS['account_key'].name,
         "--csr", KEYS['domain_csr'].name,
         "--acme-dir", self.tempdir,
         "--ca", self.CA,
     ])
     sys.stdout.seek(0)
     crt = sys.stdout.read().encode("utf8")
     sys.stdout = old_stdout
     out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE,
         stdout=PIPE, stderr=PIPE).communicate(crt)
     self.assertIn("Issuer: CN=happy hacker fake CA", out.decode("utf8"))
Пример #24
0
 def test_success_cn(self): # pragma: no cover
     """ Successfully issue a certificate via common name """
     old_stdout = sys.stdout
     sys.stdout = StringIO()
     result = acme_tiny.main([
         "--account-key", self.KEYS['account_key'].name,
         "--csr", self.KEYS['cn_csr'].name,
         "--acme-dir", self.tempdir,
         "--directory-url", self.DIR_URL,
         #"--check-port", self.check_port, # defaults to port 80 anyway, so test that the default works
     ])
     sys.stdout.seek(0)
     crt = sys.stdout.read().encode("utf8")
     sys.stdout = old_stdout
     out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(crt)
     self.assertIn(self.ca_issued_string, out.decode("utf8"))
Пример #25
0
 def test_success_domain(self):
     """ Successfully issue a certificate via subject alt name """
     old_stdout = sys.stdout
     sys.stdout = StringIO()
     result = acme_tiny.main([
         "--account-key", self.KEYS['account_key'].name,
         "--csr", self.KEYS['domain_csr'].name,
         "--acme-dir", self.tempdir,
         "--directory-url", self.DIR_URL,
         "--check-port", self.check_port,
     ])
     sys.stdout.seek(0)
     crt = sys.stdout.read().encode("utf8")
     sys.stdout = old_stdout
     out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(crt)
     self.assertIn(self.ca_issued_string, out.decode("utf8"))
Пример #26
0
 def test_success_san(self):
     """ Successfully issue a certficate via subject alt name """
     old_stdout = sys.stdout
     sys.stdout = StringIO()
     result = acme_tiny.main([
         "--account-key", KEYS['account_key'].name,
         "--csr", KEYS['san_csr'].name,
         "--acme-dir", self.tempdir,
         "--ca", self.CA,
     ])
     sys.stdout.seek(0)
     crt = sys.stdout.read().encode("utf8")
     sys.stdout = old_stdout
     out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE,
         stdout=PIPE, stderr=PIPE).communicate(crt)
     self.assertIn("Issuer: CN=happy hacker fake CA", out.decode("utf8"))
Пример #27
0
 def test_success_cn(self):
     """ Successfully issue a certificate via common name """
     old_stdout = sys.stdout
     sys.stdout = StringIO()
     result = acme_tiny.main([
         "--account-key",
         KEYS['account_key'].name,
         "--csr",
         KEYS['domain_csr'].name,
         "--acme-dir",
         self.tempdir,
         "--directory-url",
         self.DIR_URL,
     ])
     sys.stdout.seek(0)
     crt = sys.stdout.read().encode("utf8")
     sys.stdout = old_stdout
     out, err = Popen(["openssl", "x509", "-text", "-noout"],
                      stdin=PIPE,
                      stdout=PIPE,
                      stderr=PIPE).communicate(crt)
     self.assertIn("Issuer: CN=Fake LE Intermediate", out.decode("utf8"))