def _sign(csr, buf, profile, skip_notify=False, skip_push=False, overwrite=False, signer=None): # TODO: CRLDistributionPoints, OCSP URL, Certificate URL assert buf.startswith(b"-----BEGIN ") assert isinstance(csr, CertificationRequest) csr_pubkey = asymmetric.load_public_key( csr["certification_request_info"]["subject_pk_info"]) common_name = csr["certification_request_info"]["subject"].native[ "common_name"] cert_path = os.path.join(config.SIGNED_DIR, "%s.pem" % common_name) renew = False attachments = [ (buf, "application/x-pem-file", common_name + ".csr"), ] revoked_path = None overwritten = False # Move existing certificate if necessary if os.path.exists(cert_path): with open(cert_path, "rb") as fh: prev_buf = fh.read() header, _, der_bytes = pem.unarmor(prev_buf) prev = x509.Certificate.load(der_bytes) # TODO: assert validity here again? renew = \ asymmetric.load_public_key(prev["tbs_certificate"]["subject_public_key_info"]) == \ csr_pubkey # BUGBUG: is this enough? if overwrite: # TODO: is this the best approach? # TODO: why didn't unittest detect bugs here? prev_serial_hex = "%x" % prev.serial_number revoked_path = os.path.join(config.REVOKED_DIR, "%040x.pem" % prev.serial_number) os.rename(cert_path, revoked_path) attachments += [(prev_buf, "application/x-pem-file", "deprecated.crt" if renew else "overwritten.crt")] overwritten = True else: raise FileExistsError("Will not overwrite existing certificate") builder = CertificateBuilder( cn_to_dn(common_name, const.FQDN, o=certificate["tbs_certificate"]["subject"].native.get( "organization_name"), ou=profile.ou), csr_pubkey) builder.serial_number = generate_serial() now = datetime.utcnow() builder.begin_date = now - const.CLOCK_SKEW_TOLERANCE builder.end_date = now + timedelta(days=profile.lifetime) builder.issuer = certificate builder.ca = profile.ca builder.key_usage = profile.key_usage builder.extended_key_usage = profile.extended_key_usage builder.subject_alt_domains = [common_name] builder.ocsp_url = profile.responder_url builder.crl_url = profile.revoked_url end_entity_cert = builder.build(private_key) end_entity_cert_buf = asymmetric.dump_certificate(end_entity_cert) with open(cert_path + ".part", "wb") as fh: fh.write(end_entity_cert_buf) os.rename(cert_path + ".part", cert_path) attachments.append( (end_entity_cert_buf, "application/x-pem-file", common_name + ".crt")) cert_serial_hex = "%x" % end_entity_cert.serial_number # Create symlink link_name = os.path.join(config.SIGNED_BY_SERIAL_DIR, "%040x.pem" % end_entity_cert.serial_number) assert not os.path.exists( link_name ), "Certificate with same serial number already exists: %s" % link_name os.symlink("../%s.pem" % common_name, link_name) # Copy filesystem attributes to newly signed certificate if revoked_path: for key in listxattr(revoked_path): if not key.startswith(b"user."): continue setxattr(cert_path, key, getxattr(revoked_path, key)) # Attach signer username if signer: setxattr(cert_path, "user.signature.username", signer) if not skip_notify: # Send mail if renew: # Same keypair mailer.send("certificate-renewed.md", **locals()) else: # New keypair mailer.send("certificate-signed.md", **locals()) if not skip_push: url = config.LONG_POLL_PUBLISH % hashlib.sha256(buf).hexdigest() click.echo("Publishing certificate at %s ..." % url) requests.post(url, data=end_entity_cert_buf, headers={ "User-Agent": "Certidude API", "Content-Type": "application/x-x509-user-cert" }) if renew: # TODO: certificate-renewed event push.publish("certificate-revoked", common_name) push.publish("request-signed", common_name) else: push.publish("request-signed", common_name) return end_entity_cert, end_entity_cert_buf
def generate_client_certs(domain, base_year, quiet=False): ca_private_key = load_private('ca') ca_cert = load_cert('ca') ca2_private_key = load_private('ca2') ca2_cert = load_cert('ca2') public_key = load_public('client') crl_url = 'http://crls.{}:9991/client.crl'.format(domain) # Certificate that is valid if not quiet: write('Generating good client cert ... ', end='') builder = CertificateBuilder( { 'country_name': 'US', 'state_or_province_name': 'Massachusetts', 'locality_name': 'Newbury', 'organization_name': 'TLS Client Certificates Limited', 'common_name': 'Good TLS Client Certificate', }, public_key) builder.issuer = ca_cert builder.crl_url = crl_url builder.begin_date = datetime(base_year, 1, 1, 0, 0, 0, tzinfo=timezone.utc) builder.end_date = datetime(base_year + 3, 1, 1, 0, 0, 0, tzinfo=timezone.utc) certificate = builder.build(ca_private_key) dump_cert('client-good', certificate) if not quiet: write('done') # Certificate that has expired if not quiet: write('Generating expired client cert ... ', end='') builder = CertificateBuilder( { 'country_name': 'US', 'state_or_province_name': 'Massachusetts', 'locality_name': 'Newbury', 'organization_name': 'TLS Client Certificates Limited', 'common_name': 'Expired TLS Client Certificate', }, public_key) builder.issuer = ca_cert builder.crl_url = crl_url builder.begin_date = datetime(base_year - 1, 1, 1, 0, 0, 0, tzinfo=timezone.utc) builder.end_date = datetime(base_year, 1, 1, 0, 0, 0, tzinfo=timezone.utc) certificate = builder.build(ca_private_key) dump_cert('client-expired', certificate) if not quiet: write('done') # Certificate that is not yet valid if not quiet: write('Generating future client cert ... ', end='') builder = CertificateBuilder( { 'country_name': 'US', 'state_or_province_name': 'Massachusetts', 'locality_name': 'Newbury', 'organization_name': 'TLS Client Certificates Limited', 'common_name': 'Future TLS Client Certificate', }, public_key) builder.issuer = ca_cert builder.crl_url = crl_url builder.begin_date = datetime(base_year + 3, 1, 1, 0, 0, 0, tzinfo=timezone.utc) builder.end_date = datetime(base_year + 4, 1, 1, 0, 0, 0, tzinfo=timezone.utc) certificate = builder.build(ca_private_key) dump_cert('client-future', certificate) if not quiet: write('done') # Certificate issued by untrusted CA if not quiet: write('Generating untrusted client cert ... ', end='') builder = CertificateBuilder( { 'country_name': 'US', 'state_or_province_name': 'Massachusetts', 'locality_name': 'Newbury', 'organization_name': 'TLS Client Certificates Limited', 'common_name': 'Untrusted TLS Client Certificate', }, public_key) builder.issuer = ca2_cert builder.begin_date = datetime(base_year, 1, 1, 0, 0, 0, tzinfo=timezone.utc) builder.end_date = datetime(base_year + 3, 1, 1, 0, 0, 0, tzinfo=timezone.utc) certificate = builder.build(ca2_private_key) dump_cert('client-untrusted', certificate) if not quiet: write('done') # Certificate that has a weak signature if not quiet: write('Generating weak client cert ... ', end='') builder = CertificateBuilder( { 'country_name': 'US', 'state_or_province_name': 'Massachusetts', 'locality_name': 'Newbury', 'organization_name': 'TLS Client Certificates Limited', 'common_name': 'Weak TLS Client Certificate', }, public_key) builder.issuer = ca_cert builder.crl_url = crl_url # Hack since API doesn't allow selection of weak algo builder._hash_algo = 'md5' builder.begin_date = datetime(base_year, 1, 1, 0, 0, 0, tzinfo=timezone.utc) builder.end_date = datetime(base_year + 3, 1, 1, 0, 0, 0, tzinfo=timezone.utc) certificate = builder.build(ca_private_key) dump_cert('client-weak', certificate) if not quiet: write('done') # Certificate that has bad key usage if not quiet: write('Generating bad key usage client cert ... ', end='') builder = CertificateBuilder( { 'country_name': 'US', 'state_or_province_name': 'Massachusetts', 'locality_name': 'Newbury', 'organization_name': 'TLS Client Certificates Limited', 'common_name': 'Bad Key Usage TLS Client Certificate', }, public_key) builder.issuer = ca_cert builder.crl_url = crl_url builder.begin_date = datetime(base_year, 1, 1, 0, 0, 0, tzinfo=timezone.utc) builder.end_date = datetime(base_year + 3, 1, 1, 0, 0, 0, tzinfo=timezone.utc) builder.key_usage = set(['crl_sign']) builder.extended_key_usage = set(['email_protection']) certificate = builder.build(ca_private_key) dump_cert('client-bad-key-usage', certificate) if not quiet: write('done') # Certificate that has been revoked if not quiet: write('Generating revoked client cert ... ', end='') builder = CertificateBuilder( { 'country_name': 'US', 'state_or_province_name': 'Massachusetts', 'locality_name': 'Newbury', 'organization_name': 'TLS Client Certificates Limited', 'common_name': 'Revoked TLS Client Certificate', }, public_key) builder.issuer = ca_cert builder.crl_url = crl_url builder.begin_date = datetime(base_year, 1, 1, 0, 0, 0, tzinfo=timezone.utc) builder.end_date = datetime(base_year + 3, 1, 1, 0, 0, 0, tzinfo=timezone.utc) revoked_certificate = builder.build(ca_private_key) dump_cert('client-revoked', revoked_certificate) if not quiet: write('done') crl_number = 1000 crl_builder = CertificateListBuilder(crl_url, ca_cert, crl_number) crl_builder.add_certificate( revoked_certificate.serial_number, datetime(base_year, 1, 2, 0, 0, 0, tzinfo=timezone.utc), 'key_compromise') certificate_list = crl_builder.build(ca_private_key) dump_crl('client', certificate_list)