예제 #1
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
예제 #2
0
파일: token.py 프로젝트: stephica/certidude
    def on_put(self, req, resp):
        # Consume token
        now = time()
        timestamp = req.get_param_as_int("t", required=True)
        username = req.get_param("u", required=True)
        user = User.objects.get(username)
        csum = hashlib.sha256()
        csum.update(config.TOKEN_SECRET)
        csum.update(username.encode("ascii"))
        csum.update(str(timestamp).encode("ascii"))

        margin = 300 # Tolerate 5 minute clock skew as Kerberos does
        if csum.hexdigest() != req.get_param("c", required=True):
            raise falcon.HTTPForbidden("Forbidden", "Invalid token supplied, did you copy-paste link correctly?")
        if now < timestamp - margin:
            raise falcon.HTTPForbidden("Forbidden", "Token not valid yet, are you sure server clock is correct?")
        if now > timestamp + margin + config.TOKEN_LIFETIME:
            raise falcon.HTTPForbidden("Forbidden", "Token expired")

        # At this point consider token to be legitimate
        body = req.stream.read(req.content_length)
        header, _, der_bytes = pem.unarmor(body)
        csr = CertificationRequest.load(der_bytes)
        common_name = csr["certification_request_info"]["subject"].native["common_name"]
        assert common_name == username or common_name.startswith(username + "@"), "Invalid common name %s" % common_name
        try:
            _, resp.body = self.authority._sign(csr, body, profile="default",
                overwrite=config.TOKEN_OVERWRITE_PERMITTED)
            resp.set_header("Content-Type", "application/x-pem-file")
            logger.info("Autosigned %s as proven by token ownership", common_name)
        except FileExistsError:
            logger.info("Won't autosign duplicate %s", common_name)
            raise falcon.HTTPConflict(
                "Certificate with such common name (CN) already exists",
                "Will not overwrite existing certificate signing request, explicitly delete existing one and try again")
예제 #3
0
def get_request(common_name):
    if not re.match(const.RE_COMMON_NAME, common_name):
        raise ValueError("Invalid common name %s" % repr(common_name))
    path = os.path.join(config.REQUESTS_DIR, common_name + ".pem")
    try:
        with open(path, "rb") as fh:
            buf = fh.read()
            header, _, der_bytes = pem.unarmor(buf)
            return path, buf, CertificationRequest.load(der_bytes), \
                datetime.utcfromtimestamp(os.stat(path).st_ctime)
    except EnvironmentError:
        raise errors.RequestDoesNotExist("Certificate signing request file %s does not exist" % path)
예제 #4
0
def sign(common_name, overwrite=False):
    """
    Sign certificate signing request by it's common name
    """

    req_path = os.path.join(config.REQUESTS_DIR, common_name + ".pem")
    with open(req_path) as fh:
        csr_buf = fh.read()
        header, _, der_bytes = pem.unarmor(csr_buf)
        csr = CertificationRequest.load(der_bytes)

    # Sign with function below
    cert, buf = _sign(csr, csr_buf, overwrite)

    os.unlink(req_path)
    return cert, buf
예제 #5
0
 def on_put(self, req, resp):
     try:
         username, mail, created, expires, profile = self.manager.consume(req.get_param("token", required=True))
     except RelationalMixin.DoesNotExist:
         raise falcon.HTTPForbidden("Forbidden", "No such token or token expired")
     body = req.stream.read(req.content_length)
     header, _, der_bytes = pem.unarmor(body)
     csr = CertificationRequest.load(der_bytes)
     common_name = csr["certification_request_info"]["subject"].native["common_name"]
     if not common_name.startswith(username + "@"):
         raise falcon.HTTPBadRequest("Bad requst", "Invalid common name %s" % common_name)
     try:
         _, resp.body = self.authority._sign(csr, body, profile=config.PROFILES.get(profile),
             overwrite=config.TOKEN_OVERWRITE_PERMITTED)
         resp.set_header("Content-Type", "application/x-pem-file")
         logger.info("Autosigned %s as proven by token ownership", common_name)
     except FileExistsError:
         logger.info("Won't autosign duplicate %s", common_name)
         raise falcon.HTTPConflict(
             "Certificate with such common name (CN) already exists",
             "Will not overwrite existing certificate signing request, explicitly delete existing one and try again")
예제 #6
0
def sign(common_name,
         skip_notify=False,
         skip_push=False,
         overwrite=False,
         profile="default",
         signer=None):
    """
    Sign certificate signing request by it's common name
    """

    req_path = os.path.join(config.REQUESTS_DIR, common_name + ".pem")
    with open(req_path, "rb") as fh:
        csr_buf = fh.read()
        header, _, der_bytes = pem.unarmor(csr_buf)
        csr = CertificationRequest.load(der_bytes)

    # Sign with function below
    cert, buf = _sign(csr, csr_buf, skip_notify, skip_push, overwrite, profile,
                      signer)

    os.unlink(req_path)
    return cert, buf
예제 #7
0
파일: request.py 프로젝트: norsig/certidude
    def on_post(self, req, resp):
        """
        Validate and parse certificate signing request, the RESTful way
        """
        reasons = []
        body = req.stream.read(req.content_length)

        try:
            header, _, der_bytes = pem.unarmor(body)
            csr = CertificationRequest.load(der_bytes)
        except ValueError:
            logger.info(
                "Malformed certificate signing request submission from %s blocked",
                req.context.get("remote_addr"))
            raise falcon.HTTPBadRequest(
                "Bad request", "Malformed certificate signing request")
        else:
            req_public_key = asymmetric.load_public_key(
                csr["certification_request_info"]["subject_pk_info"])
            if self.authority.public_key.algorithm != req_public_key.algorithm:
                logger.info(
                    "Attempt to submit %s based request from %s blocked, only %s allowed"
                    % (req_public_key.algorithm.upper(),
                       req.context.get("remote_addr"),
                       self.authority.public_key.algorithm.upper()))
                raise falcon.HTTPBadRequest(
                    "Bad request", "Incompatible asymmetric key algorithms")

        common_name = csr["certification_request_info"]["subject"].native[
            "common_name"]
        """
        Determine whether autosign is allowed to overwrite already issued
        certificates automatically
        """

        overwrite_allowed = False
        for subnet in config.OVERWRITE_SUBNETS:
            if req.context.get("remote_addr") in subnet:
                overwrite_allowed = True
                break
        """
        Handle domain computer automatic enrollment
        """
        machine = req.context.get("machine")
        if machine:
            reasons.append("machine enrollment not allowed from %s" %
                           req.context.get("remote_addr"))
            for subnet in config.MACHINE_ENROLLMENT_SUBNETS:
                if req.context.get("remote_addr") in subnet:
                    if common_name != machine:
                        raise falcon.HTTPBadRequest(
                            "Bad request",
                            "Common name %s differs from Kerberos credential %s!"
                            % (common_name, machine))

                    # Automatic enroll with Kerberos machine cerdentials
                    resp.set_header("Content-Type", "application/x-pem-file")
                    cert, resp.body = self.authority._sign(
                        csr,
                        body,
                        profile=config.PROFILES["rw"],
                        overwrite=overwrite_allowed)
                    logger.info(
                        "Automatically enrolled Kerberos authenticated machine %s from %s",
                        machine, req.context.get("remote_addr"))
                    return
        """
        Attempt to renew certificate using currently valid key pair
        """
        try:
            path, buf, cert, signed, expires = self.authority.get_signed(
                common_name)
        except EnvironmentError:
            pass  # No currently valid certificate for this common name
        else:
            cert_pk = cert["tbs_certificate"]["subject_public_key_info"].native
            csr_pk = csr["certification_request_info"][
                "subject_pk_info"].native

            # Same public key
            if cert_pk == csr_pk:
                buf = req.get_header("X-SSL-CERT")
                if buf:
                    # Used mutually authenticated TLS handshake, assume renewal
                    header, _, der_bytes = pem.unarmor(
                        buf.replace("\t", "\n").replace("\n\n",
                                                        "\n").encode("ascii"))
                    handshake_cert = x509.Certificate.load(der_bytes)
                    if handshake_cert.native == cert.native:
                        for subnet in config.RENEWAL_SUBNETS:
                            if req.context.get("remote_addr") in subnet:
                                resp.set_header(
                                    "Content-Type",
                                    "application/x-x509-user-cert")
                                setxattr(path, "user.revocation.reason",
                                         "superseded")
                                _, resp.body = self.authority._sign(
                                    csr,
                                    body,
                                    overwrite=True,
                                    profile=SignatureProfile.from_cert(cert))
                                logger.info(
                                    "Renewing certificate for %s as %s is whitelisted",
                                    common_name,
                                    req.context.get("remote_addr"))
                                return
                    reasons.append("renewal failed")
                else:
                    # No renewal requested, redirect to signed API call
                    resp.status = falcon.HTTP_SEE_OTHER
                    resp.location = os.path.join(
                        os.path.dirname(req.relative_uri), "signed",
                        common_name)
                    return
        """
        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"):
            for subnet in config.AUTOSIGN_SUBNETS:
                if req.context.get("remote_addr") in subnet:
                    try:
                        resp.set_header("Content-Type",
                                        "application/x-pem-file")
                        _, resp.body = self.authority._sign(
                            csr,
                            body,
                            overwrite=overwrite_allowed,
                            profile=config.PROFILES["rw"])
                        logger.info(
                            "Signed %s as %s is whitelisted for autosign",
                            common_name, req.context.get("remote_addr"))
                        return
                    except EnvironmentError:
                        logger.info(
                            "Autosign for %s from %s failed, signed certificate already exists",
                            common_name, req.context.get("remote_addr"))
                        reasons.append(
                            "autosign failed, signed certificate already exists"
                        )
                    break
            else:
                reasons.append("IP address not whitelisted for autosign")
        else:
            reasons.append("autosign not requested")

        # Attempt to save the request otherwise
        try:
            request_path, _, _ = self.authority.store_request(
                body, address=str(req.context.get("remote_addr")))
        except errors.RequestExists:
            reasons.append("same request already uploaded exists")
            # We should still redirect client to long poll URL below
        except errors.DuplicateCommonNameError:
            # TODO: Certificate renewal
            logger.warning(
                "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", common_name)

        # Wait the certificate to be signed if waiting is requested
        logger.info("Signing request %s from %s put on hold,  %s", common_name,
                    req.context.get("remote_addr"), ", ".join(reasons))

        if req.get_param("wait"):
            # Redirect to nginx pub/sub
            url = config.LONG_POLL_SUBSCRIBE % hashlib.sha256(body).hexdigest()
            click.echo("Redirecting to: %s" % url)
            resp.status = falcon.HTTP_SEE_OTHER
            resp.set_header("Location", url)
        else:
            # Request was accepted, but not processed
            resp.status = falcon.HTTP_202
            resp.body = ". ".join(reasons)
            if req.client_accepts("application/json"):
                resp.body = json.dumps(
                    {
                        "title": "Accepted",
                        "description": resp.body
                    },
                    cls=MyEncoder)
예제 #8
0
    def on_post(self, req, resp):
        """
        Validate and parse certificate signing request, the RESTful way
        """
        reasons = []
        body = req.stream.read(req.content_length).encode("ascii")

        header, _, der_bytes = pem.unarmor(body)
        csr = CertificationRequest.load(der_bytes)
        common_name = csr["certification_request_info"]["subject"].native["common_name"]

        """
        Handle domain computer automatic enrollment
        """
        machine = req.context.get("machine")
        if machine:
            if config.MACHINE_ENROLLMENT_ALLOWED:
                if common_name != machine:
                    raise falcon.HTTPBadRequest(
                        "Bad request",
                        "Common name %s differs from Kerberos credential %s!" % (common_name, machine))

                # Automatic enroll with Kerberos machine cerdentials
                resp.set_header("Content-Type", "application/x-pem-file")
                cert, resp.body = authority._sign(csr, body, overwrite=True)
                logger.info(u"Automatically enrolled Kerberos authenticated machine %s from %s",
                    machine, req.context.get("remote_addr"))
                return
            else:
                reasons.append("Machine enrollment not allowed")

        """
        Attempt to renew certificate using currently valid key pair
        """
        try:
            path, buf, cert = authority.get_signed(common_name)
        except EnvironmentError:
            pass # No currently valid certificate for this common name
        else:
            cert_pk = cert["tbs_certificate"]["subject_public_key_info"].native
            csr_pk = csr["certification_request_info"]["subject_pk_info"].native

            if cert_pk == csr_pk: # Same public key, assume renewal
                expires = cert["tbs_certificate"]["validity"]["not_after"].native.replace(tzinfo=None)
                renewal_header = req.get_header("X-Renewal-Signature")

                if not renewal_header:
                    # No header supplied, redirect to signed API call
                    resp.status = falcon.HTTP_SEE_OTHER
                    resp.location = os.path.join(os.path.dirname(req.relative_uri), "signed", common_name)
                    return

                try:
                    renewal_signature = b64decode(renewal_header)
                except TypeError, ValueError:
                    logger.error(u"Renewal failed, bad signature supplied for %s", common_name)
                    reasons.append("Renewal failed, bad signature supplied")
                else:
                    try:
                        asymmetric.rsa_pss_verify(
                            asymmetric.load_certificate(cert),
                            renewal_signature, buf + body, "sha512")
                    except SignatureError:
                        logger.error(u"Renewal failed, invalid signature supplied for %s", common_name)
                        reasons.append("Renewal failed, invalid signature supplied")
                    else:
                        # At this point renewal signature was valid but we need to perform some extra checks
                        if datetime.utcnow() > expires:
                            logger.error(u"Renewal failed, current certificate for %s has expired", common_name)
                            reasons.append("Renewal failed, current certificate expired")
                        elif not config.CERTIFICATE_RENEWAL_ALLOWED:
                            logger.error(u"Renewal requested for %s, but not allowed by authority settings", common_name)
                            reasons.append("Renewal requested, but not allowed by authority settings")
                        else:
                            resp.set_header("Content-Type", "application/x-x509-user-cert")
                            _, resp.body = authority._sign(csr, body, overwrite=True)
                            logger.info(u"Renewed certificate for %s", common_name)
                            return
예제 #9
0
    def on_post(self, req, resp):
        """
        Validate and parse certificate signing request, the RESTful way
        """
        reasons = []
        body = req.stream.read(req.content_length)

        try:
            header, _, der_bytes = pem.unarmor(body)
            csr = CertificationRequest.load(der_bytes)
        except ValueError:
            raise falcon.HTTPBadRequest(
                "Bad request", "Malformed certificate signing request")

        common_name = csr["certification_request_info"]["subject"].native[
            "common_name"]
        """
        Handle domain computer automatic enrollment
        """
        machine = req.context.get("machine")
        if machine:
            if config.MACHINE_ENROLLMENT_ALLOWED:
                if common_name != machine:
                    raise falcon.HTTPBadRequest(
                        "Bad request",
                        "Common name %s differs from Kerberos credential %s!" %
                        (common_name, machine))

                # Automatic enroll with Kerberos machine cerdentials
                resp.set_header("Content-Type", "application/x-pem-file")
                cert, resp.body = self.authority._sign(csr,
                                                       body,
                                                       overwrite=True)
                logger.info(
                    "Automatically enrolled Kerberos authenticated machine %s from %s",
                    machine, req.context.get("remote_addr"))
                return
            else:
                reasons.append("Machine enrollment not allowed")
        """
        Attempt to renew certificate using currently valid key pair
        """
        try:
            path, buf, cert, signed, expires = self.authority.get_signed(
                common_name)
        except EnvironmentError:
            pass  # No currently valid certificate for this common name
        else:
            cert_pk = cert["tbs_certificate"]["subject_public_key_info"].native
            csr_pk = csr["certification_request_info"][
                "subject_pk_info"].native

            if cert_pk == csr_pk:  # Same public key, assume renewal
                expires = cert["tbs_certificate"]["validity"][
                    "not_after"].native.replace(tzinfo=None)
                renewal_header = req.get_header("X-Renewal-Signature")

                if not renewal_header:
                    # No header supplied, redirect to signed API call
                    resp.status = falcon.HTTP_SEE_OTHER
                    resp.location = os.path.join(
                        os.path.dirname(req.relative_uri), "signed",
                        common_name)
                    return

                try:
                    renewal_signature = b64decode(renewal_header)
                except (TypeError, ValueError):
                    logger.error(
                        "Renewal failed, bad signature supplied for %s",
                        common_name)
                    reasons.append("Renewal failed, bad signature supplied")
                else:
                    try:
                        asymmetric.rsa_pss_verify(
                            asymmetric.load_certificate(cert),
                            renewal_signature, buf + body, "sha512")
                    except SignatureError:
                        logger.error(
                            "Renewal failed, invalid signature supplied for %s",
                            common_name)
                        reasons.append(
                            "Renewal failed, invalid signature supplied")
                    else:
                        # At this point renewal signature was valid but we need to perform some extra checks
                        if datetime.utcnow() > expires:
                            logger.error(
                                "Renewal failed, current certificate for %s has expired",
                                common_name)
                            reasons.append(
                                "Renewal failed, current certificate expired")
                        elif not config.CERTIFICATE_RENEWAL_ALLOWED:
                            logger.error(
                                "Renewal requested for %s, but not allowed by authority settings",
                                common_name)
                            reasons.append(
                                "Renewal requested, but not allowed by authority settings"
                            )
                        else:
                            resp.set_header("Content-Type",
                                            "application/x-x509-user-cert")
                            _, resp.body = self.authority._sign(csr,
                                                                body,
                                                                overwrite=True)
                            logger.info("Renewed certificate for %s",
                                        common_name)
                            return
        """
        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"):
            if not self.authority.server_flags(common_name):
                for subnet in config.AUTOSIGN_SUBNETS:
                    if req.context.get("remote_addr") in subnet:
                        try:
                            resp.set_header("Content-Type",
                                            "application/x-pem-file")
                            _, resp.body = self.authority._sign(csr, body)
                            logger.info("Autosigned %s as %s is whitelisted",
                                        common_name,
                                        req.context.get("remote_addr"))
                            return
                        except EnvironmentError:
                            logger.info(
                                "Autosign for %s from %s failed, signed certificate already exists",
                                common_name, req.context.get("remote_addr"))
                            reasons.append(
                                "Autosign failed, signed certificate already exists"
                            )
                        break
                else:
                    reasons.append(
                        "Autosign failed, IP address not whitelisted")
            else:
                reasons.append(
                    "Autosign failed, only client certificates allowed to be signed automatically"
                )

        # Attempt to save the request otherwise
        try:
            request_path, _, _ = self.authority.store_request(
                body, address=str(req.context.get("remote_addr")))
        except errors.RequestExists:
            reasons.append("Same request already uploaded exists")
            # We should still redirect client to long poll URL below
        except errors.DuplicateCommonNameError:
            # TODO: Certificate renewal
            logger.warning(
                "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", common_name)

        # Wait the certificate to be signed if waiting is requested
        logger.info("Stored signing request %s from %s", common_name,
                    req.context.get("remote_addr"))
        if req.get_param("wait"):
            # Redirect to nginx pub/sub
            url = config.LONG_POLL_SUBSCRIBE % hashlib.sha256(body).hexdigest()
            click.echo("Redirecting to: %s" % url)
            resp.status = falcon.HTTP_SEE_OTHER
            resp.set_header("Location", url)
            logger.debug("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
            resp.body = ". ".join(reasons)
            if req.client_accepts("application/json"):
                resp.body = json.dumps(
                    {
                        "title": "Accepted",
                        "description": resp.body
                    },
                    cls=MyEncoder)