def init_save_csr(privkey, names, path, csrname="csr-certbot.pem"): """Initialize a CSR with the given private key. :param privkey: Key to include in the CSR :type privkey: :class:`certbot.le_util.Key` :param set names: `str` names to include in the CSR :param str path: Certificate save directory. :returns: CSR :rtype: :class:`certbot.le_util.CSR` """ config = zope.component.getUtility(interfaces.IConfig) csr_pem, csr_der = make_csr(privkey.pem, names, must_staple=config.must_staple) # Save CSR le_util.make_or_verify_dir(path, 0o755, os.geteuid(), config.strict_permissions) csr_f, csr_filename = le_util.unique_file( os.path.join(path, csrname), 0o644) csr_f.write(csr_pem) csr_f.close() logger.info("Creating CSR: %s", csr_filename) return le_util.CSR(csr_filename, csr_der, "der")
def test_obtain_certificate(self, mock_crypto_util): self._mock_obtain_certificate() csr = le_util.CSR(form="der", file=None, data=CSR_SAN) mock_crypto_util.init_save_csr.return_value = csr mock_crypto_util.init_save_key.return_value = mock.sentinel.key domains = ["example.com", "www.example.com"] # return_value is essentially set to (None, None) in # _mock_obtain_certificate(), which breaks this test. # Thus fixed by the next line. authzr = [] for domain in domains: authzr.append( mock.MagicMock( body=mock.MagicMock( identifier=mock.MagicMock( value=domain)))) self.client.auth_handler.get_authorizations.return_value = authzr self.assertEqual( self.client.obtain_certificate(domains), (mock.sentinel.certr, mock.sentinel.chain, mock.sentinel.key, csr)) mock_crypto_util.init_save_key.assert_called_once_with( self.config.rsa_key_size, self.config.key_dir) mock_crypto_util.init_save_csr.assert_called_once_with( mock.sentinel.key, domains, self.config.csr_dir) self._check_obtain_certificate()
def test_obtain_certificate_from_csr(self, mock_logger): self._mock_obtain_certificate() test_csr = le_util.CSR(form="der", file=None, data=CSR_SAN) auth_handler = self.client.auth_handler authzr = auth_handler.get_authorizations(self.eg_domains, False) self.assertEqual( (mock.sentinel.certr, mock.sentinel.chain), self.client.obtain_certificate_from_csr( self.eg_domains, test_csr, authzr=authzr)) # and that the cert was obtained correctly self._check_obtain_certificate() # Test for authzr=None self.assertEqual( (mock.sentinel.certr, mock.sentinel.chain), self.client.obtain_certificate_from_csr( self.eg_domains, test_csr, authzr=None)) auth_handler.get_authorizations.assert_called_with(self.eg_domains) # Test for no auth_handler self.client.auth_handler = None self.assertRaises( errors.Error, self.client.obtain_certificate_from_csr, self.eg_domains, test_csr) mock_logger.warning.assert_called_once_with(mock.ANY)
def handle_csr(self, parsed_args): """Process a --csr flag.""" if parsed_args.verb != "certonly": raise errors.Error("Currently, a CSR file may only be specified " "when obtaining a new or replacement " "via the certonly command. Please try the " "certonly command instead.") try: csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="der") typ = OpenSSL.crypto.FILETYPE_ASN1 domains = crypto_util.get_sans_from_csr( csr.data, OpenSSL.crypto.FILETYPE_ASN1) except OpenSSL.crypto.Error: try: e1 = traceback.format_exc() typ = OpenSSL.crypto.FILETYPE_PEM csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="pem") domains = crypto_util.get_sans_from_csr(csr.data, typ) except OpenSSL.crypto.Error: logger.debug("DER CSR parse error %s", e1) logger.debug("PEM CSR parse error %s", traceback.format_exc()) raise errors.Error("Failed to parse CSR file: {0}".format( parsed_args.csr[0])) # This is not necessary for webroot to work, however, # obtain_certificate_from_csr requires parsed_args.domains to be set for domain in domains: add_domains(parsed_args, domain) if not domains: # TODO: add CN to domains instead: raise errors.Error( "Unfortunately, your CSR %s needs to have a SubjectAltName for every domain" % parsed_args.csr[0]) parsed_args.actual_csr = (csr, typ) csr_domains, config_domains = set(domains), set(parsed_args.domains) if csr_domains != config_domains: raise errors.ConfigurationError( "Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}" .format(", ".join(csr_domains), ", ".join(config_domains)))
def test_pem_csr(self): csrfile = test_util.vector_path('csr.pem') data = test_util.load_vector('csr.pem') self.assertEqual(( OpenSSL.crypto.FILETYPE_PEM, le_util.CSR(file=csrfile, data=data, form="pem"), ["example.com"], ), self._call(csrfile, data))
def test_obtain_certificate_from_csr(self, mock_logger): self._mock_obtain_certificate() from certbot import cli test_csr = le_util.CSR(form="der", file=None, data=CSR_SAN) mock_parsed_args = mock.MagicMock() # The CLI should believe that this is a certonly request, because # a CSR would not be allowed with other kinds of requests! mock_parsed_args.verb = "certonly" with mock.patch("certbot.client.le_util.CSR") as mock_CSR: mock_CSR.return_value = test_csr mock_parsed_args.domains = self.eg_domains[:] mock_parser = mock.MagicMock(cli.HelpfulArgumentParser) cli.HelpfulArgumentParser.handle_csr(mock_parser, mock_parsed_args) # Now provoke an inconsistent domains error... mock_parsed_args.domains.append("hippopotamus.io") self.assertRaises(errors.ConfigurationError, cli.HelpfulArgumentParser.handle_csr, mock_parser, mock_parsed_args) authzr = self.client.auth_handler.get_authorizations(self.eg_domains, False) self.assertEqual( (mock.sentinel.certr, mock.sentinel.chain), self.client.obtain_certificate_from_csr( self.eg_domains, test_csr, authzr=authzr)) # and that the cert was obtained correctly self._check_obtain_certificate() # Test for authzr=None self.assertEqual( (mock.sentinel.certr, mock.sentinel.chain), self.client.obtain_certificate_from_csr( self.eg_domains, test_csr, authzr=None)) self.client.auth_handler.get_authorizations.assert_called_with( self.eg_domains) # Test for no auth_handler self.client.auth_handler = None self.assertRaises( errors.Error, self.client.obtain_certificate_from_csr, self.eg_domains, test_csr) mock_logger.warning.assert_called_once_with(mock.ANY)
def validate_key_csr(privkey, csr=None): """Validate Key and CSR files. Verifies that the client key and csr arguments are valid and correspond to one another. This does not currently check the names in the CSR due to the inability to read SANs from CSRs in python crypto libraries. If csr is left as None, only the key will be validated. :param privkey: Key associated with CSR :type privkey: :class:`certbot.le_util.Key` :param .le_util.CSR csr: CSR :raises .errors.Error: when validation fails """ # TODO: Handle all of these problems appropriately # The client can eventually do things like prompt the user # and allow the user to take more appropriate actions # Key must be readable and valid. if privkey.pem and not crypto_util.valid_privkey(privkey.pem): raise errors.Error("The provided key is not a valid key") if csr: if csr.form == "der": csr_obj = OpenSSL.crypto.load_certificate_request( OpenSSL.crypto.FILETYPE_ASN1, csr.data) csr = le_util.CSR( csr.file, OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, csr_obj), "pem") # If CSR is provided, it must be readable and valid. if csr.data and not crypto_util.valid_csr(csr.data): raise errors.Error("The provided CSR is not a valid CSR") # If both CSR and key are provided, the key must be the same key used # in the CSR. if csr.data and privkey.pem: if not crypto_util.csr_matches_pubkey(csr.data, privkey.pem): raise errors.Error("The key and CSR do not match")
def import_csr_file(csrfile, data): """Import a CSR file, which can be either PEM or DER. :param str csrfile: CSR filename :param str data: contents of the CSR file :returns: (`OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1`, le_util.CSR object representing the CSR, list of domains requested in the CSR) :rtype: tuple """ for form, typ in (("der", OpenSSL.crypto.FILETYPE_ASN1,), ("pem", OpenSSL.crypto.FILETYPE_PEM,),): try: domains = get_names_from_csr(data, typ) except OpenSSL.crypto.Error: logger.debug("CSR parse error (form=%s, typ=%s):", form, typ) logger.debug(traceback.format_exc()) continue return typ, le_util.CSR(file=csrfile, data=data, form=form), domains raise errors.Error("Failed to parse CSR file: {0}".format(csrfile))