Пример #1
0
    def _push_live_certificate(self, cert_id, key_type_id):
        """Moves a new certificate to the live path after checking that everything looks sane"""
        logger.info("Pushing the new certificate for %s / %s", cert_id,
                    key_type_id)
        try:
            private_key = PrivateKeyLoader.load(
                self._get_path(cert_id, key_type_id, public=False, kind='new'))
            cert = Certificate.load(
                self._get_path(cert_id,
                               key_type_id,
                               public=True,
                               kind='new',
                               cert_type='full_chain'))
            private_key.save(
                self._get_path(cert_id, key_type_id, public=False,
                               kind='live'))
            for cert_type, cert_type_details in CERTIFICATE_TYPES.items():
                cert.save(self._get_path(cert_id,
                                         key_type_id,
                                         public=True,
                                         kind='live',
                                         cert_type=cert_type),
                          mode=cert_type_details['save_mode'])
        except (OSError, X509Error):
            logger.exception("Problem pushing live certificate %s / %s",
                             cert_id, key_type_id)
            return CertificateStatus.CERTIFICATE_ISSUED

        return CertificateStatus.VALID
Пример #2
0
    def create_initial_certs(self):
        """
        Creates initial certificates for everything that doesn't currently exist.
        This is so that web servers which depend on having a certificate to start can start and
        begin serving traffic so they can forward ACME challenges through to us - that will enable
        us to request a real certificate to replace our initial one.
        """
        for cert_id in self.cert_status:
            for key_type_id, key_type_details in KEY_TYPES.items():
                if self.cert_status[cert_id][
                        key_type_id].status != CertificateStatus.INITIAL:
                    continue

                logger.info(
                    "Creating initial self-signed certificate for %s / %s",
                    cert_id, key_type_id)
                key = key_type_details['class']()
                key.generate(**key_type_details['params'])
                key.save(
                    self._get_path(cert_id,
                                   key_type_id,
                                   public=False,
                                   kind='live'))

                cert = Certificate(
                    SelfSignedCertificate(
                        private_key=key,
                        common_name="Snakeoil cert",
                        sans=(),
                        from_date=datetime.datetime.utcnow(),
                        until_date=datetime.datetime.utcnow() +
                        datetime.timedelta(days=3),
                    ).pem)
                for cert_type, cert_type_details in CERTIFICATE_TYPES.items():
                    path = self._get_path(cert_id,
                                          key_type_id,
                                          public=True,
                                          kind='live',
                                          cert_type=cert_type)
                    cert.save(path, mode=cert_type_details['save_mode'])
                self.cert_status[cert_id][
                    key_type_id].status = CertificateStatus.SELF_SIGNED
Пример #3
0
        def _get_certificate_status(cert_id, key_type_id, certificate):  # pylint: disable=too-many-return-statements
            try:
                new_cert_path = self._get_path(cert_id,
                                               key_type_id,
                                               public=True,
                                               kind='new',
                                               cert_type='full_chain')
                new_cert = Certificate.load(new_cert_path)
                if new_cert.certificate.not_valid_before > certificate.certificate.not_valid_before:
                    return CertificateStatus.READY_TO_BE_PUSHED
            except OSError:
                pass

            if certificate.self_signed is True:
                return CertificateStatus.SELF_SIGNED

            if datetime.datetime.utcnow(
            ) > certificate.certificate.not_valid_after:
                logger.warning("Certificate %s type %s expired on %s", cert_id,
                               key_type_id,
                               certificate.certificate.not_valid_after)
                return CertificateStatus.EXPIRED

            if certificate.needs_renew():
                return CertificateStatus.NEEDS_RENEWAL

            cur_cn = certificate.common_name.lower()
            new_cn = self.config.certificates[cert_id]['CN'].lower()
            if cur_cn != new_cn:
                logger.warning(
                    'Certificate %s type %s has CN %s but is configured for %s, moving back to re-issue',
                    cert_id, key_type_id, cur_cn, new_cn)
                return CertificateStatus.SUBJECTS_CHANGED

            cur_sans = {
                san.lower()
                for san in certificate.subject_alternative_names
            }
            new_sans = {
                san.lower()
                for san in self.config.certificates[cert_id]['SNI']
            }
            if cur_sans != new_sans:
                logger.warning(
                    'Certificate %s type %s has SANs %s but is configured for %s, moving back to re-issue',
                    cert_id, key_type_id, cur_sans, new_sans)
                return CertificateStatus.SUBJECTS_CHANGED

            return CertificateStatus.VALID
Пример #4
0
    def get_certificate(self, csr_id, deadline=None):
        """
        Returns the certificate and the full chain (if present) wrapped in
        a x509.Certificate instance.
        This should be called after the order has been finalized.
        """
        if deadline is None:
            # using now() instead of utcnow() cause acme_client uses now()
            # and using utcnow() on systems where now() != utcnow() cause
            # unexpected behaviour
            deadline = datetime.now() + timedelta(seconds=10)

        finished_order = self._get_order(csr_id)

        try:
            certificate_order = self.acme_client.fetch_certificate(
                finished_order, deadline=deadline)
        except errors.TimeoutError:
            raise ACMETimeoutFetchingCertificateError(
                'Timeout waiting for the ACME directory to finalize the order')
        except errors.IssuanceError as issuance_error:
            self._clean(csr_id)
            raise ACMEError('Unable to get certificate') from issuance_error
        except requests.exceptions.RequestException as request_error:
            raise ACMETransportError(
                'Unable to fetch certificate') from request_error

        self._clean(csr_id)

        try:
            certificate = Certificate(
                certificate_order.fullchain_pem.encode('utf-8'))
        except X509Error as certificate_error:
            raise ACMEIssuedCertificateError(
                'Received invalid PEM from ACME server') from certificate_error

        return certificate
Пример #5
0
    def _handle_ready_to_be_pushed(self, cert_id, key_type_id):
        """Handles READY_TO_BE_PUSHED status. Performs the following actions:
            - Checks if the certificate is ready to be pushed to live_certs_path
            - Pushes the cerfificate iff it's ready to be pushed.
        """
        try:
            cert = Certificate.load(
                self._get_path(cert_id,
                               key_type_id,
                               public=True,
                               kind='new',
                               cert_type='full_chain'))
            staging_timedelta = self.config.certificates[cert_id][
                'staging_time']
            if cert.certificate.not_valid_before >= (
                    datetime.datetime.utcnow() - staging_timedelta):
                return CertificateStatus.READY_TO_BE_PUSHED
        except (OSError, X509Error):
            logger.exception(
                "Problem verifying not valid before date on certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.CERTIFICATE_ISSUED

        return self._push_live_certificate(cert_id, key_type_id)
Пример #6
0
    def _set_cert_status(self):
        """
        Figures out the current status for every configured certificate
        """
        state = collections.defaultdict(dict)

        def _get_certificate_status(cert_id, key_type_id, certificate):  # pylint: disable=too-many-return-statements
            try:
                new_cert_path = self._get_path(cert_id,
                                               key_type_id,
                                               public=True,
                                               kind='new',
                                               cert_type='full_chain')
                new_cert = Certificate.load(new_cert_path)
                if new_cert.certificate.not_valid_before > certificate.certificate.not_valid_before:
                    return CertificateStatus.READY_TO_BE_PUSHED
            except OSError:
                pass

            if certificate.self_signed is True:
                return CertificateStatus.SELF_SIGNED

            if datetime.datetime.utcnow(
            ) > certificate.certificate.not_valid_after:
                logger.warning("Certificate %s type %s expired on %s", cert_id,
                               key_type_id,
                               certificate.certificate.not_valid_after)
                return CertificateStatus.EXPIRED

            if certificate.needs_renew():
                return CertificateStatus.NEEDS_RENEWAL

            cur_cn = certificate.common_name.lower()
            new_cn = self.config.certificates[cert_id]['CN'].lower()
            if cur_cn != new_cn:
                logger.warning(
                    'Certificate %s type %s has CN %s but is configured for %s, moving back to re-issue',
                    cert_id, key_type_id, cur_cn, new_cn)
                return CertificateStatus.SUBJECTS_CHANGED

            cur_sans = {
                san.lower()
                for san in certificate.subject_alternative_names
            }
            new_sans = {
                san.lower()
                for san in self.config.certificates[cert_id]['SNI']
            }
            if cur_sans != new_sans:
                logger.warning(
                    'Certificate %s type %s has SANs %s but is configured for %s, moving back to re-issue',
                    cert_id, key_type_id, cur_sans, new_sans)
                return CertificateStatus.SUBJECTS_CHANGED

            return CertificateStatus.VALID

        for cert_id in self.config.certificates:
            for key_type_id in KEY_TYPES:
                try:
                    current_status = self.cert_status[cert_id][key_type_id]
                    if current_status in (
                            CertificateStatus.CSR_PUSHED,
                            CertificateStatus.CHALLENGES_PUSHED,
                            CertificateStatus.CHALLENGES_REJECTED,
                            CertificateStatus.CERTIFICATE_ISSUED,
                            CertificateStatus.ACMECHIEF_ERROR,
                            CertificateStatus.ACMEDIR_ERROR):
                        # we don't want to break the current cert. issue process
                        continue
                except KeyError:
                    pass

                try:
                    certificate = Certificate.load(
                        self._get_path(cert_id,
                                       key_type_id,
                                       public=True,
                                       kind='live'))
                    new_status = _get_certificate_status(
                        cert_id, key_type_id, certificate)
                except (OSError, X509Error):
                    new_status = CertificateStatus.INITIAL

                state[cert_id][key_type_id] = CertificateState(new_status)

        return state
Пример #7
0
    def test_certificate(self):
        from_date = datetime.datetime.utcnow()
        until_date = from_date + datetime.timedelta(days=90)
        initial_cert = get_self_signed_certificate(from_date, until_date)

        cert = Certificate(initial_cert.pem)
        self.assertIsInstance(cert.certificate, crypto_x509.Certificate)
        self.assertEqual(cert.chain, [cert])
        self.assertFalse(cert.needs_renew())
        self.assertTrue(cert.self_signed)
        mocked_now = until_date - datetime.timedelta(days=10)
        with mock.patch('acme_chief.x509.datetime') as mocked_datetime:
            mocked_datetime.utcnow = mock.Mock(return_value=mocked_now)
            self.assertTrue(cert.needs_renew())

        with tempfile.TemporaryDirectory() as temp_dir:
            full_chain_cert = Certificate(FULL_CHAIN)
            # sanity check
            self.assertEqual(len(full_chain_cert.chain), 2)
            self.assertEqual(
                full_chain_cert.chain[0].certificate.serial_number,
                FIRST_CERT_SERIAL_NUMBER)
            self.assertEqual(
                full_chain_cert.chain[1].certificate.serial_number,
                SECOND_CERT_SERIAL_NUMBER)

            cert_only_path = os.path.join(temp_dir, 'cert.crt')
            chain_only_path = os.path.join(temp_dir, 'chain.crt')
            full_chain_path = os.path.join(temp_dir, 'chained.crt')
            full_chain_cert.save(cert_only_path,
                                 mode=CertificateSaveMode.CERT_ONLY)
            full_chain_cert.save(chain_only_path,
                                 mode=CertificateSaveMode.CHAIN_ONLY)
            full_chain_cert.save(full_chain_path,
                                 mode=CertificateSaveMode.FULL_CHAIN)

            cert_only = Certificate.load(cert_only_path)
            self.assertEqual(len(cert_only.chain), 1)
            self.assertEqual(cert_only.certificate.serial_number,
                             FIRST_CERT_SERIAL_NUMBER)

            chain_only = Certificate.load(chain_only_path)
            self.assertEqual(len(chain_only.chain), 1)
            self.assertEqual(chain_only.certificate.serial_number,
                             SECOND_CERT_SERIAL_NUMBER)

            full_chain = Certificate.load(full_chain_path)
            self.assertEqual(len(full_chain.chain), 2)
            self.assertEqual(full_chain.chain[0].certificate.serial_number,
                             FIRST_CERT_SERIAL_NUMBER)
            self.assertEqual(full_chain.chain[1].certificate.serial_number,
                             SECOND_CERT_SERIAL_NUMBER)