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
Exemple #2
0
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)