예제 #1
0
파일: tenant.py 프로젝트: ashcrow/keylime
    def init_add(self, args):
        # command line options can overwrite config values
        if "agent_ip" in args:
            self.cloudagent_ip = args["agent_ip"]

        if 'agent_port' in args and args['agent_port'] is not None:
            self.cloudagent_port = args['agent_port']

        if 'cv_agent_ip' in args and args['cv_agent_ip'] is not None:
            self.cv_cloudagent_ip = args['cv_agent_ip']
        else:
            self.cv_cloudagent_ip = self.cloudagent_ip

        # Make sure all keys exist in dictionary
        if "file" not in args:
            args["file"] = None
        if "keyfile" not in args:
            args["keyfile"] = None
        if "payload" not in args:
            args["payload"] = None
        if "ca_dir" not in args:
            args["ca_dir"] = None
        if "incl_dir" not in args:
            args["incl_dir"] = None
        if "ca_dir_pw" not in args:
            args["ca_dir_pw"] = None

        # Set up accepted algorithms
        self.accept_tpm_hash_algs = config.get(
            'tenant', 'accept_tpm_hash_algs').split(',')
        self.accept_tpm_encryption_algs = config.get(
            'tenant', 'accept_tpm_encryption_algs').split(',')
        self.accept_tpm_signing_algs = config.get(
            'tenant', 'accept_tpm_signing_algs').split(',')

        # Set up PCR values
        tpm_policy = config.get('tenant', 'tpm_policy')
        if "tpm_policy" in args and args["tpm_policy"] is not None:
            tpm_policy = args["tpm_policy"]
        self.tpm_policy = TPM_Utilities.readPolicy(tpm_policy)
        logger.info(f"TPM PCR Mask from policy is {self.tpm_policy['mask']}")

        vtpm_policy = config.get('tenant', 'vtpm_policy')
        if "vtpm_policy" in args and args["vtpm_policy"] is not None:
            vtpm_policy = args["vtpm_policy"]
        self.vtpm_policy = TPM_Utilities.readPolicy(vtpm_policy)
        logger.info(f"TPM PCR Mask from policy is {self.vtpm_policy['mask']}")

        # Read command-line path string IMA whitelist
        wl_data = None
        if "ima_whitelist" in args and args["ima_whitelist"] is not None:

            # Auto-enable IMA (or-bit mask)
            self.tpm_policy['mask'] = "0x%X" % (
                int(self.tpm_policy['mask'], 0) + (1 << common.IMA_PCR))

            if type(args["ima_whitelist"]) in [str, str]:
                if args["ima_whitelist"] == "default":
                    args["ima_whitelist"] = config.get('tenant',
                                                       'ima_whitelist')
                wl_data = ima.read_whitelist(args["ima_whitelist"])
            elif type(args["ima_whitelist"]) is list:
                wl_data = args["ima_whitelist"]
            else:
                raise UserError("Invalid whitelist provided")

        # Read command-line path string IMA exclude list
        excl_data = None
        if "ima_exclude" in args and args["ima_exclude"] is not None:
            if type(args["ima_exclude"]) in [str, str]:
                if args["ima_exclude"] == "default":
                    args["ima_exclude"] = config.get('tenant',
                                                     'ima_excludelist')
                excl_data = ima.read_excllist(args["ima_exclude"])
            elif type(args["ima_exclude"]) is list:
                excl_data = args["ima_exclude"]
            else:
                raise UserError("Invalid exclude list provided")

        # Set up IMA
        if TPM_Utilities.check_mask(self.tpm_policy['mask'], common.IMA_PCR) or \
                TPM_Utilities.check_mask(self.vtpm_policy['mask'], common.IMA_PCR):

            # Process IMA whitelists
            self.ima_whitelist = ima.process_whitelists(wl_data, excl_data)

        # if none
        if (args["file"] is None and args["keyfile"] is None
                and args["ca_dir"] is None):
            raise UserError(
                "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
            )

        if args["keyfile"] is not None:
            if args["file"] is not None or args["ca_dir"] is not None:
                raise UserError(
                    "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
                )

            # read the keys in
            if type(args["keyfile"]) is dict and "data" in args["keyfile"]:
                if type(args["keyfile"]["data"]) is list and len(
                        args["keyfile"]["data"]) == 1:
                    keyfile = args["keyfile"]["data"][0]
                    if keyfile is None:
                        raise UserError("Invalid key file contents")
                    f = io.StringIO(keyfile)
                else:
                    raise UserError("Invalid key file provided")
            else:
                f = open(args["keyfile"], 'r')
            self.K = base64.b64decode(f.readline())
            self.U = base64.b64decode(f.readline())
            self.V = base64.b64decode(f.readline())
            f.close()

            # read the payload in (opt.)
            if type(args["payload"]) is dict and "data" in args["payload"]:
                if type(args["payload"]["data"]) is list and len(
                        args["payload"]["data"]) > 0:
                    self.payload = args["payload"]["data"][0]
            else:
                if args["payload"] is not None:
                    f = open(args["payload"], 'r')
                    self.payload = f.read()
                    f.close()

        if args["file"] is not None:
            if args["keyfile"] is not None or args["ca_dir"] is not None:
                raise UserError(
                    "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
                )

            if type(args["file"]) is dict and "data" in args["file"]:
                if type(args["file"]["data"]) is list and len(
                        args["file"]["data"]) > 0:
                    contents = args["file"]["data"][0]
                    if contents is None:
                        raise UserError("Invalid file payload contents")
                else:
                    raise UserError("Invalid file payload provided")
            else:
                with open(args["file"], 'r') as f:
                    contents = f.read()
            ret = user_data_encrypt.encrypt(contents)
            self.K = ret['k']
            self.U = ret['u']
            self.V = ret['v']
            self.payload = ret['ciphertext']

        if args["ca_dir"] is None and args["incl_dir"] is not None:
            raise UserError(
                "--include option is only valid when used with --cert")
        if args["ca_dir"] is not None:
            if args["file"] is not None or args["keyfile"] is not None:
                raise UserError(
                    "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
                )
            if args["ca_dir"] == 'default':
                args["ca_dir"] = common.CA_WORK_DIR

            if "ca_dir_pw" in args and args["ca_dir_pw"] is not None:
                ca_util.setpassword(args["ca_dir_pw"])

            if not os.path.exists(args["ca_dir"]) or not os.path.exists(
                    "%s/cacert.crt" % args["ca_dir"]):
                logger.warning(" CA directory does not exist.  Creating...")
                ca_util.cmd_init(args["ca_dir"])

            if not os.path.exists("%s/%s-private.pem" %
                                  (args["ca_dir"], self.agent_uuid)):
                ca_util.cmd_mkcert(args["ca_dir"], self.agent_uuid)

            cert_pkg, serial, subject = ca_util.cmd_certpkg(
                args["ca_dir"], self.agent_uuid)

            # support revocation
            if not os.path.exists(
                    "%s/RevocationNotifier-private.pem" % args["ca_dir"]):
                ca_util.cmd_mkcert(args["ca_dir"], "RevocationNotifier")
            rev_package, _, _ = ca_util.cmd_certpkg(args["ca_dir"],
                                                    "RevocationNotifier")

            # extract public and private keys from package
            sf = io.BytesIO(rev_package)
            with zipfile.ZipFile(sf) as zf:
                privkey = zf.read("RevocationNotifier-private.pem")
                cert = zf.read("RevocationNotifier-cert.crt")

            # put the cert of the revoker into the cert package
            sf = io.BytesIO(cert_pkg)
            with zipfile.ZipFile(sf, 'a',
                                 compression=zipfile.ZIP_STORED) as zf:
                zf.writestr('RevocationNotifier-cert.crt', cert)

                # add additional files to zip
                if args["incl_dir"] is not None:
                    if type(args["incl_dir"]) is dict and "data" in args[
                            "incl_dir"] and "name" in args["incl_dir"]:
                        if type(args["incl_dir"]["data"]) is list and type(
                                args["incl_dir"]["name"]) is list:
                            if len(args["incl_dir"]["data"]) != len(
                                    args["incl_dir"]["name"]):
                                raise UserError("Invalid incl_dir provided")
                            for i in range(len(args["incl_dir"]["data"])):
                                zf.writestr(
                                    os.path.basename(
                                        args["incl_dir"]["name"][i]),
                                    args["incl_dir"]["data"][i])
                    else:
                        if os.path.exists(args["incl_dir"]):
                            files = next(os.walk(args["incl_dir"]))[2]
                            for filename in files:
                                with open(
                                        "%s/%s" % (args["incl_dir"], filename),
                                        'rb') as f:
                                    zf.writestr(os.path.basename(f.name),
                                                f.read())
                        else:
                            logger.warn(
                                f'Specified include directory {args["incl_dir"]} does not exist.  Skipping...'
                            )

            cert_pkg = sf.getvalue()

            # put the private key into the data to be send to the CV
            self.revocation_key = privkey

            # encrypt up the cert package
            ret = user_data_encrypt.encrypt(cert_pkg)
            self.K = ret['k']
            self.U = ret['u']
            self.V = ret['v']
            self.metadata = {'cert_serial': serial, 'subject': subject}
            self.payload = ret['ciphertext']

        if self.payload is not None and len(self.payload) > config.getint(
                'tenant', 'max_payload_size'):
            raise UserError("Payload size %s exceeds max size %d" % (len(
                self.payload), config.getint('tenant', 'max_payload_size')))
예제 #2
0
    def do_GET(self):
        """This method services the GET request typically from either the Tenant or the Cloud Verifier.

        Only tenant and cloudverifier uri's are supported. Both requests require a nonce parameter.
        The Cloud verifier requires an additional mask paramter.  If the uri or parameters are incorrect, a 400 response is returned.
        """

        logger.info("GET invoked from %s with uri: %s", self.client_address,
                    self.path)
        rest_params = web_util.get_restful_params(self.path)
        if rest_params is None:
            web_util.echo_json_response(
                self, 405,
                "Not Implemented: Use /version, /keys/ or /quotes/ interfaces")
            return

        if "version" in rest_params:
            version_info = {
                "supported_version": keylime_api_version.current_version()
            }
            web_util.echo_json_response(self, 200, "Success", version_info)
            return

        if not rest_params["api_version"]:
            web_util.echo_json_response(self, 400, "API Version not supported")
            return

        if "keys" in rest_params and rest_params["keys"] == "verify":
            if self.server.K is None:
                logger.info(
                    "GET key challenge returning 400 response. bootstrap key not available"
                )
                web_util.echo_json_response(
                    self, 400, "Bootstrap key not yet available.")
                return
            if "challenge" not in rest_params:
                logger.info(
                    "GET key challenge returning 400 response. No challenge provided"
                )
                web_util.echo_json_response(self, 400,
                                            "No challenge provided.")
                return

            challenge = rest_params["challenge"]
            response = {}
            response["hmac"] = crypto.do_hmac(self.server.K, challenge)
            web_util.echo_json_response(self, 200, "Success", response)
            logger.info("GET key challenge returning 200 response.")

        # If agent pubkey requested
        elif "keys" in rest_params and rest_params["keys"] == "pubkey":
            response = {}
            response["pubkey"] = self.server.rsapublickey_exportable

            web_util.echo_json_response(self, 200, "Success", response)
            logger.info("GET pubkey returning 200 response.")
            return

        elif "quotes" in rest_params:
            nonce = rest_params.get("nonce", None)
            pcrmask = rest_params.get("mask", None)
            ima_ml_entry = rest_params.get("ima_ml_entry", "0")

            # if the query is not messed up
            if nonce is None:
                logger.warning(
                    "GET quote returning 400 response. nonce not provided as an HTTP parameter in request"
                )
                web_util.echo_json_response(
                    self, 400,
                    "nonce not provided as an HTTP parameter in request")
                return

            # Sanitization assurance (for tpm.run() tasks below)
            if not (nonce.isalnum() and
                    (pcrmask is None or validators.valid_hex(pcrmask))
                    and ima_ml_entry.isalnum()):
                logger.warning(
                    "GET quote returning 400 response. parameters should be strictly alphanumeric"
                )
                web_util.echo_json_response(
                    self, 400, "parameters should be strictly alphanumeric")
                return

            if len(nonce) > tpm_instance.MAX_NONCE_SIZE:
                logger.warning(
                    "GET quote returning 400 response. Nonce is too long (max size %i): %i",
                    tpm_instance.MAX_NONCE_SIZE,
                    len(nonce),
                )
                web_util.echo_json_response(
                    self, 400,
                    f"Nonce is too long (max size {tpm_instance.MAX_NONCE_SIZE}): {len(nonce)}"
                )
                return

            hash_alg = tpm_instance.defaults["hash"]
            quote = tpm_instance.create_quote(
                nonce, self.server.rsapublickey_exportable, pcrmask, hash_alg)
            imaMask = pcrmask

            # Allow for a partial quote response (without pubkey)
            enc_alg = tpm_instance.defaults["encrypt"]
            sign_alg = tpm_instance.defaults["sign"]

            if "partial" in rest_params and (rest_params["partial"] is None
                                             or rest_params["partial"] == "1"):
                response = {
                    "quote": quote,
                    "hash_alg": hash_alg,
                    "enc_alg": enc_alg,
                    "sign_alg": sign_alg,
                }
            else:
                response = {
                    "quote": quote,
                    "hash_alg": hash_alg,
                    "enc_alg": enc_alg,
                    "sign_alg": sign_alg,
                    "pubkey": self.server.rsapublickey_exportable,
                }

            response["boottime"] = self.server.boottime

            # return a measurement list if available
            if TPM_Utilities.check_mask(imaMask, config.IMA_PCR):
                ima_ml_entry = int(ima_ml_entry)
                if ima_ml_entry > self.server.next_ima_ml_entry:
                    ima_ml_entry = 0
                ml, nth_entry, num_entries = ima.read_measurement_list(
                    self.server.ima_log_file, ima_ml_entry)
                if num_entries > 0:
                    response["ima_measurement_list"] = ml
                    response["ima_measurement_list_entry"] = nth_entry
                    self.server.next_ima_ml_entry = num_entries

            # similar to how IMA log retrievals are triggered by IMA_PCR, we trigger boot logs with MEASUREDBOOT_PCRs
            # other possibilities would include adding additional data to rest_params to trigger boot log retrievals
            # generally speaking, retrieving the 15Kbytes of a boot log does not seem significant compared to the
            # potential Mbytes of an IMA measurement list.
            if TPM_Utilities.check_mask(imaMask, config.MEASUREDBOOT_PCRS[0]):
                if not self.server.tpm_log_file_data:
                    logger.warning("TPM2 event log not available: %s",
                                   config.MEASUREDBOOT_ML)
                else:
                    response[
                        "mb_measurement_list"] = self.server.tpm_log_file_data

            web_util.echo_json_response(self, 200, "Success", response)
            logger.info("GET %s quote returning 200 response.",
                        rest_params["quotes"])
            return

        else:
            logger.warning("GET returning 400 response. uri not supported: %s",
                           self.path)
            web_util.echo_json_response(self, 400, "uri not supported")
            return
예제 #3
0
    def process_allowlist(self, args):
        # Set up PCR values
        tpm_policy = config.get('tenant', 'tpm_policy')
        if "tpm_policy" in args and args["tpm_policy"] is not None:
            tpm_policy = args["tpm_policy"]
        self.tpm_policy = TPM_Utilities.readPolicy(tpm_policy)
        logger.info("TPM PCR Mask from policy is %s", self.tpm_policy['mask'])

        vtpm_policy = config.get('tenant', 'vtpm_policy')
        if "vtpm_policy" in args and args["vtpm_policy"] is not None:
            vtpm_policy = args["vtpm_policy"]
        self.vtpm_policy = TPM_Utilities.readPolicy(vtpm_policy)
        logger.info("TPM PCR Mask from policy is %s", self.vtpm_policy['mask'])

        if args.get("ima_sign_verification_keys") is not None:
            # Auto-enable IMA (or-bit mask)
            self.tpm_policy['mask'] = "0x%X" % (
                    int(self.tpm_policy['mask'], 0) | (1 << config.IMA_PCR))

            # Add all IMA file signing verification keys to a keyring
            ima_keyring = ima_file_signatures.ImaKeyring()
            for filename in args["ima_sign_verification_keys"]:
                pubkey = ima_file_signatures.get_pubkey_from_file(filename)
                if not pubkey:
                    raise UserError(
                        "File '%s' is not a file with a key" % filename)
                ima_keyring.add_pubkey(pubkey)
            self.ima_sign_verification_keys = ima_keyring.to_string()

        # Read command-line path string allowlist
        al_data = None

        if "allowlist" in args and args["allowlist"] is not None:

            self.enforce_pcrs(list(self.tpm_policy.keys()), [ config.IMA_PCR ], "IMA")

            # Auto-enable IMA (or-bit mask)
            self.tpm_policy['mask'] = "0x%X" % (
                    int(self.tpm_policy['mask'], 0) | (1 << config.IMA_PCR))

            if isinstance(args["allowlist"], str):
                if args["allowlist"] == "default":
                    args["allowlist"] = config.get('tenant', 'allowlist')
                al_data = ima.read_allowlist(args["allowlist"], args["allowlist_checksum"], args["allowlist_sig"], args["allowlist_sig_key"])
            elif isinstance(args["allowlist"], list):
                al_data = args["allowlist"]
            else:
                raise UserError("Invalid allowlist provided")

        # Read command-line path string IMA exclude list
        excl_data = None
        if "ima_exclude" in args and args["ima_exclude"] is not None:
            if isinstance(args["ima_exclude"], str):
                if args["ima_exclude"] == "default":
                    args["ima_exclude"] = config.get(
                        'tenant', 'ima_excludelist')
                excl_data = ima.read_excllist(args["ima_exclude"])
            elif isinstance(args["ima_exclude"], list):
                excl_data = args["ima_exclude"]
            else:
                raise UserError("Invalid exclude list provided")

        # Set up IMA
        if TPM_Utilities.check_mask(self.tpm_policy['mask'], config.IMA_PCR) or \
                TPM_Utilities.check_mask(self.vtpm_policy['mask'],
                                         config.IMA_PCR):
            # Process allowlists
            self.allowlist = ima.process_allowlists(al_data, excl_data)

        # Read command-line path string TPM event log (measured boot) reference state
        mb_refstate_data = None
        if "mb_refstate" in args and args["mb_refstate"] is not None:

            self.enforce_pcrs(list(self.tpm_policy.keys()), config.MEASUREDBOOT_PCRS, "measured boot")

            # Auto-enable TPM event log mesured boot (or-bit mask)
            for _pcr in config.MEASUREDBOOT_PCRS :
                self.tpm_policy['mask'] = "0x%X" % (
                    int(self.tpm_policy['mask'], 0) | (1 << _pcr))

            logger.info("TPM PCR Mask automatically modified is %s to include IMA/Event log PCRs", self.tpm_policy['mask'])

            if isinstance(args["mb_refstate"], str):
                if args["mb_refstate"] == "default":
                    args["mb_refstate"] = config.get('tenant', 'mb_refstate')
                mb_refstate_data = measured_boot.read_mb_refstate(args["mb_refstate"])
            else:
                raise UserError("Invalid measured boot reference state (intended state) provided")

        # Set up measured boot (TPM event log) reference state
        if TPM_Utilities.check_mask(self.tpm_policy['mask'], config.MEASUREDBOOT_PCRS[2]) :

            # Process measured boot reference state
            self.mb_refstate = measured_boot.process_refstate(mb_refstate_data)
예제 #4
0
    def do_GET(self):
        """This method services the GET request typically from either the Tenant or the Cloud Verifier.

        Only tenant and cloudverifier uri's are supported. Both requests require a nonce parameter.
        The Cloud verifier requires an additional mask paramter.  If the uri or parameters are incorrect, a 400 response is returned.
        """

        logger.info('GET invoked from %s with uri: %s', self.client_address,
                    self.path)
        rest_params = config.get_restful_params(self.path)
        if rest_params is None:
            config.echo_json_response(
                self, 405,
                "Not Implemented: Use /keys/ or /quotes/ interfaces")
            return

        if "keys" in rest_params and rest_params['keys'] == 'verify':
            if self.server.K is None:
                logger.info(
                    'GET key challenge returning 400 response. bootstrap key not available'
                )
                config.echo_json_response(self, 400,
                                          "Bootstrap key not yet available.")
                return
            challenge = rest_params['challenge']
            response = {}
            response['hmac'] = crypto.do_hmac(self.server.K, challenge)
            config.echo_json_response(self, 200, "Success", response)
            logger.info('GET key challenge returning 200 response.')

        # If agent pubkey requested
        elif "keys" in rest_params and rest_params["keys"] == "pubkey":
            response = {}
            response['pubkey'] = self.server.rsapublickey_exportable

            config.echo_json_response(self, 200, "Success", response)
            logger.info('GET pubkey returning 200 response.')
            return

        elif "quotes" in rest_params:
            nonce = rest_params['nonce']
            pcrmask = rest_params['mask'] if 'mask' in rest_params else None
            vpcrmask = rest_params['vmask'] if 'vmask' in rest_params else None

            # if the query is not messed up
            if nonce is None:
                logger.warning(
                    'GET quote returning 400 response. nonce not provided as an HTTP parameter in request'
                )
                config.echo_json_response(
                    self, 400,
                    "nonce not provided as an HTTP parameter in request")
                return

            # Sanitization assurance (for tpm.run() tasks below)
            if not (nonce.isalnum() and
                    (pcrmask is None or pcrmask.isalnum()) and
                    (vpcrmask is None or vpcrmask.isalnum())):
                logger.warning(
                    'GET quote returning 400 response. parameters should be strictly alphanumeric'
                )
                config.echo_json_response(
                    self, 400, "parameters should be strictly alphanumeric")
                return

            # identity quotes are always shallow
            hash_alg = tpm_instance.defaults['hash']
            if not tpm_instance.is_vtpm(
            ) or rest_params["quotes"] == 'identity':
                quote = tpm_instance.create_quote(
                    nonce, self.server.rsapublickey_exportable, pcrmask,
                    hash_alg)
                imaMask = pcrmask

            # Allow for a partial quote response (without pubkey)
            enc_alg = tpm_instance.defaults['encrypt']
            sign_alg = tpm_instance.defaults['sign']

            if "partial" in rest_params and (rest_params["partial"] is None
                                             or int(rest_params["partial"],
                                                    0) == 1):
                response = {
                    'quote': quote,
                    'hash_alg': hash_alg,
                    'enc_alg': enc_alg,
                    'sign_alg': sign_alg,
                }
            else:
                response = {
                    'quote': quote,
                    'hash_alg': hash_alg,
                    'enc_alg': enc_alg,
                    'sign_alg': sign_alg,
                    'pubkey': self.server.rsapublickey_exportable,
                }

            # return a measurement list if available
            if TPM_Utilities.check_mask(imaMask, config.IMA_PCR):
                if not os.path.exists(config.IMA_ML):
                    logger.warning("IMA measurement list not available: %s",
                                   config.IMA_ML)
                else:
                    with open(config.IMA_ML, 'r') as f:
                        ml = f.read()
                    response['ima_measurement_list'] = ml

            # similar to how IMA log retrievals are triggered by IMA_PCR, we trigger boot logs with MEASUREDBOOT_PCRs
            # other possibilities would include adding additional data to rest_params to trigger boot log retrievals
            # generally speaking, retrieving the 15Kbytes of a boot log does not seem significant compared to the
            # potential Mbytes of an IMA measurement list.
            if TPM_Utilities.check_mask(imaMask, config.MEASUREDBOOT_PCRS[0]):
                if not os.path.exists(config.MEASUREDBOOT_ML):
                    logger.warning("TPM2 event log not available: %s",
                                   config.MEASUREDBOOT_ML)
                else:
                    with open(config.MEASUREDBOOT_ML, 'rb') as f:
                        el = base64.b64encode(f.read())
                    response['mb_measurement_list'] = el

            config.echo_json_response(self, 200, "Success", response)
            logger.info('GET %s quote returning 200 response.',
                        rest_params["quotes"])
            return

        else:
            logger.warning('GET returning 400 response. uri not supported: %s',
                           self.path)
            config.echo_json_response(self, 400, "uri not supported")
            return
예제 #5
0
    def do_GET(self):
        """This method services the GET request typically from either the Tenant or the Cloud Verifier.

        Only tenant and cloudverifier uri's are supported. Both requests require a nonce parameter.
        The Cloud verifier requires an additional mask paramter.  If the uri or parameters are incorrect, a 400 response is returned.
        """

        logger.info('GET invoked from ' + str(self.client_address) +
                    ' with uri:' + self.path)

        rest_params = common.get_restful_params(self.path)
        if rest_params is None:
            common.echo_json_response(
                self, 405,
                "Not Implemented: Use /keys/ or /quotes/ interfaces")
            return

        if "keys" in rest_params and rest_params['keys'] == 'verify':
            if self.server.K is None:
                logger.info(
                    'GET key challenge returning 400 response. bootstrap key not available'
                )
                common.echo_json_response(self, 400,
                                          "Bootstrap key not yet available.")
                return
            challenge = rest_params['challenge']
            response = {}
            response['hmac'] = crypto.do_hmac(self.server.K, challenge)
            common.echo_json_response(self, 200, "Success", response)
            logger.info('GET key challenge returning 200 response.')

        # If agent pubkey requested
        elif "keys" in rest_params and rest_params["keys"] == "pubkey":
            response = {}
            response['pubkey'] = self.server.rsapublickey_exportable

            common.echo_json_response(self, 200, "Success", response)
            logger.info('GET pubkey returning 200 response.')
            return

        elif "quotes" in rest_params:
            nonce = rest_params['nonce']
            pcrmask = rest_params['mask'] if 'mask' in rest_params else None
            vpcrmask = rest_params['vmask'] if 'vmask' in rest_params else None

            # if the query is not messed up
            if nonce is None:
                logger.warning(
                    'GET quote returning 400 response. nonce not provided as an HTTP parameter in request'
                )
                common.echo_json_response(
                    self, 400,
                    "nonce not provided as an HTTP parameter in request")
                return

            # Sanitization assurance (for tpm.run() tasks below)
            if not (nonce.isalnum() and
                    (pcrmask is None or pcrmask.isalnum()) and
                    (vpcrmask is None or vpcrmask.isalnum())):
                logger.warning(
                    'GET quote returning 400 response. parameters should be strictly alphanumeric'
                )
                common.echo_json_response(
                    self, 400, "parameters should be strictly alphanumeric")
                return

            # identity quotes are always shallow
            hash_alg = tpm.defaults['hash']
            if not tpm.is_vtpm() or rest_params["quotes"] == 'identity':
                quote = tpm.create_quote(nonce,
                                         self.server.rsapublickey_exportable,
                                         pcrmask, hash_alg)
                imaMask = pcrmask
            else:
                quote = tpm.create_deep_quote(
                    nonce, self.server.rsapublickey_exportable, vpcrmask,
                    pcrmask)
                imaMask = vpcrmask

            # Allow for a partial quote response (without pubkey)
            enc_alg = tpm.defaults['encrypt']
            sign_alg = tpm.defaults['sign']
            if "partial" in rest_params and (rest_params["partial"] is None
                                             or int(rest_params["partial"],
                                                    0) == 1):
                response = {
                    'quote': quote,
                    'tpm_version': tpm_version,
                    'hash_alg': hash_alg,
                    'enc_alg': enc_alg,
                    'sign_alg': sign_alg,
                }
            else:
                response = {
                    'quote': quote,
                    'tpm_version': tpm_version,
                    'hash_alg': hash_alg,
                    'enc_alg': enc_alg,
                    'sign_alg': sign_alg,
                    'pubkey': self.server.rsapublickey_exportable,
                }

            # return a measurement list if available
            if TPM_Utilities.check_mask(imaMask, common.IMA_PCR):
                if not os.path.exists(common.IMA_ML):
                    logger.warn("IMA measurement list not available: %s" %
                                (common.IMA_ML))
                else:
                    with open(common.IMA_ML, 'r') as f:
                        ml = f.read()
                    response['ima_measurement_list'] = ml

            common.echo_json_response(self, 200, "Success", response)
            logger.info('GET %s quote returning 200 response.' %
                        (rest_params["quotes"]))
            return

        else:
            logger.warning('GET returning 400 response. uri not supported: ' +
                           self.path)
            common.echo_json_response(self, 400, "uri not supported")
            return
예제 #6
0
    def init_add(self, args):
        """ Set up required values. Command line options can overwrite these config values

        Arguments:
            args {[string]} -- agent_ip|agent_port|cv_agent_ip
        """
        if "agent_ip" in args:
            self.agent_ip = args["agent_ip"]

        if 'agent_port' in args and args['agent_port'] is not None:
            self.agent_port = args['agent_port']

        if 'cv_agent_ip' in args and args['cv_agent_ip'] is not None:
            self.cv_cloudagent_ip = args['cv_agent_ip']
        else:
            self.cv_cloudagent_ip = self.agent_ip

        # Make sure all keys exist in dictionary
        if "file" not in args:
            args["file"] = None
        if "keyfile" not in args:
            args["keyfile"] = None
        if "payload" not in args:
            args["payload"] = None
        if "ca_dir" not in args:
            args["ca_dir"] = None
        if "incl_dir" not in args:
            args["incl_dir"] = None
        if "ca_dir_pw" not in args:
            args["ca_dir_pw"] = None

        # Set up accepted algorithms
        self.accept_tpm_hash_algs = config.get(
            'tenant', 'accept_tpm_hash_algs').split(',')
        self.accept_tpm_encryption_algs = config.get(
            'tenant', 'accept_tpm_encryption_algs').split(',')
        self.accept_tpm_signing_algs = config.get(
            'tenant', 'accept_tpm_signing_algs').split(',')

        # Set up PCR values
        tpm_policy = config.get('tenant', 'tpm_policy')
        if "tpm_policy" in args and args["tpm_policy"] is not None:
            tpm_policy = args["tpm_policy"]
        self.tpm_policy = TPM_Utilities.readPolicy(tpm_policy)
        logger.info(f"TPM PCR Mask from policy is {self.tpm_policy['mask']}")

        vtpm_policy = config.get('tenant', 'vtpm_policy')
        if "vtpm_policy" in args and args["vtpm_policy"] is not None:
            vtpm_policy = args["vtpm_policy"]
        self.vtpm_policy = TPM_Utilities.readPolicy(vtpm_policy)
        logger.info(f"TPM PCR Mask from policy is {self.vtpm_policy['mask']}")

        if args.get("ima_sign_verification_keys") is not None:
            # Auto-enable IMA (or-bit mask)
            self.tpm_policy['mask'] = "0x%X" % (int(self.tpm_policy['mask'], 0)
                                                | (1 << config.IMA_PCR))

            # Add all IMA file signing verification keys to a keyring
            ima_keyring = ima_file_signatures.ImaKeyring()
            for filename in args["ima_sign_verification_keys"]:
                pubkey = ima_file_signatures.get_pubkey_from_file(filename)
                if not pubkey:
                    raise UserError("File '%s' is not a file with a key" %
                                    filename)
                ima_keyring.add_pubkey(pubkey)
            self.ima_sign_verification_keys = ima_keyring.to_string()

        # Read command-line path string allowlist
        al_data = None

        if "allowlist" in args and args["allowlist"] is not None:

            self.enforce_pcrs(list(self.tpm_policy.keys()), [config.IMA_PCR],
                              "IMA")

            # Auto-enable IMA (or-bit mask)
            self.tpm_policy['mask'] = "0x%X" % (int(self.tpm_policy['mask'], 0)
                                                | (1 << config.IMA_PCR))

            if isinstance(args["allowlist"], str):
                if args["allowlist"] == "default":
                    args["allowlist"] = config.get('tenant', 'allowlist')
                al_data = ima.read_allowlist(args["allowlist"])
            elif isinstance(args["allowlist"], list):
                al_data = args["allowlist"]
            else:
                raise UserError("Invalid allowlist provided")

        # Read command-line path string IMA exclude list
        excl_data = None
        if "ima_exclude" in args and args["ima_exclude"] is not None:
            if isinstance(args["ima_exclude"], str):
                if args["ima_exclude"] == "default":
                    args["ima_exclude"] = config.get('tenant',
                                                     'ima_excludelist')
                excl_data = ima.read_excllist(args["ima_exclude"])
            elif isinstance(args["ima_exclude"], list):
                excl_data = args["ima_exclude"]
            else:
                raise UserError("Invalid exclude list provided")

        # Set up IMA
        if TPM_Utilities.check_mask(self.tpm_policy['mask'], config.IMA_PCR) or \
                TPM_Utilities.check_mask(self.vtpm_policy['mask'], config.IMA_PCR):

            # Process allowlists
            self.allowlist = ima.process_allowlists(al_data, excl_data)

        # Read command-line path string TPM2 event log template
        if "mb_refstate" in args and args["mb_refstate"] is not None:

            self.enforce_pcrs(list(self.tpm_policy.keys()),
                              config.MEASUREDBOOT_PCRS, "measured boot")

            # Auto-enable TPM2 event log (or-bit mask)
            for _pcr in config.MEASUREDBOOT_PCRS:
                self.tpm_policy['mask'] = "0x%X" % (int(
                    self.tpm_policy['mask'], 0) | (1 << _pcr))

            logger.info(
                f"TPM PCR Mask automatically modified is {self.tpm_policy['mask']} to include IMA/Event log PCRs"
            )

            if isinstance(args["mb_refstate"], str):
                if args["mb_refstate"] == "default":
                    args["mb_refstate"] = config.get('tenant', 'mb_refstate')
                al_data = 'test'
            elif isinstance(args["mb_refstate"], list):
                al_data = args["mb_refstate"]
            else:
                raise UserError(
                    "Invalid measured boot reference state (intended state) provided"
                )

        # if none
        if (args["file"] is None and args["keyfile"] is None
                and args["ca_dir"] is None):
            raise UserError(
                "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
            )

        if args["keyfile"] is not None:
            if args["file"] is not None or args["ca_dir"] is not None:
                raise UserError(
                    "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
                )

            # read the keys in
            if isinstance(args["keyfile"], dict) and "data" in args["keyfile"]:
                if isinstance(args["keyfile"]["data"], list) and len(
                        args["keyfile"]["data"]) == 1:
                    keyfile = args["keyfile"]["data"][0]
                    if keyfile is None:
                        raise UserError("Invalid key file contents")
                    f = io.StringIO(keyfile)
                else:
                    raise UserError("Invalid key file provided")
            else:
                f = open(args["keyfile"], 'r')
            self.K = base64.b64decode(f.readline())
            self.U = base64.b64decode(f.readline())
            self.V = base64.b64decode(f.readline())
            f.close()

            # read the payload in (opt.)
            if isinstance(args["payload"], dict) and "data" in args["payload"]:
                if isinstance(args["payload"]["data"],
                              list) and len(args["payload"]["data"]) > 0:
                    self.payload = args["payload"]["data"][0]
            else:
                if args["payload"] is not None:
                    f = open(args["payload"], 'r')
                    self.payload = f.read()
                    f.close()

        if args["file"] is not None:
            if args["keyfile"] is not None or args["ca_dir"] is not None:
                raise UserError(
                    "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
                )

            if isinstance(args["file"], dict) and "data" in args["file"]:
                if isinstance(args["file"]["data"],
                              list) and len(args["file"]["data"]) > 0:
                    contents = args["file"]["data"][0]
                    if contents is None:
                        raise UserError("Invalid file payload contents")
                else:
                    raise UserError("Invalid file payload provided")
            else:
                with open(args["file"], 'r') as f:
                    contents = f.read()
            ret = user_data_encrypt.encrypt(contents)
            self.K = ret['k']
            self.U = ret['u']
            self.V = ret['v']
            self.payload = ret['ciphertext']

        if args["ca_dir"] is None and args["incl_dir"] is not None:
            raise UserError(
                "--include option is only valid when used with --cert")
        if args["ca_dir"] is not None:
            if args["file"] is not None or args["keyfile"] is not None:
                raise UserError(
                    "You must specify one of -k, -f, or --cert to specify the key/contents to be securely delivered to the agent"
                )
            if args["ca_dir"] == 'default':
                args["ca_dir"] = config.CA_WORK_DIR

            if "ca_dir_pw" in args and args["ca_dir_pw"] is not None:
                ca_util.setpassword(args["ca_dir_pw"])

            if not os.path.exists(args["ca_dir"]) or not os.path.exists(
                    "%s/cacert.crt" % args["ca_dir"]):
                logger.warning(" CA directory does not exist.  Creating...")
                ca_util.cmd_init(args["ca_dir"])
            if not os.path.exists("%s/%s-private.pem" %
                                  (args["ca_dir"], self.agent_uuid)):
                ca_util.cmd_mkcert(args["ca_dir"], self.agent_uuid)

            cert_pkg, serial, subject = ca_util.cmd_certpkg(
                args["ca_dir"], self.agent_uuid)

            # support revocation
            if not os.path.exists(
                    "%s/RevocationNotifier-private.pem" % args["ca_dir"]):
                ca_util.cmd_mkcert(args["ca_dir"], "RevocationNotifier")
            rev_package, _, _ = ca_util.cmd_certpkg(args["ca_dir"],
                                                    "RevocationNotifier")

            # extract public and private keys from package
            sf = io.BytesIO(rev_package)
            with zipfile.ZipFile(sf) as zf:
                privkey = zf.read("RevocationNotifier-private.pem")
                cert = zf.read("RevocationNotifier-cert.crt")

            # put the cert of the revoker into the cert package
            sf = io.BytesIO(cert_pkg)
            with zipfile.ZipFile(sf, 'a',
                                 compression=zipfile.ZIP_STORED) as zf:
                zf.writestr('RevocationNotifier-cert.crt', cert)

                # add additional files to zip
                if args["incl_dir"] is not None:
                    if isinstance(args["incl_dir"], dict) and "data" in args[
                            "incl_dir"] and "name" in args["incl_dir"]:
                        if isinstance(args["incl_dir"]["data"],
                                      list) and isinstance(
                                          args["incl_dir"]["name"], list):
                            if len(args["incl_dir"]["data"]) != len(
                                    args["incl_dir"]["name"]):
                                raise UserError("Invalid incl_dir provided")
                            for i in range(len(args["incl_dir"]["data"])):
                                zf.writestr(
                                    os.path.basename(
                                        args["incl_dir"]["name"][i]),
                                    args["incl_dir"]["data"][i])
                    else:
                        if os.path.exists(args["incl_dir"]):
                            files = next(os.walk(args["incl_dir"]))[2]
                            for filename in files:
                                with open(
                                        "%s/%s" % (args["incl_dir"], filename),
                                        'rb') as f:
                                    zf.writestr(os.path.basename(f.name),
                                                f.read())
                        else:
                            logger.warning(
                                f'Specified include directory {args["incl_dir"]} does not exist.  Skipping...'
                            )

            cert_pkg = sf.getvalue()

            # put the private key into the data to be send to the CV
            self.revocation_key = privkey

            # encrypt up the cert package
            ret = user_data_encrypt.encrypt(cert_pkg)
            self.K = ret['k']
            self.U = ret['u']
            self.V = ret['v']
            self.metadata = {'cert_serial': serial, 'subject': subject}
            self.payload = ret['ciphertext']

        if self.payload is not None and len(self.payload) > config.getint(
                'tenant', 'max_payload_size'):
            raise UserError("Payload size %s exceeds max size %d" % (len(
                self.payload), config.getint('tenant', 'max_payload_size')))