예제 #1
0
    def test_load_key_unsecure_permissions(self):
        def unsecure_opener(path, flags):
            return os.open(path, flags, 0o777)

        with tempfile.TemporaryDirectory() as tmpdir:
            filename = os.path.join(tmpdir, 'rsa.pem')
            with open(filename, "w", opener=unsecure_opener) as f:
                f.write(RSA_TEST_KEY)

            with self.assertRaises(X509Error):
                PrivateKeyLoader.load(filename)
예제 #2
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
예제 #3
0
    def test_load_rsa_key(self):
        with tempfile.TemporaryDirectory() as tmpdir:
            filename = os.path.join(tmpdir, 'rsa.pem')
            with open(filename, "w", opener=secure_opener) as f:
                f.write(RSA_TEST_KEY)
            key = PrivateKeyLoader.load(filename)

        self.assertIsInstance(key, RSAPrivateKey)
예제 #4
0
    def _handle_order_finalized(self, cert_id, key_type_id):
        """Handles ORDER_FINALIZED status. Performs the following actions:
            - Attempts to fetch the signed certificate from the ACME directory
            - Persists the certificate on disk
        """
        logger.info("Handling order finalized event 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'))
        except (OSError, X509Error):
            logger.exception(
                "Failed to load new private key for certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.ACMECHIEF_ERROR

        cert_details = self.config.certificates[cert_id]

        csr_id = CertificateSigningRequest.generate_csr_id(
            public_key_pem=private_key.public_pem,
            common_name=cert_details['CN'],
            sans=cert_details['SNI'],
        )

        session = self._get_acme_session(cert_details)

        try:
            certificate = session.get_certificate(csr_id)
        except ACMETimeoutFetchingCertificateError:
            logger.exception(
                "Unable to fetch certificate for an already finalized order for certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.ORDER_FINALIZED
        except ACMEIssuedCertificateError:
            logger.warning(
                "Unable to handle certificate issued by the ACME directory for certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.CERTIFICATE_ISSUED
        except ACMEError:
            logger.exception(
                "Problem getting certificate for certificate %s / %s", cert_id,
                key_type_id)
            return CertificateStatus.ACMEDIR_ERROR

        try:
            certificate.save(self._get_path(cert_id,
                                            key_type_id,
                                            public=True,
                                            kind='new',
                                            cert_type='full_chain'),
                             mode=CertificateSaveMode.FULL_CHAIN)
        except OSError:
            logger.exception("Problem persisting certificate %s / %s on disk",
                             cert_id, key_type_id)
            return CertificateStatus.CERTIFICATE_ISSUED

        return self._handle_ready_to_be_pushed(cert_id, key_type_id)
예제 #5
0
    def _handle_pushed_csr(self, cert_id, key_type_id):
        """Handles PUSHED_CSR status. Performs the following actions:
            - Checks that challenges have been validated
            - Passes the ball to the next status handle
        """
        logger.info("Handling pushed CSR event 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'))
        except (OSError, X509Error):
            logger.exception(
                "Failed to load new private key for certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.ACMECHIEF_ERROR

        cert_details = self.config.certificates[cert_id]

        csr_id = CertificateSigningRequest.generate_csr_id(
            public_key_pem=private_key.public_pem,
            common_name=cert_details['CN'],
            sans=cert_details['SNI'],
        )

        challenge_type = CHALLENGE_TYPES[cert_details['challenge']]

        session = self._get_acme_session(cert_details)

        try:
            challenges = session.challenges[csr_id][challenge_type]
        except KeyError:
            logger.exception(
                "Could not find challenge for challenge type %s, certificate %s / %s",
                challenge_type, cert_id, key_type_id)
            return CertificateStatus.ACMECHIEF_ERROR

        for challenge in challenges:
            if challenge.challenge_type is ACMEChallengeType.DNS01:
                validation_params = {
                    'dns_servers':
                    self.config.challenges[ACMEChallengeType.DNS01]
                    ['validation_dns_servers']
                }
            else:
                validation_params = {}

            if challenge.validate(
                    **validation_params) is not ACMEChallengeValidation.VALID:
                # keep the issuance process in this step till all the challenges have been validated
                logger.warning("Unable to validate challenge %s", challenge)
                return CertificateStatus.CSR_PUSHED

        status = CertificateStatus.CHALLENGES_VALIDATED
        status = self._handle_validated_challenges(cert_id, key_type_id)

        return status
예제 #6
0
    def _handle_pushed_challenges(self, cert_id, key_type_id):  # pylint: disable=too-many-return-statements
        """Handles CHALLENGES_PUSHED status. Performs the following actions:
            - Attempts to finalize the ACME order.
            - Passes the ball to the next status handler.
        """
        logger.info("Handling pushed challenges event 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'))
        except (OSError, X509Error):
            logger.exception(
                "Failed to load new private key for certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.ACMECHIEF_ERROR

        cert_details = self.config.certificates[cert_id]

        csr_id = CertificateSigningRequest.generate_csr_id(
            public_key_pem=private_key.public_pem,
            common_name=cert_details['CN'],
            sans=cert_details['SNI'],
        )

        session = self._get_acme_session(cert_details)
        try:
            session.finalize_order(csr_id)
        except ACMEChallengeNotValidatedError:
            logger.warning(
                "ACME Directory hasn't validated the challenge(s) yet for certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.CHALLENGES_PUSHED
        except ACMEInvalidChallengeError:
            logger.warning(
                "ACME Directory has rejected the challenge(s) for certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.CHALLENGES_REJECTED
        except ACMEOrderNotFound:
            logger.exception(
                "Could not find ACME order when attempting to get the certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.ACMECHIEF_ERROR
        except ACMEError:
            logger.exception(
                "Problem getting certificate for certificate %s / %s", cert_id,
                key_type_id)
            return CertificateStatus.ACMEDIR_ERROR

        status = CertificateStatus.ORDER_FINALIZED
        status = self._handle_order_finalized(cert_id, key_type_id)

        return status
예제 #7
0
    def load(cls, account_id, base_path=BASEPATH, directory_url=DIRECTORY_URL):
        """Load the account with the specified account_id from disk"""
        logger.debug("Loading ACME account %s from directory: %s", account_id,
                     directory_url)
        paths = ACMEAccount._get_paths(account_id, base_path=base_path)

        key = PrivateKeyLoader.load(paths[ACMEAccountFiles.KEY])
        with open(paths[ACMEAccountFiles.REGR], 'r') as regr_file:
            regr = messages.RegistrationResource.json_loads(regr_file.read())

        return ACMEAccount(key=key,
                           regr=regr,
                           base_path=base_path,
                           directory_url=directory_url)
예제 #8
0
    def _handle_validated_challenges(self, cert_id, key_type_id):
        """Handles CHALLENGES_VALIDATED status. Performs the following actions:
            - pushes solved challenges to the ACME directory
            - Passes the ball to the next status handler
        """
        logger.info("Handling validated challenges event 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'))
        except (OSError, X509Error):
            logger.exception(
                "Failed to load new private key for certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.ACMECHIEF_ERROR

        cert_details = self.config.certificates[cert_id]

        csr_id = CertificateSigningRequest.generate_csr_id(
            public_key_pem=private_key.public_pem,
            common_name=cert_details['CN'],
            sans=cert_details['SNI'],
        )
        session = self._get_acme_session(cert_details)
        challenge_type = CHALLENGE_TYPES[cert_details['challenge']]
        try:
            session.push_solved_challenges(csr_id,
                                           challenge_type=challenge_type)
        except ACMEOrderNotFound:
            # unable to find CSR in current ACME session, go back to the initial step
            logger.exception(
                "Could not find ACME order when pushing solved challenges for challenge type %s, "
                "certificate %s / %s", challenge_type, cert_id, key_type_id)
            return CertificateStatus.ACMECHIEF_ERROR

        try:
            return self._handle_pushed_challenges(cert_id, key_type_id)
        except ACMEOrderNotFound:
            logger.exception(
                "Could not find ACME order when handling pushed challenges for certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.ACMECHIEF_ERROR
        except ACMEError:
            logger.exception(
                "ACMEError when handling pushed challenges for certificate %s / %s",
                cert_id, key_type_id)
            return CertificateStatus.CHALLENGES_PUSHED