Example #1
0
def delete_request(common_name):
    # Validate CN
    if not re.match(RE_HOSTNAME, common_name):
        raise ValueError("Invalid common name")

    path = os.path.join(config.REQUESTS_DIR, common_name + ".pem")
    request = Request(open(path))
    os.unlink(path)

    # Publish event at CA channel
    push.publish("request-deleted", request.common_name)

    # Write empty certificate to long-polling URL
    requests.delete(config.PUSH_PUBLISH % request.fingerprint(),
        headers={"User-Agent": "Certidude API"})
Example #2
0
    def on_post(self, req, resp):
        """
        Submit certificate signing request (CSR) in PEM format
        """

        body = req.stream.read(req.content_length)

        # Normalize body, TODO: newlines
        if not body.endswith("\n"):
            body += "\n"

        csr = Request(body)

        if not csr.common_name:
            logger.warning(u"Rejected signing request without common name from %s",
                req.context.get("remote_addr"))
            raise falcon.HTTPBadRequest(
                "Bad request",
                "No common name specified!")

        machine = req.context.get("machine")
        if machine:
            if csr.common_name != machine:
                raise falcon.HTTPBadRequest(
                    "Bad request",
                    "Common name %s differs from Kerberos credential %s!" % (csr.common_name, machine))
            if csr.signable:
                # Automatic enroll with Kerberos machine cerdentials
                resp.set_header("Content-Type", "application/x-x509-user-cert")
                resp.body = authority.sign(csr, overwrite=True).dump()
                return


        # Check if this request has been already signed and return corresponding certificte if it has been signed
        try:
            cert = authority.get_signed(csr.common_name)
        except EnvironmentError:
            pass
        else:
            if cert.pubkey == csr.pubkey:
                resp.status = falcon.HTTP_SEE_OTHER
                resp.location = os.path.join(os.path.dirname(req.relative_uri), "signed", csr.common_name)
                return

        # TODO: check for revoked certificates and return HTTP 410 Gone

        # Process automatic signing if the IP address is whitelisted, autosigning was requested and certificate can be automatically signed
        if req.get_param_as_bool("autosign") and csr.signable:
            for subnet in config.AUTOSIGN_SUBNETS:
                if req.context.get("remote_addr") in subnet:
                    try:
                        resp.set_header("Content-Type", "application/x-x509-user-cert")
                        resp.body = authority.sign(csr).dump()
                        return
                    except EnvironmentError: # Certificate already exists, try to save the request
                        pass
                    break

        # Attempt to save the request otherwise
        try:
            csr = authority.store_request(body)
        except errors.RequestExists:
            # We should stil redirect client to long poll URL below
            pass
        except errors.DuplicateCommonNameError:
            # TODO: Certificate renewal
            logger.warning(u"Rejected signing request with overlapping common name from %s",
                req.context.get("remote_addr"))
            raise falcon.HTTPConflict(
                "CSR with such CN already exists",
                "Will not overwrite existing certificate signing request, explicitly delete CSR and try again")
        else:
            push.publish("request-submitted", csr.common_name)

        # Wait the certificate to be signed if waiting is requested
        if req.get_param("wait"):
            # Redirect to nginx pub/sub
            url = config.PUSH_LONG_POLL % csr.fingerprint()
            click.echo("Redirecting to: %s"  % url)
            resp.status = falcon.HTTP_SEE_OTHER
            resp.set_header("Location", url.encode("ascii"))
            logger.debug(u"Redirecting signing request from %s to %s", req.context.get("remote_addr"), url)
        else:
            # Request was accepted, but not processed
            resp.status = falcon.HTTP_202
            logger.info(u"Signing request from %s stored", req.context.get("remote_addr"))
Example #3
0
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):
    """
    Exchange CSR for certificate using Certidude HTTP API server
    """

    # Set up URL-s
    request_params = set()
    if autosign:
        request_params.add("autosign=yes")
    if wait:
        request_params.add("wait=forever")

    if not url.endswith("/"):
        url = url + "/"

    authority_url = url + "certificate"
    request_url = url + "request"

    if request_params:
        request_url = request_url + "?" + "&".join(request_params)

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

    try:
        certificate = Certificate(open(certificate_path))
        click.echo("Found certificate: %s" % certificate_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)

            # 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_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 extensions
            extensions = []
            if key_usage:
                extensions.append(("keyUsage", key_usage, True))
            if extended_key_usage:
                extensions.append(("extendedKeyUsage", extended_key_usage, True))
            if email_address:
                extensions.append(("subjectAltName", "email:" + email_address, False))
            request.set_extensions(extensions)

            # 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)


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

            click.echo("Submitting to %s, waiting for response..." % request_url)
            try:
                response = urllib.request.urlopen(submission)
                buf = response.read()
                cert = crypto.load_certificate(crypto.FILETYPE_PEM, buf)
            except crypto.Error:
                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

            os.umask(0o022)
            with open(certificate_path + ".part", "wb") as gh:
                gh.write(buf)

            click.echo("Writing certificate to: %s" % certificate_path)
            os.rename(certificate_path + ".part", certificate_path)
Example #4
0
    def on_post(self, req, resp, ca):
        """
        Submit certificate signing request (CSR) in PEM format
        """

        if req.get_header("Content-Type") != "application/pkcs10":
            raise falcon.HTTPUnsupportedMediaType(
                "This API call accepts only application/pkcs10 content type")

        body = req.stream.read(req.content_length)
        csr = Request(body)

        # Check if this request has been already signed and return corresponding certificte if it has been signed
        try:
            cert_buf = ca.get_certificate(csr.common_name)
        except FileNotFoundError:
            pass
        else:
            cert = Certificate(cert_buf)
            if cert.pubkey == csr.pubkey:
                resp.status = falcon.HTTP_FOUND
                resp.location = os.path.join(os.path.dirname(req.relative_uri), "signed", csr.common_name)
                return

        # TODO: check for revoked certificates and return HTTP 410 Gone

        # Process automatic signing if the IP address is whitelisted and autosigning was requested
        if ca.autosign_allowed(req.env["REMOTE_ADDR"]) and req.get_param("autosign"):
            try:
                resp.append_header("Content-Type", "application/x-x509-user-cert")
                resp.body = ca.sign(csr).dump()
                return
            except FileExistsError: # Certificate already exists, try to save the request
                pass

        # Attempt to save the request otherwise
        try:
            request = ca.store_request(body)
        except FileExistsError:
            raise falcon.HTTPConflict(
                "CSR with such CN already exists",
                "Will not overwrite existing certificate signing request, explicitly delete CSR and try again")

        # Wait the certificate to be signed if waiting is requested
        if req.get_param("wait"):
            url_template = os.getenv("CERTIDUDE_EVENT_SUBSCRIBE")
            if url_template:
                # Redirect to nginx pub/sub
                url = url_template % dict(channel=request.fingerprint())
                click.echo("Redirecting to: %s"  % url)
                resp.status = falcon.HTTP_FOUND
                resp.append_header("Location", url)
            else:
                click.echo("Using dummy streaming mode, please switch to nginx in production!", err=True)
                # Dummy streaming mode
                while True:
                    sleep(1)
                    if not ca.request_exists(csr.common_name):
                        resp.append_header("Content-Type", "application/x-x509-user-cert")
                        resp.status = falcon.HTTP_201 # Certificate was created
                        resp.body = ca.get_certificate(csr.common_name)
                        break
        else:
            # Request was accepted, but not processed
            resp.status = falcon.HTTP_202
Example #5
0
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):
    """
    Exchange CSR for certificate using Certidude HTTP API server
    """

    # Set up URL-s
    request_params = set()
    if autosign:
        request_params.add("autosign=yes")
    if wait:
        request_params.add("wait=forever")

    if not url.endswith("/"):
        url = url + "/"

    authority_url = url + "certificate"
    request_url = url + "request"

    if request_params:
        request_url = request_url + "?" + "&".join(request_params)

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

    try:
        certificate = Certificate(open(certificate_path))
        click.echo("Found certificate: %s" % certificate_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)

            # 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_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 extensions
            extensions = []
            if key_usage:
                extensions.append(("keyUsage", key_usage, True))
            if extended_key_usage:
                extensions.append(
                    ("extendedKeyUsage", extended_key_usage, True))
            if email_address:
                extensions.append(
                    ("subjectAltName", "email:" + email_address, False))
            request.set_extensions(extensions)

            # 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)

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

            click.echo("Submitting to %s, waiting for response..." %
                       request_url)
            try:
                response = urllib.request.urlopen(submission)
                buf = response.read()
                cert = crypto.load_certificate(crypto.FILETYPE_PEM, buf)
            except crypto.Error:
                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

            os.umask(0o022)
            with open(certificate_path + ".part", "wb") as gh:
                gh.write(buf)

            click.echo("Writing certificate to: %s" % certificate_path)
            os.rename(certificate_path + ".part", certificate_path)