test_module.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import unittest, os, sys, tempfile, logging
  2. from subprocess import Popen, PIPE
  3. try:
  4. from StringIO import StringIO # Python 2
  5. except ImportError:
  6. from io import StringIO # Python 3
  7. import acme_tiny
  8. from .monkey import gen_keys
  9. KEYS = gen_keys()
  10. class TestModule(unittest.TestCase):
  11. "Tests for acme_tiny.get_crt()"
  12. def setUp(self):
  13. self.DIR_URL = "https://acme-staging-v02.api.letsencrypt.org/directory"
  14. self.tempdir = tempfile.mkdtemp()
  15. self.fuse_proc = Popen(["python", "tests/monkey.py", self.tempdir])
  16. def tearDown(self):
  17. self.fuse_proc.terminate()
  18. self.fuse_proc.wait()
  19. os.rmdir(self.tempdir)
  20. def test_success_cn(self):
  21. """ Successfully issue a certificate via common name """
  22. old_stdout = sys.stdout
  23. sys.stdout = StringIO()
  24. result = acme_tiny.main([
  25. "--account-key", KEYS['account_key'].name,
  26. "--csr", KEYS['domain_csr'].name,
  27. "--acme-dir", self.tempdir,
  28. "--directory-url", self.DIR_URL,
  29. ])
  30. sys.stdout.seek(0)
  31. crt = sys.stdout.read().encode("utf8")
  32. sys.stdout = old_stdout
  33. out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(crt)
  34. self.assertIn("Issuer: CN=Fake LE Intermediate", out.decode("utf8"))
  35. def test_success_san(self):
  36. """ Successfully issue a certificate via subject alt name """
  37. old_stdout = sys.stdout
  38. sys.stdout = StringIO()
  39. result = acme_tiny.main([
  40. "--account-key", KEYS['account_key'].name,
  41. "--csr", KEYS['san_csr'].name,
  42. "--acme-dir", self.tempdir,
  43. "--directory-url", self.DIR_URL,
  44. ])
  45. sys.stdout.seek(0)
  46. crt = sys.stdout.read().encode("utf8")
  47. sys.stdout = old_stdout
  48. out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(crt)
  49. self.assertIn("Issuer: CN=Fake LE Intermediate", out.decode("utf8"))
  50. def test_success_cli(self):
  51. """ Successfully issue a certificate via command line interface """
  52. crt, err = Popen([
  53. "python", "acme_tiny.py",
  54. "--account-key", KEYS['account_key'].name,
  55. "--csr", KEYS['domain_csr'].name,
  56. "--acme-dir", self.tempdir,
  57. "--directory-url", self.DIR_URL,
  58. ], stdout=PIPE, stderr=PIPE).communicate()
  59. out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(crt)
  60. self.assertIn("Issuer: CN=Fake LE Intermediate", out.decode("utf8"))
  61. def test_missing_account_key(self):
  62. """ OpenSSL throws an error when the account key is missing """
  63. try:
  64. result = acme_tiny.main([
  65. "--account-key", "/foo/bar",
  66. "--csr", KEYS['domain_csr'].name,
  67. "--acme-dir", self.tempdir,
  68. "--directory-url", self.DIR_URL,
  69. ])
  70. except Exception as e:
  71. result = e
  72. self.assertIsInstance(result, IOError)
  73. self.assertIn("Error opening Private Key", result.args[0])
  74. def test_missing_csr(self):
  75. """ OpenSSL throws an error when the CSR is missing """
  76. try:
  77. result = acme_tiny.main([
  78. "--account-key", KEYS['account_key'].name,
  79. "--csr", "/foo/bar",
  80. "--acme-dir", self.tempdir,
  81. "--directory-url", self.DIR_URL,
  82. ])
  83. except Exception as e:
  84. result = e
  85. self.assertIsInstance(result, IOError)
  86. self.assertIn("Error loading /foo/bar", result.args[0])
  87. def test_weak_key(self):
  88. """ Let's Encrypt rejects weak keys """
  89. try:
  90. result = acme_tiny.main([
  91. "--account-key", KEYS['weak_key'].name,
  92. "--csr", KEYS['domain_csr'].name,
  93. "--acme-dir", self.tempdir,
  94. "--directory-url", self.DIR_URL,
  95. ])
  96. except Exception as e:
  97. result = e
  98. self.assertIsInstance(result, ValueError)
  99. self.assertIn("key too small", result.args[0])
  100. def test_invalid_domain(self):
  101. """ Let's Encrypt rejects invalid domains """
  102. try:
  103. result = acme_tiny.main([
  104. "--account-key", KEYS['account_key'].name,
  105. "--csr", KEYS['invalid_csr'].name,
  106. "--acme-dir", self.tempdir,
  107. "--directory-url", self.DIR_URL,
  108. ])
  109. except Exception as e:
  110. result = e
  111. self.assertIsInstance(result, ValueError)
  112. self.assertIn("Invalid character in DNS name", result.args[0])
  113. def test_nonexistent_domain(self):
  114. """ Should be unable verify a nonexistent domain """
  115. try:
  116. result = acme_tiny.main([
  117. "--account-key", KEYS['account_key'].name,
  118. "--csr", KEYS['nonexistent_csr'].name,
  119. "--acme-dir", self.tempdir,
  120. "--directory-url", self.DIR_URL,
  121. ])
  122. except Exception as e:
  123. result = e
  124. self.assertIsInstance(result, ValueError)
  125. self.assertIn("but couldn't download", result.args[0])
  126. def test_account_key_domain(self):
  127. """ Can't use the account key for the CSR """
  128. try:
  129. result = acme_tiny.main([
  130. "--account-key", KEYS['account_key'].name,
  131. "--csr", KEYS['account_csr'].name,
  132. "--acme-dir", self.tempdir,
  133. "--directory-url", self.DIR_URL,
  134. ])
  135. except Exception as e:
  136. result = e
  137. self.assertIsInstance(result, ValueError)
  138. self.assertIn("certificate public key must be different than account key", result.args[0])
  139. def test_contact(self):
  140. """ Make sure optional contact details can be set """
  141. # add a logging handler that captures the info log output
  142. log_output = StringIO()
  143. debug_handler = logging.StreamHandler(log_output)
  144. acme_tiny.LOGGER.addHandler(debug_handler)
  145. # call acme_tiny with new contact details
  146. old_stdout = sys.stdout
  147. sys.stdout = StringIO()
  148. result = acme_tiny.main([
  149. "--account-key", KEYS['account_key'].name,
  150. "--csr", KEYS['domain_csr'].name,
  151. "--acme-dir", self.tempdir,
  152. "--directory-url", self.DIR_URL,
  153. "--contact", "mailto:devteam@example.com", "mailto:boss@example.com",
  154. ])
  155. sys.stdout.seek(0)
  156. crt = sys.stdout.read().encode("utf8")
  157. sys.stdout = old_stdout
  158. log_output.seek(0)
  159. log_string = log_output.read().encode("utf8")
  160. # make sure the certificate was issued and the contact details were updated
  161. out, err = Popen(["openssl", "x509", "-text", "-noout"], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(crt)
  162. self.assertIn("Issuer: CN=Fake LE Intermediate", out.decode("utf8"))
  163. self.assertIn("Updated contact details:\nmailto:devteam@example.com\nmailto:boss@example.com", log_string.decode("utf8"))
  164. # remove logging capture
  165. acme_tiny.LOGGER.removeHandler(debug_handler)