mirror of
https://github.com/certbot/certbot.git
synced 2026-06-09 00:32:12 -04:00
Merge pull request #1020 from letsencrypt/report_csr_expiry
Report expiration when using a CSR
This commit is contained in:
commit
85e6f50f68
6 changed files with 97 additions and 83 deletions
|
|
@ -269,9 +269,13 @@ def _treat_as_renewal(config, domains):
|
|||
|
||||
def _report_new_cert(cert_path):
|
||||
"""Reports the creation of a new certificate to the user."""
|
||||
expiry = crypto_util.notAfter(cert_path).date()
|
||||
reporter_util = zope.component.getUtility(interfaces.IReporter)
|
||||
reporter_util.add_message("Congratulations! Your certificate has been "
|
||||
"saved at {0}.".format(cert_path),
|
||||
"saved at {0} and will expire on {1}. To obtain "
|
||||
"a new version of the certificate in the "
|
||||
"future, simply run Let's Encrypt again.".format(
|
||||
cert_path, expiry),
|
||||
reporter_util.MEDIUM_PRIORITY)
|
||||
|
||||
|
||||
|
|
@ -301,12 +305,6 @@ def _auth_from_domains(le_client, config, domains, plugins):
|
|||
raise Error("Certificate could not be obtained")
|
||||
|
||||
_report_new_cert(lineage.cert)
|
||||
reporter_util = zope.component.getUtility(interfaces.IReporter)
|
||||
reporter_util.add_message(
|
||||
"Your certificate will expire on {0}. To obtain a new version of the "
|
||||
"certificate in the future, simply run this client again.".format(
|
||||
lineage.notafter().date()),
|
||||
reporter_util.MEDIUM_PRIORITY)
|
||||
|
||||
return lineage
|
||||
|
||||
|
|
@ -453,9 +451,9 @@ def auth(args, config, plugins):
|
|||
if args.csr is not None:
|
||||
certr, chain = le_client.obtain_certificate_from_csr(le_util.CSR(
|
||||
file=args.csr[0], data=args.csr[1], form="der"))
|
||||
le_client.save_certificate(
|
||||
cert_path, _ = le_client.save_certificate(
|
||||
certr, chain, args.cert_path, args.chain_path)
|
||||
_report_new_cert(args.cert_path)
|
||||
_report_new_cert(cert_path)
|
||||
else:
|
||||
domains = _find_domains(args, installer)
|
||||
_auth_from_domains(le_client, config, domains, plugins)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import logging
|
|||
import os
|
||||
|
||||
import OpenSSL
|
||||
import pyrfc3339
|
||||
import zope.component
|
||||
|
||||
from acme import crypto_util as acme_crypto_util
|
||||
|
|
@ -276,3 +277,48 @@ def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
|
|||
# assumes that OpenSSL.crypto.dump_certificate includes ending
|
||||
# newline character
|
||||
return "".join(_dump_cert(cert) for cert in chain)
|
||||
|
||||
|
||||
def notBefore(cert_path):
|
||||
"""When does the cert at cert_path start being valid?
|
||||
|
||||
:param str cert_path: path to a cert in PEM format
|
||||
|
||||
:returns: the notBefore value from the cert at cert_path
|
||||
:rtype: :class:`datetime.datetime`
|
||||
|
||||
"""
|
||||
return _notAfterBefore(cert_path, OpenSSL.crypto.X509.get_notBefore)
|
||||
|
||||
|
||||
def notAfter(cert_path):
|
||||
"""When does the cert at cert_path stop being valid?
|
||||
|
||||
:param str cert_path: path to a cert in PEM format
|
||||
|
||||
:returns: the notAfter value from the cert at cert_path
|
||||
:rtype: :class:`datetime.datetime`
|
||||
|
||||
"""
|
||||
return _notAfterBefore(cert_path, OpenSSL.crypto.X509.get_notAfter)
|
||||
|
||||
|
||||
def _notAfterBefore(cert_path, method):
|
||||
"""Internal helper function for finding notbefore/notafter.
|
||||
|
||||
:param str cert_path: path to a cert in PEM format
|
||||
:param function method: one of ``OpenSSL.crypto.X509.get_notBefore``
|
||||
or ``OpenSSL.crypto.X509.get_notAfter``
|
||||
|
||||
:returns: the notBefore or notAfter value from the cert at cert_path
|
||||
:rtype: :class:`datetime.datetime`
|
||||
|
||||
"""
|
||||
with open(cert_path) as f:
|
||||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
||||
f.read())
|
||||
timestamp = method(x509)
|
||||
reformatted_timestamp = [timestamp[0:4], "-", timestamp[4:6], "-",
|
||||
timestamp[6:8], "T", timestamp[8:10], ":",
|
||||
timestamp[10:12], ":", timestamp[12:]]
|
||||
return pyrfc3339.parse("".join(reformatted_timestamp))
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ import re
|
|||
import time
|
||||
|
||||
import configobj
|
||||
import OpenSSL
|
||||
import parsedatetime
|
||||
import pytz
|
||||
import pyrfc3339
|
||||
|
||||
from letsencrypt import constants
|
||||
from letsencrypt import crypto_util
|
||||
|
|
@ -381,47 +379,6 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
|||
for kind in ALL_FOUR:
|
||||
self._update_link_to(kind, version)
|
||||
|
||||
def _notafterbefore(self, method, version):
|
||||
"""Internal helper function for finding notbefore/notafter."""
|
||||
if version is None:
|
||||
target = self.current_target("cert")
|
||||
else:
|
||||
target = self.version("cert", version)
|
||||
pem = open(target).read()
|
||||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
|
||||
pem)
|
||||
i = method(x509)
|
||||
return pyrfc3339.parse(i[0:4] + "-" + i[4:6] + "-" + i[6:8] + "T" +
|
||||
i[8:10] + ":" + i[10:12] + ":" + i[12:])
|
||||
|
||||
def notbefore(self, version=None):
|
||||
"""When does the specified cert version start being valid?
|
||||
|
||||
(If no version is specified, use the current version.)
|
||||
|
||||
:param int version: the desired version number
|
||||
|
||||
:returns: the notBefore value from the specified cert version in
|
||||
this lineage
|
||||
:rtype: :class:`datetime.datetime`
|
||||
|
||||
"""
|
||||
return self._notafterbefore(lambda x509: x509.get_notBefore(), version)
|
||||
|
||||
def notafter(self, version=None):
|
||||
"""When does the specified cert version stop being valid?
|
||||
|
||||
(If no version is specified, use the current version.)
|
||||
|
||||
:param int version: the desired version number
|
||||
|
||||
:returns: the notAfter value from the specified cert version in
|
||||
this lineage
|
||||
:rtype: :class:`datetime.datetime`
|
||||
|
||||
"""
|
||||
return self._notafterbefore(lambda x509: x509.get_notAfter(), version)
|
||||
|
||||
def names(self, version=None):
|
||||
"""What are the subject names of this certificate?
|
||||
|
||||
|
|
@ -470,7 +427,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
|||
interval = self.configuration.get("deploy_before_expiry",
|
||||
"5 days")
|
||||
autodeploy_interval = parse_time_interval(interval)
|
||||
expiry = self.notafter()
|
||||
expiry = crypto_util.notAfter(self.current_target("cert"))
|
||||
now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
|
||||
remaining = expiry - now
|
||||
if remaining < autodeploy_interval:
|
||||
|
|
@ -537,7 +494,8 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
|||
# Renewals on the basis of expiry time
|
||||
interval = self.configuration.get("renew_before_expiry", "10 days")
|
||||
autorenew_interval = parse_time_interval(interval)
|
||||
expiry = self.notafter(self.latest_common_version())
|
||||
expiry = crypto_util.notAfter(self.version(
|
||||
"cert", self.latest_common_version()))
|
||||
now = datetime.datetime.utcnow().replace(tzinfo=pytz.UTC)
|
||||
remaining = expiry - now
|
||||
if remaining < autorenew_interval:
|
||||
|
|
|
|||
|
|
@ -141,18 +141,23 @@ class CLITest(unittest.TestCase):
|
|||
ret, _, _, _ = self._call(['-a', 'bad_auth', 'auth'])
|
||||
self.assertEqual(ret, 'The requested bad_auth plugin does not appear to be installed')
|
||||
|
||||
@mock.patch('letsencrypt.crypto_util.notAfter')
|
||||
@mock.patch('letsencrypt.cli.zope.component.getUtility')
|
||||
def test_auth_new_request_success(self, mock_get_utility):
|
||||
def test_auth_new_request_success(self, mock_get_utility, mock_notAfter):
|
||||
cert_path = '/etc/letsencrypt/live/foo.bar'
|
||||
date = '1970-01-01'
|
||||
mock_notAfter().date.return_value = date
|
||||
|
||||
mock_lineage = mock.MagicMock(cert=cert_path)
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.obtain_and_enroll_certificate.return_value = mock_lineage
|
||||
self._auth_new_request_common(mock_client)
|
||||
self.assertEqual(
|
||||
mock_client.obtain_and_enroll_certificate.call_count, 1)
|
||||
msg = mock_get_utility().add_message.call_args_list[0][0][0]
|
||||
self.assertTrue(cert_path in msg)
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 2)
|
||||
self.assertTrue(
|
||||
cert_path in mock_get_utility().add_message.call_args[0][0])
|
||||
self.assertTrue(
|
||||
date in mock_get_utility().add_message.call_args[0][0])
|
||||
|
||||
def test_auth_new_request_failure(self):
|
||||
mock_client = mock.MagicMock()
|
||||
|
|
@ -172,6 +177,7 @@ class CLITest(unittest.TestCase):
|
|||
@mock.patch('letsencrypt.cli._init_le_client')
|
||||
def test_auth_renewal(self, mock_init, mock_renewal, mock_get_utility):
|
||||
cert_path = '/etc/letsencrypt/live/foo.bar'
|
||||
|
||||
mock_lineage = mock.MagicMock(cert=cert_path)
|
||||
mock_cert = mock.MagicMock(body='body')
|
||||
mock_key = mock.MagicMock(pem='pem_key')
|
||||
|
|
@ -187,19 +193,25 @@ class CLITest(unittest.TestCase):
|
|||
self.assertEqual(mock_lineage.save_successor.call_count, 1)
|
||||
mock_lineage.update_all_links_to.assert_called_once_with(
|
||||
mock_lineage.latest_common_version())
|
||||
msg = mock_get_utility().add_message.call_args_list[0][0][0]
|
||||
self.assertTrue(cert_path in msg)
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 2)
|
||||
self.assertTrue(
|
||||
cert_path in mock_get_utility().add_message.call_args[0][0])
|
||||
|
||||
@mock.patch('letsencrypt.crypto_util.notAfter')
|
||||
@mock.patch('letsencrypt.cli.display_ops.pick_installer')
|
||||
@mock.patch('letsencrypt.cli.zope.component.getUtility')
|
||||
@mock.patch('letsencrypt.cli._init_le_client')
|
||||
def test_auth_csr(self, mock_init, mock_get_utility, mock_pick_installer):
|
||||
def test_auth_csr(self, mock_init, mock_get_utility,
|
||||
mock_pick_installer, mock_notAfter):
|
||||
cert_path = '/etc/letsencrypt/live/foo.bar'
|
||||
date = '1970-01-01'
|
||||
mock_notAfter().date.return_value = date
|
||||
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.obtain_certificate_from_csr.return_value = ('certr',
|
||||
'chain')
|
||||
mock_client.save_certificate.return_value = cert_path, None
|
||||
mock_init.return_value = mock_client
|
||||
|
||||
installer = 'installer'
|
||||
self._call(
|
||||
['-a', 'standalone', '-i', installer, 'auth', '--csr', CSR,
|
||||
|
|
@ -209,6 +221,8 @@ class CLITest(unittest.TestCase):
|
|||
'certr', 'chain', cert_path, '/')
|
||||
self.assertTrue(
|
||||
cert_path in mock_get_utility().add_message.call_args[0][0])
|
||||
self.assertTrue(
|
||||
date in mock_get_utility().add_message.call_args[0][0])
|
||||
|
||||
@mock.patch('letsencrypt.cli.sys')
|
||||
def test_handle_exception(self, mock_sys):
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from letsencrypt.tests import test_util
|
|||
|
||||
RSA256_KEY = test_util.load_vector('rsa256_key.pem')
|
||||
RSA512_KEY = test_util.load_vector('rsa512_key.pem')
|
||||
CERT_PATH = test_util.vector_path('cert.pem')
|
||||
CERT = test_util.load_vector('cert.pem')
|
||||
SAN_CERT = test_util.load_vector('cert-san.pem')
|
||||
|
||||
|
|
@ -232,5 +233,23 @@ class CertLoaderTest(unittest.TestCase):
|
|||
pyopenssl_load_certificate(bad_cert_data)
|
||||
|
||||
|
||||
class NotBeforeTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.crypto_util.notBefore"""
|
||||
|
||||
def test_notBefore(self):
|
||||
from letsencrypt.crypto_util import notBefore
|
||||
self.assertEqual(notBefore(CERT_PATH).isoformat(),
|
||||
'2014-12-11T22:34:45+00:00')
|
||||
|
||||
|
||||
class NotAfterTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.crypto_util.notAfter"""
|
||||
|
||||
def test_notAfter(self):
|
||||
from letsencrypt.crypto_util import notAfter
|
||||
self.assertEqual(notAfter(CERT_PATH).isoformat(),
|
||||
'2014-12-18T22:34:45+00:00')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import unittest
|
|||
|
||||
import configobj
|
||||
import mock
|
||||
import pytz
|
||||
|
||||
from letsencrypt import configuration
|
||||
from letsencrypt import errors
|
||||
|
|
@ -348,26 +347,6 @@ class RenewableCertTests(BaseRenewableCertTest):
|
|||
self.assertEqual(self.test_rc.names(12),
|
||||
["example.com", "www.example.com"])
|
||||
|
||||
def _test_notafterbefore(self, function, timestamp):
|
||||
test_cert = test_util.load_vector("cert.pem")
|
||||
os.symlink(os.path.join("..", "..", "archive", "example.org",
|
||||
"cert12.pem"), self.test_rc.cert)
|
||||
with open(self.test_rc.cert, "w") as f:
|
||||
f.write(test_cert)
|
||||
desired_time = datetime.datetime.utcfromtimestamp(timestamp)
|
||||
desired_time = desired_time.replace(tzinfo=pytz.UTC)
|
||||
for result in (function(), function(12)):
|
||||
self.assertEqual(result, desired_time)
|
||||
self.assertEqual(result.utcoffset(), datetime.timedelta(0))
|
||||
|
||||
def test_notbefore(self):
|
||||
self._test_notafterbefore(self.test_rc.notbefore, 1418337285)
|
||||
# 2014-12-11 22:34:45+00:00 = Unix time 1418337285
|
||||
|
||||
def test_notafter(self):
|
||||
self._test_notafterbefore(self.test_rc.notafter, 1418942085)
|
||||
# 2014-12-18 22:34:45+00:00 = Unix time 1418942085
|
||||
|
||||
@mock.patch("letsencrypt.storage.datetime")
|
||||
def test_time_interval_judgments(self, mock_datetime):
|
||||
"""Test should_autodeploy() and should_autorenew() on the basis
|
||||
|
|
|
|||
Loading…
Reference in a new issue