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)
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
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)
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)
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
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
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)
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