mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-30 17:09:19 +00:00 
			
		
		
		
	Refactor signature request submission
Certidude client now reads configuration from /etc/certidude/client.conf, submits CSR-s and once signed configures services based on /etc/certidude/services.conf
This commit is contained in:
		| @@ -8,6 +8,7 @@ from OpenSSL import crypto | ||||
| from certidude import config, push | ||||
| from certidude.wrappers import Certificate, Request | ||||
| from certidude.signer import raw_sign | ||||
| from certidude import errors | ||||
|  | ||||
| RE_HOSTNAME = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$" | ||||
|  | ||||
| @@ -15,12 +16,6 @@ RE_HOSTNAME = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0 | ||||
| # https://jamielinux.com/docs/openssl-certificate-authority/ | ||||
| # http://pycopia.googlecode.com/svn/trunk/net/pycopia/ssl/certs.py | ||||
|  | ||||
| class RequestExists(Exception): | ||||
|     pass | ||||
|  | ||||
| class DuplicateCommonNameError(Exception): | ||||
|     pass | ||||
|  | ||||
| def publish_certificate(func): | ||||
|     # TODO: Implement e-mail and nginx notifications using hooks | ||||
|     def wrapped(csr, *args, **kwargs): | ||||
| @@ -68,9 +63,9 @@ def store_request(buf, overwrite=False): | ||||
|     # If there is cert, check if it's the same | ||||
|     if os.path.exists(request_path): | ||||
|         if open(request_path).read() == buf: | ||||
|             raise RequestExists("Request already exists") | ||||
|             raise errors.RequestExists("Request already exists") | ||||
|         else: | ||||
|             raise DuplicateCommonNameError("Another request with same common name already exists") | ||||
|             raise errors.DuplicateCommonNameError("Another request with same common name already exists") | ||||
|     else: | ||||
|         with open(request_path + ".part", "w") as fh: | ||||
|             fh.write(buf) | ||||
|   | ||||
							
								
								
									
										207
									
								
								certidude/cli.py
									
									
									
									
									
								
							
							
						
						
									
										207
									
								
								certidude/cli.py
									
									
									
									
									
								
							| @@ -9,6 +9,7 @@ import logging | ||||
| import os | ||||
| import pwd | ||||
| import re | ||||
| import requests | ||||
| import signal | ||||
| import socket | ||||
| import subprocess | ||||
| @@ -23,6 +24,7 @@ from time import sleep | ||||
| from setproctitle import setproctitle | ||||
| from OpenSSL import crypto | ||||
|  | ||||
|  | ||||
| env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True) | ||||
|  | ||||
| # Big fat warning: | ||||
| @@ -60,12 +62,162 @@ if os.getuid() >= 1000: | ||||
|         FIRST_NAME = gecos | ||||
|  | ||||
|  | ||||
| @click.command("spawn", help="Run privilege isolated signer process") | ||||
| @click.command("request", help="Run processes for requesting certificates and configuring services") | ||||
| @click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background") | ||||
| def certidude_spawn_request(fork): | ||||
|     from certidude.helpers import certidude_request_certificate | ||||
|     from configparser import ConfigParser | ||||
|  | ||||
|     clients = ConfigParser() | ||||
|     clients.readfp(open("/etc/certidude/client.conf")) | ||||
|  | ||||
|     services = ConfigParser() | ||||
|     services.readfp(open("/etc/certidude/services.conf")) | ||||
|  | ||||
|     # Process directories | ||||
|     run_dir = "/run/certidude" | ||||
|  | ||||
|     # Prepare signer PID-s directory | ||||
|     if not os.path.exists(run_dir): | ||||
|         click.echo("Creating: %s" % run_dir) | ||||
|         os.makedirs(run_dir) | ||||
|  | ||||
|     for certificate in clients.sections(): | ||||
|         if clients.get(certificate, "managed") != "true": | ||||
|             continue | ||||
|  | ||||
|         pid_path = os.path.join(run_dir, certificate + ".pid") | ||||
|  | ||||
|         try: | ||||
|             with open(pid_path) as fh: | ||||
|                 pid = int(fh.readline()) | ||||
|                 os.kill(pid, signal.SIGTERM) | ||||
|                 click.echo("Terminated process %d" % pid) | ||||
|             os.unlink(pid_path) | ||||
|         except (ValueError, ProcessLookupError, FileNotFoundError): | ||||
|             pass | ||||
|  | ||||
|         if fork: | ||||
|             child_pid = os.fork() | ||||
|         else: | ||||
|             child_pid = None | ||||
|  | ||||
|         if child_pid: | ||||
|             click.echo("Spawned certificate request process with PID %d" % (child_pid)) | ||||
|             continue | ||||
|  | ||||
|         with open(pid_path, "w") as fh: | ||||
|             fh.write("%d\n" % os.getpid()) | ||||
|         setproctitle("certidude spawn request %s" % certificate) | ||||
|         retries = 30 | ||||
|         while retries > 0: | ||||
|             try: | ||||
|                 certidude_request_certificate( | ||||
|                     clients.get(certificate, "server"), | ||||
|                     clients.get(certificate, "key_path"), | ||||
|                     clients.get(certificate, "request_path"), | ||||
|                     clients.get(certificate, "certificate_path"), | ||||
|                     clients.get(certificate, "authority_path"), | ||||
|                     socket.gethostname(), | ||||
|                     None, | ||||
|                     autosign=True, | ||||
|                     wait=True) | ||||
|                 break | ||||
|             except requests.exceptions.Timeout: | ||||
|                 retries -= 1 | ||||
|                 continue | ||||
|  | ||||
|         for endpoint in services.sections(): | ||||
|             if services.get(endpoint, "certificate") != certificate: | ||||
|                 continue | ||||
|  | ||||
|             csummer = hashlib.sha1() | ||||
|             csummer.update(endpoint.encode("ascii")) | ||||
|             csum = csummer.hexdigest() | ||||
|             uuid = csum[:8] + "-" + csum[8:12] + "-" + csum[12:16] + "-" + csum[16:20] + "-" + csum[20:32] | ||||
|  | ||||
|             # Set up IPsec via NetworkManager | ||||
|             if services.get(endpoint, "service") == "network-manager/strongswan": | ||||
|  | ||||
|                 config = configparser.ConfigParser() | ||||
|                 config.add_section("connection") | ||||
|                 config.add_section("vpn") | ||||
|                 config.add_section("ipv4") | ||||
|  | ||||
|                 config.set("connection", "id", endpoint) | ||||
|                 config.set("connection", "uuid", uuid) | ||||
|                 config.set("connection", "type", "vpn") | ||||
|  | ||||
|                 config.set("vpn", "service-type", "org.freedesktop.NetworkManager.strongswan") | ||||
|                 config.set("vpn", "userkey", clients.get(certificate, "key_path")) | ||||
|                 config.set("vpn", "usercert", clients.get(certificate, "certificate_path")) | ||||
|                 config.set("vpn", "encap", "no") | ||||
|                 config.set("vpn", "address", services.get(endpoint, "remote")) | ||||
|                 config.set("vpn", "virtual", "yes") | ||||
|                 config.set("vpn", "method", "key") | ||||
|                 config.set("vpn", "certificate", clients.get(certificate, "authority_path")) | ||||
|                 config.set("vpn", "ipcomp", "no") | ||||
|  | ||||
|                 config.set("ipv4", "method", "auto") | ||||
|  | ||||
|                 # Add routes, may need some more tweaking | ||||
|                 for index, subnet in enumerate(services.get(endpoint, "route").split(","), start=1): | ||||
|                     config.set("ipv4", "route%d" % index, subnet) | ||||
|  | ||||
|                 # Prevent creation of files with liberal permissions | ||||
|                 os.umask(0o177) | ||||
|  | ||||
|                 # Write keyfile | ||||
|                 with open(os.path.join("/etc/NetworkManager/system-connections", endpoint), "w") as configfile: | ||||
|                     config.write(configfile) | ||||
|                 continue | ||||
|  | ||||
|             # Set up IPsec via /etc/ipsec.conf | ||||
|             if services.get(endpoint, "service") == "strongswan": | ||||
|                 from ipsecparse import loads | ||||
|                 config = loads(open('/etc/ipsec.conf').read()) | ||||
|                 config["conn", endpoint] = dict( | ||||
|                     leftsourceip="%config", | ||||
|                     left="%defaultroute", | ||||
|                     leftcert=clients.get(certificate, "certificate_path"), | ||||
|                     rightid="%any", | ||||
|                     right=services.get(endpoint, "remote"), | ||||
|                     rightsubnet=services.get(endpoint, "route"), | ||||
|                     keyexchange="ikev2", | ||||
|                     keyingtries="300", | ||||
|                     dpdaction="restart", | ||||
|                     closeaction="restart", | ||||
|                     auto="start") | ||||
|                 with open("/etc/ipsec.conf.part", "w") as fh: | ||||
|                     fh.write(config.dumps()) | ||||
|                 os.rename("/etc/ipsec.conf.part", "/etc/ipsec.conf") | ||||
|  | ||||
|                 # Regenerate /etc/ipsec.secrets | ||||
|                 with open("/etc/ipsec.secrets.part", "w") as fh: | ||||
|                     for filename in os.listdir("/etc/ipsec.d/private"): | ||||
|                         if not filename.endswith(".pem"): | ||||
|                             continue | ||||
|                         fh.write(": RSA /etc/ipsec.d/private/%s\n" % filename) | ||||
|                 os.rename("/etc/ipsec.secrets.part", "/etc/ipsec.secrets") | ||||
|  | ||||
|                 # Attempt to reload config or start if it's not running | ||||
|                 if os.system("ipsec update") == 130: | ||||
|                     os.system("ipsec start") | ||||
|                 continue | ||||
|  | ||||
|  | ||||
|  | ||||
|             # TODO: OpenVPN, Puppet, OpenLDAP, intranet HTTPS, <insert awesomeness here> | ||||
|  | ||||
|         os.unlink(pid_path) | ||||
|  | ||||
|  | ||||
| @click.command("signer", help="Run privilege isolated signer process") | ||||
| @click.option("-k", "--kill", default=False, is_flag=True, help="Kill previous instance") | ||||
| @click.option("-n", "--no-interaction", default=True, is_flag=True, help="Don't load password protected keys") | ||||
| def certidude_spawn(kill, no_interaction): | ||||
| def certidude_spawn_signer(kill, no_interaction): | ||||
|     """ | ||||
|     Spawn processes for signers | ||||
|     Spawn privilege isolated signer process | ||||
|     """ | ||||
|     from certidude import config | ||||
|  | ||||
| @@ -80,7 +232,6 @@ def certidude_spawn(kill, no_interaction): | ||||
|  | ||||
|     # Process directories | ||||
|     run_dir = "/run/certidude" | ||||
|     chroot_dir = os.path.join(run_dir, "jail") | ||||
|  | ||||
|     # Prepare signer PID-s directory | ||||
|     if not os.path.exists(run_dir): | ||||
| @@ -92,15 +243,13 @@ def certidude_spawn(kill, no_interaction): | ||||
|     "".encode("charmap") | ||||
|  | ||||
|     # Prepare chroot directories | ||||
|     chroot_dir = os.path.join(run_dir, "jail") | ||||
|     if not os.path.exists(os.path.join(chroot_dir, "dev")): | ||||
|         os.makedirs(os.path.join(chroot_dir, "dev")) | ||||
|     if not os.path.exists(os.path.join(chroot_dir, "dev", "urandom")): | ||||
|         # TODO: use os.mknod instead | ||||
|         os.system("mknod -m 444 %s c 1 9" % os.path.join(chroot_dir, "dev", "urandom")) | ||||
|  | ||||
|     ca_loaded = False | ||||
|  | ||||
|  | ||||
|     try: | ||||
|         with open(config.SIGNER_PID_PATH) as fh: | ||||
|             pid = int(fh.readline()) | ||||
| @@ -122,26 +271,27 @@ def certidude_spawn(kill, no_interaction): | ||||
|  | ||||
|     child_pid = os.fork() | ||||
|  | ||||
|     if child_pid == 0: | ||||
|         with open(config.SIGNER_PID_PATH, "w") as fh: | ||||
|             fh.write("%d\n" % os.getpid()) | ||||
|  | ||||
| #        setproctitle("%s spawn %s" % (sys.argv[0], ca.common_name)) | ||||
|         logging.basicConfig( | ||||
|             filename="/var/log/signer.log", | ||||
|             level=logging.INFO) | ||||
|         server = SignServer( | ||||
|             config.SIGNER_SOCKET_PATH, | ||||
|             config.AUTHORITY_PRIVATE_KEY_PATH, | ||||
|             config.AUTHORITY_CERTIFICATE_PATH, | ||||
|             config.CERTIFICATE_LIFETIME, | ||||
|             config.CERTIFICATE_BASIC_CONSTRAINTS, | ||||
|             config.CERTIFICATE_KEY_USAGE_FLAGS, | ||||
|             config.CERTIFICATE_EXTENDED_KEY_USAGE_FLAGS, | ||||
|             config.REVOCATION_LIST_LIFETIME) | ||||
|         asyncore.loop() | ||||
|     else: | ||||
|     if child_pid: | ||||
|         click.echo("Spawned certidude signer process with PID %d at %s" % (child_pid, config.SIGNER_SOCKET_PATH)) | ||||
|         return | ||||
|  | ||||
|     setproctitle("certidude spawn signer" % section) | ||||
|     with open(config.SIGNER_PID_PATH, "w") as fh: | ||||
|         fh.write("%d\n" % os.getpid()) | ||||
|     logging.basicConfig( | ||||
|         filename="/var/log/signer.log", | ||||
|         level=logging.INFO) | ||||
|     server = SignServer( | ||||
|         config.SIGNER_SOCKET_PATH, | ||||
|         config.AUTHORITY_PRIVATE_KEY_PATH, | ||||
|         config.AUTHORITY_CERTIFICATE_PATH, | ||||
|         config.CERTIFICATE_LIFETIME, | ||||
|         config.CERTIFICATE_BASIC_CONSTRAINTS, | ||||
|         config.CERTIFICATE_KEY_USAGE_FLAGS, | ||||
|         config.CERTIFICATE_EXTENDED_KEY_USAGE_FLAGS, | ||||
|         config.REVOCATION_LIST_LIFETIME) | ||||
|     asyncore.loop() | ||||
|  | ||||
|  | ||||
|  | ||||
| @click.command("client", help="Setup X.509 certificates for application") | ||||
| @@ -894,6 +1044,9 @@ def certidude_setup_openvpn(): pass | ||||
| @click.group("setup", help="Getting started section") | ||||
| def certidude_setup(): pass | ||||
|  | ||||
| @click.group("spawn", help="Spawn helper processes") | ||||
| def certidude_spawn(): pass | ||||
|  | ||||
| @click.group() | ||||
| def entry_point(): pass | ||||
|  | ||||
| @@ -907,6 +1060,8 @@ certidude_setup.add_command(certidude_setup_openvpn) | ||||
| certidude_setup.add_command(certidude_setup_strongswan) | ||||
| certidude_setup.add_command(certidude_setup_client) | ||||
| certidude_setup.add_command(certidude_setup_production) | ||||
| certidude_spawn.add_command(certidude_spawn_request) | ||||
| certidude_spawn.add_command(certidude_spawn_signer) | ||||
| entry_point.add_command(certidude_setup) | ||||
| entry_point.add_command(certidude_serve) | ||||
| entry_point.add_command(certidude_spawn) | ||||
|   | ||||
							
								
								
									
										12
									
								
								certidude/errors.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								certidude/errors.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
|  | ||||
| class RequestExists(Exception): | ||||
|     pass | ||||
|  | ||||
| class FatalError(Exception): | ||||
|     """ | ||||
|     Exception to be raised when user intervention is required | ||||
|     """ | ||||
|     pass | ||||
|  | ||||
| class DuplicateCommonNameError(FatalError): | ||||
|     pass | ||||
| @@ -1,11 +1,13 @@ | ||||
|  | ||||
| import click | ||||
| import os | ||||
| import requests | ||||
| import urllib.request | ||||
| from certidude import errors | ||||
| from certidude.wrappers import Certificate, Request | ||||
| from OpenSSL import crypto | ||||
|  | ||||
| def certidude_request_certificate(url, key_path, request_path, certificate_path, authority_path, common_name, org_unit, email_address=None, given_name=None, surname=None, autosign=False, wait=False, key_usage=None, extended_key_usage=None, ip_address=None, dns=None): | ||||
| def certidude_request_certificate(url, key_path, request_path, certificate_path, authority_path, common_name, org_unit=None, email_address=None, given_name=None, surname=None, autosign=False, wait=False, key_usage=None, extended_key_usage=None, ip_address=None, dns=None): | ||||
|     """ | ||||
|     Exchange CSR for certificate using Certidude HTTP API server | ||||
|     """ | ||||
| @@ -18,12 +20,10 @@ def certidude_request_certificate(url, key_path, request_path, certificate_path, | ||||
|         request_params.add("wait=forever") | ||||
|  | ||||
|     # Expand ca.example.com to http://ca.example.com/api/ | ||||
|     if not "/" in url: | ||||
|     if not url.endswith("/"): | ||||
|         url += "/api/" | ||||
|     if "//" not in url: | ||||
|         url = "http://" + url | ||||
|     if not url.endswith("/"): | ||||
|         url = url + "/" | ||||
|  | ||||
|     authority_url = url + "certificate" | ||||
|     request_url = url + "request" | ||||
| @@ -31,131 +31,116 @@ def certidude_request_certificate(url, key_path, request_path, certificate_path, | ||||
|     if request_params: | ||||
|         request_url = request_url + "?" + "&".join(request_params) | ||||
|  | ||||
|     if os.path.exists(certificate_path): | ||||
|         click.echo("Found certificate: %s" % certificate_path) | ||||
|         # TODO: Check certificate validity, download CRL? | ||||
|         return | ||||
|  | ||||
|     if os.path.exists(authority_path): | ||||
|         click.echo("Found CA certificate in: %s" % authority_path) | ||||
|     else: | ||||
|         if authority_url: | ||||
|             click.echo("Attempting to fetch CA certificate from %s" % authority_url) | ||||
|             try: | ||||
|                 with urllib.request.urlopen(authority_url) as fh: | ||||
|                     buf = fh.read() | ||||
|                     try: | ||||
|                         cert = crypto.load_certificate(crypto.FILETYPE_PEM, buf) | ||||
|                     except crypto.Error: | ||||
|                         raise ValueError("Failed to parse PEM: %s" % buf) | ||||
|                     with open(authority_path + ".part", "wb") as oh: | ||||
|                         oh.write(buf) | ||||
|                     click.echo("Writing CA certificate to: %s" % authority_path) | ||||
|                     os.rename(authority_path + ".part", authority_path) | ||||
|             except urllib.error.HTTPError as e: | ||||
|                 click.echo("Failed to fetch CA certificate, server responded with: %d %s" % (e.code, e.reason), err=True) | ||||
|                 return 1 | ||||
|         else: | ||||
|             raise FileNotFoundError("CA certificate not found and no URL specified") | ||||
|         click.echo("Attempting to fetch CA certificate from %s" % authority_url) | ||||
|  | ||||
|         try: | ||||
|             r = requests.get(authority_url) | ||||
|             cert = crypto.load_certificate(crypto.FILETYPE_PEM, r.text) | ||||
|         except crypto.Error: | ||||
|             raise ValueError("Failed to parse PEM: %s" % r.text) | ||||
|         with open(authority_path + ".part", "w") as oh: | ||||
|             oh.write(r.text) | ||||
|         click.echo("Writing CA certificate to: %s" % authority_path) | ||||
|         os.rename(authority_path + ".part", authority_path) | ||||
|  | ||||
|     try: | ||||
|         certificate = Certificate(open(certificate_path)) | ||||
|         click.echo("Found certificate: %s" % certificate_path) | ||||
|         request = Request(open(request_path)) | ||||
|         click.echo("Found signing request: %s" % request_path) | ||||
|     except FileNotFoundError: | ||||
|         try: | ||||
|             request = Request(open(request_path)) | ||||
|             click.echo("Found signing request: %s" % request_path) | ||||
|         except FileNotFoundError: | ||||
|  | ||||
|             # Construct private key | ||||
|             click.echo("Generating 4096-bit RSA key...") | ||||
|             key = crypto.PKey() | ||||
|             key.generate_key(crypto.TYPE_RSA, 4096) | ||||
|         # Construct private key | ||||
|         click.echo("Generating 4096-bit RSA key...") | ||||
|         key = crypto.PKey() | ||||
|         key.generate_key(crypto.TYPE_RSA, 4096) | ||||
|  | ||||
|             # Dump private key | ||||
|             os.umask(0o077) | ||||
|             with open(key_path + ".part", "wb") as fh: | ||||
|                 fh.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) | ||||
|         # Dump private key | ||||
|         os.umask(0o077) | ||||
|         with open(key_path + ".part", "wb") as fh: | ||||
|             fh.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) | ||||
|  | ||||
|             # Construct CSR | ||||
|             csr = crypto.X509Req() | ||||
|             csr.set_version(2) # Corresponds to X.509v3 | ||||
|             csr.set_pubkey(key) | ||||
|             request = Request(csr) | ||||
|         # Construct CSR | ||||
|         csr = crypto.X509Req() | ||||
|         csr.set_version(2) # Corresponds to X.509v3 | ||||
|         csr.set_pubkey(key) | ||||
|         request = Request(csr) | ||||
|  | ||||
|             # Set subject attributes | ||||
|             request.common_name = common_name | ||||
|             if given_name: | ||||
|                 request.given_name = given_name | ||||
|             if surname: | ||||
|                 request.surname = surname | ||||
|             if org_unit: | ||||
|                 request.organizational_unit = org_unit | ||||
|         # Set subject attributes | ||||
|         request.common_name = common_name | ||||
|         if given_name: | ||||
|             request.given_name = given_name | ||||
|         if surname: | ||||
|             request.surname = surname | ||||
|         if org_unit: | ||||
|             request.organizational_unit = org_unit | ||||
|  | ||||
|             # Collect subject alternative names | ||||
|             subject_alt_name = set() | ||||
|             if email_address: | ||||
|                 subject_alt_name.add("email:" + email_address) | ||||
|             if ip_address: | ||||
|                 subject_alt_name.add("IP:" + ip_address) | ||||
|             if dns: | ||||
|                 subject_alt_name.add("DNS:" + dns) | ||||
|         # Collect subject alternative names | ||||
|         subject_alt_name = set() | ||||
|         if email_address: | ||||
|             subject_alt_name.add("email:" + email_address) | ||||
|         if ip_address: | ||||
|             subject_alt_name.add("IP:" + ip_address) | ||||
|         if dns: | ||||
|             subject_alt_name.add("DNS:" + dns) | ||||
|  | ||||
|             # Set extensions | ||||
|             extensions = [] | ||||
|             if key_usage: | ||||
|                 extensions.append(("keyUsage", key_usage, True)) | ||||
|             if extended_key_usage: | ||||
|                 extensions.append(("extendedKeyUsage", extended_key_usage, True)) | ||||
|             if subject_alt_name: | ||||
|                 extensions.append(("subjectAltName", ", ".join(subject_alt_name), True)) | ||||
|             request.set_extensions(extensions) | ||||
|         # Set extensions | ||||
|         extensions = [] | ||||
|         if key_usage: | ||||
|             extensions.append(("keyUsage", key_usage, True)) | ||||
|         if extended_key_usage: | ||||
|             extensions.append(("extendedKeyUsage", extended_key_usage, True)) | ||||
|         if subject_alt_name: | ||||
|             extensions.append(("subjectAltName", ", ".join(subject_alt_name), True)) | ||||
|         request.set_extensions(extensions) | ||||
|  | ||||
|             # Dump CSR | ||||
|             os.umask(0o022) | ||||
|             with open(request_path + ".part", "w") as fh: | ||||
|                 fh.write(request.dump()) | ||||
|         # Dump CSR | ||||
|         os.umask(0o022) | ||||
|         with open(request_path + ".part", "w") as fh: | ||||
|             fh.write(request.dump()) | ||||
|  | ||||
|             click.echo("Writing private key to: %s" % key_path) | ||||
|             os.rename(key_path + ".part", key_path) | ||||
|             click.echo("Writing certificate signing request to: %s" % request_path) | ||||
|             os.rename(request_path + ".part", request_path) | ||||
|         click.echo("Writing private key to: %s" % key_path) | ||||
|         os.rename(key_path + ".part", key_path) | ||||
|         click.echo("Writing certificate signing request to: %s" % request_path) | ||||
|         os.rename(request_path + ".part", request_path) | ||||
|  | ||||
|  | ||||
|         with open(request_path, "rb") as fh: | ||||
|             buf = fh.read() | ||||
|             submission = urllib.request.Request(request_url, buf) | ||||
|             submission.add_header("User-Agent", "Certidude") | ||||
|             submission.add_header("Content-Type", "application/pkcs10") | ||||
|             submission.add_header("Accept", "application/x-x509-user-cert") | ||||
|     click.echo("Submitting to %s, waiting for response..." % request_url) | ||||
|     submission = requests.post(request_url, | ||||
|         data=open(request_path), | ||||
|         headers={"User-Agent": "Certidude", "Content-Type": "application/pkcs10", "Accept": "application/x-x509-user-cert"}) | ||||
|  | ||||
|             click.echo("Submitting to %s, waiting for response..." % request_url) | ||||
|             try: | ||||
|                 response = urllib.request.urlopen(submission) | ||||
|                 buf = response.read() | ||||
|                 if response.code == 202: | ||||
|                     click.echo("No waiting was requested and server responded with 202 Accepted, run this command again once the certificate is signed") | ||||
|                     return 1 | ||||
|                 assert buf, "Server responded with no body, status code %d" % response.code | ||||
|                 cert = crypto.load_certificate(crypto.FILETYPE_PEM, buf) | ||||
|             except crypto.Error: | ||||
|                 if buf == b'-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n': | ||||
|                     raise ValueError("Server refused to sign the request") # TODO: Raise proper exception | ||||
|                 else: | ||||
|                     raise ValueError("Failed to parse PEM: %s" % buf) | ||||
|             except urllib.error.HTTPError as e: | ||||
|                 if e.code == 409: | ||||
|                     click.echo("Different signing request with same CN is already present on server, server refuses to overwrite", err=True) | ||||
|                     return 2 | ||||
|                 else: | ||||
|                     click.echo("Failed to fetch certificate, server responded with: %d %s" % (e.code, e.reason), err=True) | ||||
|                     return 3 | ||||
|             else: | ||||
|                 if response.code == 202: | ||||
|                     click.echo("Server stored the request for processing (202 Accepted), but waiting was not requested, hence quitting for now", err=True) | ||||
|                     return 254 | ||||
|     if submission.status_code == requests.codes.ok: | ||||
|         pass | ||||
|     if submission.status_code == requests.codes.accepted: | ||||
|         # Server stored the request for processing (202 Accepted), but waiting was not requested, hence quitting for now | ||||
|         return | ||||
|     if submission.status_code == requests.codes.conflict: | ||||
|         raise errors.DuplicateCommonNameError("Different signing request with same CN is already present on server, server refuses to overwrite") | ||||
|     else: | ||||
|         submission.raise_for_status() | ||||
|  | ||||
|             os.umask(0o022) | ||||
|             with open(certificate_path + ".part", "wb") as gh: | ||||
|                 gh.write(buf) | ||||
|     if submission.text == '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n': | ||||
|         # Should the client retry or disable request submission? | ||||
|         raise ValueError("Server refused to sign the request") # TODO: Raise proper exception | ||||
|  | ||||
|             click.echo("Writing certificate to: %s" % certificate_path) | ||||
|             os.rename(certificate_path + ".part", certificate_path) | ||||
|     try: | ||||
|         cert = crypto.load_certificate(crypto.FILETYPE_PEM, submission.text) | ||||
|     except crypto.Error: | ||||
|         raise ValueError("Failed to parse PEM: %s" % buf) | ||||
|  | ||||
|     os.umask(0o022) | ||||
|     with open(certificate_path + ".part", "w") as fh: | ||||
|         fh.write(submission.text) | ||||
|  | ||||
|     click.echo("Writing certificate to: %s" % certificate_path) | ||||
|     os.rename(certificate_path + ".part", certificate_path) | ||||
|  | ||||
|     # TODO: Validate fetched certificate against CA | ||||
|     # TODO: Check that recevied certificate CN and pubkey match | ||||
|   | ||||
		Reference in New Issue
	
	Block a user