Beispiel #1
0
def revoke(common_name, reason, user="******"):
    """
    Revoke valid certificate
    """
    signed_path, buf, cert, signed, expires = get_signed(common_name)

    if reason not in ("key_compromise", "ca_compromise", "affiliation_changed",
                      "superseded", "cessation_of_operation",
                      "certificate_hold", "remove_from_crl",
                      "privilege_withdrawn"):
        raise ValueError("Invalid revocation reason %s" % reason)

    setxattr(signed_path, "user.revocation.reason", reason)
    revoked_path = os.path.join(config.REVOKED_DIR,
                                "%040x.pem" % cert.serial_number)

    logger.info("Revoked certificate %s by %s", common_name, user)

    os.unlink(
        os.path.join(config.SIGNED_BY_SERIAL_DIR,
                     "%040x.pem" % cert.serial_number))
    os.rename(signed_path, revoked_path)

    push.publish("certificate-revoked", common_name)

    attach_cert = buf, "application/x-pem-file", common_name + ".crt"
    mailer.send("certificate-revoked.md",
                attachments=(attach_cert, ),
                serial_hex="%x" % cert.serial_number,
                common_name=common_name)
    return revoked_path
Beispiel #2
0
    def issue(self, issuer, subject, subject_mail=None):
        # Expand variables
        subject_username = subject.name
        if not subject_mail:
            subject_mail = subject.mail

        # Generate token
        token = ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for _ in range(32))
        token_created = datetime.utcnow()
        token_expires = token_created + config.TOKEN_LIFETIME

        self.sql_execute("token_issue.sql",
            token_created, token_expires, token,
            issuer.name if issuer else None,
            subject_username, subject_mail, "rw")

        # Token lifetime in local time, to select timezone: dpkg-reconfigure tzdata
        try:
            with open("/etc/timezone") as fh:
                token_timezone = fh.read().strip()
        except EnvironmentError:
            token_timezone = None

        router = sorted([j[0] for j in authority.list_signed(
                    common_name=config.SERVICE_ROUTERS)])[0]
        protocols = ",".join(config.SERVICE_PROTOCOLS)
        url = config.TOKEN_URL % locals()

        context = globals()
        context.update(locals())

        mailer.send("token.md", to=subject_mail, **context)
        return token
Beispiel #3
0
def revoke(common_name):
    """
    Revoke valid certificate
    """
    signed_path, buf, cert = get_signed(common_name)
    revoked_path = os.path.join(config.REVOKED_DIR,
                                "%x.pem" % cert.serial_number)
    os.rename(signed_path, revoked_path)
    os.unlink(
        os.path.join(config.SIGNED_BY_SERIAL_DIR,
                     "%x.pem" % cert.serial_number))

    push.publish("certificate-revoked", common_name)

    # Publish CRL for long polls
    url = config.LONG_POLL_PUBLISH % "crl"
    click.echo("Publishing CRL at %s ..." % url)
    requests.post(url,
                  data=export_crl(),
                  headers={
                      "User-Agent": "Certidude API",
                      "Content-Type": "application/x-pem-file"
                  })

    attach_cert = buf, "application/x-pem-file", common_name + ".crt"
    mailer.send("certificate-revoked.md",
                attachments=(attach_cert, ),
                serial_hex="%x" % cert.serial_number,
                common_name=common_name)
    return revoked_path
Beispiel #4
0
def store_request(buf, overwrite=False):
    """
    Store CSR for later processing
    """

    if not buf: return # No certificate supplied
    csr = x509.load_pem_x509_csr(buf, backend=default_backend())
    for name in csr.subject:
        if name.oid == NameOID.COMMON_NAME:
            common_name = name.value
            break
    else:
        raise ValueError("No common name in %s" % csr.subject)

    request_path = os.path.join(config.REQUESTS_DIR, common_name + ".pem")

    if not re.match(RE_HOSTNAME, common_name):
        raise ValueError("Invalid common name")

    # If there is cert, check if it's the same
    if os.path.exists(request_path):
        if open(request_path).read() == buf:
            raise errors.RequestExists("Request already exists")
        else:
            raise errors.DuplicateCommonNameError("Another request with same common name already exists")
    else:
        with open(request_path + ".part", "w") as fh:
            fh.write(buf)
        os.rename(request_path + ".part", request_path)

    req = Request(open(request_path))
    mailer.send("request-stored.md", attachments=(req,), request=req)
    return req
Beispiel #5
0
    def wrapped(csr, *args, **kwargs):
        cert = func(csr, *args, **kwargs)
        assert isinstance(cert, Certificate), "notify wrapped function %s returned %s" % (func, type(cert))

        if cert.given_name and cert.surname and cert.email_address:
            recipient = "%s %s <%s>" % (cert.given_name, cert.surname, cert.email_address)
        elif cert.email_address:
            recipient = cert.email_address
        else:
            recipient = None

        mailer.send(
            "certificate-signed.md",
            to=recipient,
            attachments=(cert,),
            certificate=cert)

        if config.PUSH_PUBLISH:
            url = config.PUSH_PUBLISH % csr.fingerprint()
            click.echo("Publishing certificate at %s ..." % url)
            requests.post(url, data=cert.dump(),
                headers={"User-Agent": "Certidude API", "Content-Type": "application/x-x509-user-cert"})

            # For deleting request in the web view, use pubkey modulo
            push.publish("request-signed", cert.common_name)
        return cert
Beispiel #6
0
    def on_post(self, req, resp):
        # Generate token
        issuer = req.context.get("user")
        username = req.get_param("user", required=True)
        user = User.objects.get(username)
        timestamp = int(time())
        csum = hashlib.sha256()
        csum.update(config.TOKEN_SECRET)
        csum.update(username)
        csum.update(str(timestamp))
        args = "u=%s&t=%d&c=%s" % (username, timestamp, csum.hexdigest())

        # Token lifetime in local time, to select timezone: dpkg-reconfigure tzdata
        token_created = datetime.fromtimestamp(timestamp)
        token_expires = datetime.fromtimestamp(timestamp +
                                               config.TOKEN_LIFETIME)
        try:
            with open("/etc/timezone") as fh:
                token_timezone = fh.read().strip()
        except EnvironmentError:
            token_timezone = None
        context = globals()
        context.update(locals())
        mailer.send("token.md", to=user, **context)
        resp.body = args
Beispiel #7
0
def revoke_certificate(common_name):
    """
    Revoke valid certificate
    """
    cert = get_signed(common_name)
    revoked_filename = os.path.join(config.REVOKED_DIR, "%s.pem" % cert.serial_number)
    os.rename(cert.path, revoked_filename)
    push.publish("certificate-revoked", cert.common_name)
    mailer.send("certificate-revoked.md", attachments=(cert,), certificate=cert)
Beispiel #8
0
def store_request(buf, overwrite=False, address="", user=""):
    """
    Store CSR for later processing
    """

    if not buf:
        raise ValueError("No signing request supplied")

    if pem.detect(buf):
        header, _, der_bytes = pem.unarmor(buf)
        csr = CertificationRequest.load(der_bytes)
    else:
        csr = CertificationRequest.load(buf)
        buf = pem_armor_csr(csr)

    common_name = csr["certification_request_info"]["subject"].native[
        "common_name"]

    if not re.match(const.RE_COMMON_NAME, common_name):
        raise ValueError("Invalid common name %s" % repr(common_name))

    request_path = os.path.join(config.REQUESTS_DIR, common_name + ".pem")

    # If there is cert, check if it's the same
    if os.path.exists(request_path) and not overwrite:
        if open(request_path, "rb").read() == buf:
            raise errors.RequestExists("Request already exists")
        else:
            raise errors.DuplicateCommonNameError(
                "Another request with same common name already exists")
    else:
        with open(request_path + ".part", "wb") as fh:
            fh.write(buf)
        os.rename(request_path + ".part", request_path)

    attach_csr = buf, "application/x-pem-file", common_name + ".csr"
    mailer.send("request-stored.md",
                attachments=(attach_csr, ),
                common_name=common_name)
    setxattr(request_path, "user.request.address", address)
    setxattr(request_path, "user.request.user", user)
    try:
        hostname, aliaslist, ipaddrlist = socket.gethostbyaddr(address)
    except (socket.herror,
            OSError):  # Failed to resolve hostname or resolved to multiple
        pass
    else:
        setxattr(request_path, "user.request.hostname", hostname)
    return request_path, csr, common_name
Beispiel #9
0
    def on_post(self, req, resp):
        # Generate token
        issuer = req.context.get("user")
        username = req.get_param("username")
        secondary = req.get_param("mail")

        if username:
            # Otherwise try to look up user so we can derive their e-mail address
            user = User.objects.get(username)
        else:
            # If no username is specified, assume it's intended for someone outside domain
            username = "******" % hashlib.sha256(
                secondary.encode("ascii")).hexdigest()[-8:]
            if not secondary:
                raise

        timestamp = int(time())
        csum = hashlib.sha256()
        csum.update(config.TOKEN_SECRET)
        csum.update(username.encode("ascii"))
        csum.update(str(timestamp).encode("ascii"))
        args = "u=%s&t=%d&c=%s&i=%s" % (username, timestamp, csum.hexdigest(),
                                        issuer.name)

        # Token lifetime in local time, to select timezone: dpkg-reconfigure tzdata
        token_created = datetime.fromtimestamp(timestamp)
        token_expires = datetime.fromtimestamp(timestamp +
                                               config.TOKEN_LIFETIME)
        try:
            with open("/etc/timezone") as fh:
                token_timezone = fh.read().strip()
        except EnvironmentError:
            token_timezone = None
        url = "%s#%s" % (config.TOKEN_URL, args)
        context = globals()
        context.update(locals())
        mailer.send("token.md", to=user, **context)
        return {
            "token": args,
            "url": url,
        }
Beispiel #10
0
def revoke(common_name, reason):
    """
    Revoke valid certificate
    """
    signed_path, buf, cert, signed, expires = get_signed(common_name)

    if reason not in ("key_compromise", "ca_compromise", "affiliation_changed",
                      "superseded", "cessation_of_operation",
                      "certificate_hold", "remove_from_crl",
                      "privilege_withdrawn"):
        raise ValueError("Invalid revocation reason %s" % reason)

    setxattr(signed_path, "user.revocation.reason", reason)
    revoked_path = os.path.join(config.REVOKED_DIR,
                                "%040x.pem" % cert.serial_number)

    os.unlink(
        os.path.join(config.SIGNED_BY_SERIAL_DIR,
                     "%040x.pem" % cert.serial_number))
    os.rename(signed_path, revoked_path)

    push.publish("certificate-revoked", common_name)

    # Publish CRL for long polls
    url = config.LONG_POLL_PUBLISH % "crl"
    click.echo("Publishing CRL at %s ..." % url)
    requests.post(url,
                  data=export_crl(),
                  headers={
                      "User-Agent": "Certidude API",
                      "Content-Type": "application/x-pem-file"
                  })

    attach_cert = buf, "application/x-pem-file", common_name + ".crt"
    mailer.send("certificate-revoked.md",
                attachments=(attach_cert, ),
                serial_hex="%040x" % cert.serial_number,
                common_name=common_name)
    return revoked_path
Beispiel #11
0
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
Beispiel #12
0
def _sign(csr, buf, overwrite=False):
    # TODO: CRLDistributionPoints, OCSP URL, Certificate URL

    assert buf.startswith("-----BEGIN CERTIFICATE REQUEST-----\n")
    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) 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?
            prev_serial_hex = "%x" % prev.serial_number
            revoked_path = os.path.join(config.REVOKED_DIR,
                                        "%s.pem" % prev_serial_hex)
            os.rename(cert_path, revoked_path)
            attachments += [(prev_buf, "application/x-pem-file",
                             "deprecated.crt" if renew else "overwritten.crt")]
            overwritten = True
        else:
            raise EnvironmentError("Will not overwrite existing certificate")

    # Sign via signer process
    builder = CertificateBuilder({u'common_name': common_name}, csr_pubkey)
    builder.serial_number = random.randint(
        0x1000000000000000000000000000000000000000,
        0xffffffffffffffffffffffffffffffffffffffff)

    now = datetime.utcnow()
    builder.begin_date = now - timedelta(minutes=5)
    builder.end_date = now + timedelta(
        days=config.SERVER_CERTIFICATE_LIFETIME
        if server_flags(common_name) else config.CLIENT_CERTIFICATE_LIFETIME)
    builder.issuer = certificate
    builder.ca = False
    builder.key_usage = set([u"digital_signature", u"key_encipherment"])

    # OpenVPN uses CN while StrongSwan uses SAN
    if server_flags(common_name):
        builder.subject_alt_domains = [common_name]
        builder.extended_key_usage = set(
            [u"server_auth", u"1.3.6.1.5.5.8.2.2", u"client_auth"])
    else:
        builder.extended_key_usage = set([u"client_auth"])

    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,
                             "%x.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("user."):
                continue
            setxattr(cert_path, key, getxattr(revoked_path, key))

    # Send mail
    if renew:  # Same keypair
        mailer.send("certificate-renewed.md", **locals())
    else:  # New keypair
        mailer.send("certificate-signed.md", **locals())

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

    push.publish("request-signed", common_name)
    return end_entity_cert, end_entity_cert_buf
Beispiel #13
0
def _sign(csr,
          buf,
          skip_notify=False,
          skip_push=False,
          overwrite=False,
          profile="default",
          signer=None):
    # TODO: CRLDistributionPoints, OCSP URL, Certificate URL
    if profile not in config.PROFILES:
        raise ValueError("Invalid profile supplied '%s'" % profile)

    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?
            prev_serial_hex = "%x" % prev.serial_number
            revoked_path = os.path.join(config.REVOKED_DIR,
                                        "%s.pem" % prev_serial_hex)
            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")

    # Sign via signer process
    dn = {u'common_name': common_name}
    profile_server_flags, lifetime, dn[
        "organizational_unit_name"], _ = config.PROFILES[profile]
    lifetime = int(lifetime)

    builder = CertificateBuilder(dn, csr_pubkey)
    builder.serial_number = random.randint(
        0x1000000000000000000000000000000000000000,
        0x7fffffffffffffffffffffffffffffffffffffff)

    now = datetime.utcnow()
    builder.begin_date = now - timedelta(minutes=5)
    builder.end_date = now + timedelta(days=lifetime)
    builder.issuer = certificate
    builder.ca = False
    builder.key_usage = set(["digital_signature", "key_encipherment"])

    # If we have FQDN and profile suggests server flags, enable them
    if server_flags(common_name) and profile_server_flags:
        builder.subject_alt_domains = [
            common_name
        ]  # OpenVPN uses CN while StrongSwan uses SAN to match hostname of the server
        builder.extended_key_usage = set(
            ["server_auth", "1.3.6.1.5.5.8.2.2", "client_auth"])
    else:
        builder.subject_alt_domains = [common_name
                                       ]  # iOS demands SAN also for clients
        builder.extended_key_usage = set(["client_auth"])

    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,
                             "%x.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"
                      })

        push.publish("request-signed", common_name)
    return end_entity_cert, end_entity_cert_buf