Exemple #1
0
def gencrl(serials,cert,ca_pk):
    request = {"certificate": cert,
               "serialNumber": serials,
               "issuingKey": ca_pk,
               "expireTime": ""
               }
    secdir = secure_mount.mount()
    try:
        # need to temporarily write out the private key with no password
        # to tmpfs
        priv_key = os.path.abspath("%s/ca-key.pem"%secdir)
        with open(priv_key,'w') as f:
            f.write(ca_pk)
        cmdline = " -ca-key %s -ca cacert.crt"%(priv_key)

        start_cfssl(cmdline)
        body = post_cfssl('api/v1/cfssl/gencrl',request)

    finally:
        stop_cfssl()
        # replace with srm
        os.remove('%s/ca-key.pem'%secdir)

    if body['success']:
        retval = base64.b64decode(body['result'])
    else:
        raise Exception("Unable to create crl for cert serials %s.  Error: %s"%(serials,body['errors']))
    return retval
    def worker():
        def print_notification(revocation):
            logger.warning("Received revocation: %s", revocation)

        keypath = os.path.join(secure_mount.mount(), "unzipped",
                               "RevocationNotifier-cert.crt")
        await_notifications(print_notification, revocation_cert_path=keypath)
    def __init__(self, server_address, RequestHandlerClass, agent_uuid):
        """Constructor overridden to provide ability to pass configuration arguments to the server"""
        secdir = secure_mount.mount()
        keyname = "%s/%s" % (secdir, config.get('cloud_agent', 'rsa_keyname'))

        # read or generate the key depending on configuration
        if os.path.isfile(keyname):
            # read in private key
            logger.debug("Using existing key in %s" % keyname)
            f = open(keyname, "rb")
            rsa_key = crypto.rsa_import_privkey(f.read())
        else:
            logger.debug("key not found, generating a new one")
            rsa_key = crypto.rsa_generate(2048)
            with open(keyname, "wb") as f:
                f.write(crypto.rsa_export_privkey(rsa_key))

        self.rsaprivatekey = rsa_key
        self.rsapublickey_exportable = crypto.rsa_export_pubkey(
            self.rsaprivatekey)

        #attempt to get a U value from the TPM NVRAM
        nvram_u = tpm.read_key_nvram()
        if nvram_u is not None:
            logger.info("Existing U loaded from TPM NVRAM")
            self.add_U(nvram_u)
        http.server.HTTPServer.__init__(self, server_address,
                                        RequestHandlerClass)
        self.enc_keyname = config.get('cloud_agent', 'enc_keyname')
        self.agent_uuid = agent_uuid
async def execute(revocation):
    serial = revocation.get("metadata",{}).get("cert_serial",None)
    if revocation.get('type',None) != 'revocation' or serial is None:
        logger.error("Unsupported revocation message: %s"%revocation)

    # load up the ca cert
    secdir = secure_mount.mount()
    ca = X509.load_cert('%s/unzipped/cacert.crt'%secdir)

    # need to find any sa's that were established with that cert serial
    output = cmd_exec.run("racoonctl show-sa ipsec",lock=False,raiseOnError=True)['retout']
    deletelist=set()
    for line in output:
        if not line.startswith(b"\t"):
            certder = cmd_exec.run("racoonctl get-cert inet %s"%line.strip(),raiseOnError=False,lock=False)['retout']
            if len(certder)==0:
                continue;
            certobj = X509.load_cert_der_string(b''.join(certder))

            # check that CA is the same.  the strip indexing bit is to remove the stuff around it 'keyid:THEACTUALKEYID\n'
            if ca.get_ext('subjectKeyIdentifier').get_value() != certobj.get_ext('authorityKeyIdentifier').get_value().strip()[6:]:
                continue

            if certobj.get_serial_number() == serial:
                deletelist.add(line.strip())

    for todelete in deletelist:
        logger.info("deleting IPsec sa between %s"%todelete)
        cmd_exec.run("racoonctl delete-sa isakmp inet %s"%todelete,lock=False)
        tokens = todelete.split()
        cmd_exec.run("racoonctl delete-sa isakmp inet %s %s"%(tokens[1],tokens[0]),lock=False)
Exemple #5
0
    def test_010_reg_agent_post(self):
        """Test registrar's POST /v2/agents/{UUID} Interface"""
        global keyblob, aik, vtpm, ek

        # Change CWD for TPM-related operations
        cwd = os.getcwd()
        common.ch_dir(common.WORK_DIR, None)
        secdir = secure_mount.mount()

        # Initialize the TPM with AIK
        (ek, ekcert, aik, ek_tpm,
         aik_name) = tpm.tpm_init(self_activate=False,
                                  config_pw=config.get('cloud_agent',
                                                       'tpm_ownerpassword'))
        vtpm = tpm.is_vtpm()

        # Seed RNG (root only)
        if common.REQUIRE_ROOT:
            tpm.init_system_rand()

        # Handle virtualized and emulated TPMs
        if ekcert is None:
            if vtpm:
                ekcert = 'virtual'
            elif tpm.is_emulator():
                ekcert = 'emulator'

        # Get back to our original CWD
        common.ch_dir(cwd, None)

        data = {
            'ek': ek,
            'ekcert': ekcert,
            'aik': aik,
            'aik_name': aik_name,
            'ek_tpm': ek_tpm,
            'tpm_version': tpm.get_tpm_version(),
        }
        v_json_message = json.dumps(data)

        params = f"/v{self.api_version}/agents/{tenant_templ.agent_uuid}"
        response = httpclient_requests.request(
            "POST",
            "%s" % tenant_templ.registrar_ip,
            tenant_templ.registrar_boot_port,
            params=params,
            data=v_json_message,
            context=None)

        self.assertEqual(response.status, 200,
                         "Non-successful Registrar agent Add return code!")
        json_response = json.loads(response.read().decode())

        # Ensure response is well-formed
        self.assertIn("results", json_response, "Malformed response body!")
        self.assertIn("blob", json_response["results"],
                      "Malformed response body!")

        keyblob = json_response["results"]["blob"]
        self.assertIsNotNone(keyblob, "Malformed response body!")
    def worker():
        def print_notification(revocation):
            logger.warning("Received revocation: %s" % revocation)

        keypath = '%s/unzipped/RevocationNotifier-cert.crt' % (
            secure_mount.mount())
        await_notifications(print_notification, revocation_cert_path=keypath)
Exemple #7
0
async def execute(revocation):
    json_meta = json.loads(revocation['meta_data'])
    serial = json_meta['cert_serial']
    subject = json_meta['subject']
    if revocation.get(
            'type', None) != 'revocation' or serial is None or subject is None:
        logger.error("Unsupported revocation message: %s" % revocation)

    # import the crl into NSS
    secdir = secure_mount.mount()
    logger.info("loading updated CRL from %s/unzipped/cacrl.der into NSS" %
                secdir)
    cmd_exec.run("crlutil -I -i %s/unzipped/cacrl.der -d sql:/etc/ipsec.d" %
                 secdir,
                 lock=False)

    # need to find any sa's that were established with that cert subject name
    output = cmd_exec.run("ipsec whack --trafficstatus",
                          lock=False,
                          raiseOnError=True)['retout']
    deletelist = set()
    id = ""
    for line in output:
        line = line.strip()
        try:
            idstart = line.index("id='") + 4
            idend = line[idstart:].index('\'')

            id = line[idstart:idstart + idend]

            privatestart = line.index("private#") + 8
            privateend = line[privatestart:].index("/")

            ip = line[privatestart:privatestart + privateend]
        except ValueError:
            # weirdly formatted line
            continue

    # kill all the commas
    id = id.replace(",", "")
    cursubj = {}
    for token in id.split():
        cur = token.split('=')
        cursubj[cur[0]] = cur[1]

    cert = {}
    for token in subject[1:].split("/"):
        cur = token.split('=')
        cert[cur[0]] = cur[1]

    if cert == cursubj:
        deletelist.add(ip)

    for todelete in deletelist:
        logger.info("deleting IPsec sa with %s" % todelete)
        cmd_exec.run("ipsec whack --crash %s" % todelete,
                     raiseOnError=False,
                     lock=False)
Exemple #8
0
    def test_010_reg_agent_post(self):
        """Test registrar's POST /v2/agents/{UUID} Interface"""
        global keyblob, aik, vtpm, ek

        # Change CWD for TPM-related operations
        cwd = os.getcwd()
        config.ch_dir(config.WORK_DIR, None)
        _ = secure_mount.mount()

        # Initialize the TPM with AIK
        (ek, ekcert, aik, ek_tpm,
         aik_name) = tpm.tpm_init(self_activate=False,
                                  config_pw=config.get('cloud_agent',
                                                       'tpm_ownerpassword'))
        vtpm = tpm.is_vtpm()

        # Seed RNG (root only)
        if config.REQUIRE_ROOT:
            tpm.init_system_rand()

        # Handle virtualized and emulated TPMs
        if ekcert is None:
            if vtpm:
                ekcert = 'virtual'
            elif tpm.is_emulator():
                ekcert = 'emulator'

        # Get back to our original CWD
        config.ch_dir(cwd, None)

        data = {
            'ek': ek,
            'ekcert': ekcert,
            'aik': aik,
            'aik_name': aik_name,
            'ek_tpm': ek_tpm,
            'tpm_version': tpm.VERSION,
        }

        test_010_reg_agent_post = RequestsClient(
            tenant_templ.registrar_base_url, tls_enabled=False)
        response = test_010_reg_agent_post.post(
            f'/v{self.api_version}/agents/{tenant_templ.agent_uuid}',
            data=json.dumps(data),
            cert="",
            verify=False)

        self.assertEqual(response.status_code, 200,
                         "Non-successful Registrar agent Add return code!")
        json_response = response.json()

        # Ensure response is well-formed
        self.assertIn("results", json_response, "Malformed response body!")
        self.assertIn("blob", json_response["results"],
                      "Malformed response body!")

        keyblob = json_response["results"]["blob"]
        self.assertIsNotNone(keyblob, "Malformed response body!")
Exemple #9
0
    def test_check_mount_no_secure(self, config_mock, _logger_mock):
        """Test when mounted outside tmpfs."""
        config_mock.MOUNT_SECURE = False

        with tempfile.TemporaryDirectory() as tmpdirname:
            config_mock.WORK_DIR = tmpdirname
            self.assertEqual(secure_mount.mount(), f"{tmpdirname}/tmpfs-dev")
            # pylint: disable=protected-access
            self.assertEqual(secure_mount._MOUNTED, [])
    def test_010_reg_agent_post(self):
        """Test registrar's POST /agents/{UUID} Interface"""
        global keyblob, vtpm, tpm_instance, ek_tpm, aik_tpm
        contact_ip = "127.0.0.1"
        contact_port = 9002
        tpm_instance = tpm_main.tpm()

        # Change CWD for TPM-related operations
        cwd = os.getcwd()
        config.ch_dir(config.WORK_DIR, None)
        _ = secure_mount.mount()

        # Initialize the TPM with AIK
        (ekcert, ek_tpm, aik_tpm) = tpm_instance.tpm_init(
            self_activate=False,
            config_pw=config.get('cloud_agent', 'tpm_ownerpassword'))
        vtpm = tpm_instance.is_vtpm()

        # Handle virtualized and emulated TPMs
        if ekcert is None:
            if vtpm:
                ekcert = 'virtual'
            elif tpm_instance.is_emulator():
                ekcert = 'emulator'

        # Get back to our original CWD
        config.ch_dir(cwd, None)

        data = {
            'ekcert': ekcert,
            'aik_tpm': aik_tpm,
            'ip': contact_ip,
            'port': contact_port
        }
        if ekcert is None or ekcert == 'emulator':
            data['ek_tpm'] = ek_tpm

        test_010_reg_agent_post = RequestsClient(
            tenant_templ.registrar_base_url, tls_enabled=False)
        response = test_010_reg_agent_post.post(
            f'/v{self.api_version}/agents/{tenant_templ.agent_uuid}',
            data=json.dumps(data),
            cert="",
            verify=False)

        self.assertEqual(response.status_code, 200,
                         "Non-successful Registrar agent Add return code!")
        json_response = response.json()

        # Ensure response is well-formed
        self.assertIn("results", json_response, "Malformed response body!")
        self.assertIn("blob", json_response["results"],
                      "Malformed response body!")

        keyblob = json_response["results"]["blob"]
        self.assertIsNotNone(keyblob, "Malformed response body!")
Exemple #11
0
    def test_check_mount_secure_already_mounted(self, config_mock, check_mounted_mock):
        """Test when mounting in tmpfs but is already present."""
        config_mock.MOUNT_SECURE = True
        check_mounted_mock.return_value = True

        with tempfile.TemporaryDirectory() as tmpdirname:
            config_mock.WORK_DIR = tmpdirname
            self.assertEqual(secure_mount.mount(), f"{tmpdirname}/secure")
            # pylint: disable=protected-access
            self.assertEqual(secure_mount._MOUNTED, [])
Exemple #12
0
def execute(json_revocation):
    if json_revocation['type'] != 'revocation':
        return

    secdir = secure_mount.mount()

    cert_path = config.get('cloud_agent', 'revocation_cert')
    if cert_path == "default":
        cert_path = os.path.join(secdir, "unzipped",
                                 "RevocationNotifier-cert.crt")
    else:
        # if it is a relative, convert to absolute in work_dir
        if cert_path[0] != '/':
            cert_path = os.path.abspath(
                os.path.join(common.WORK_DIR, cert_path))
        if not os.path.exists(cert_path):
            raise Exception(
                f"revocation_cert {os.path.abspath(cert_path)} not found")

    # get the updated CRL
    dist_path = ca_util.get_crl_distpoint(cert_path)

    with open(os.path.join(secdir, "unzipped", "cacrl.der"), "rb") as f:
        oldcrl = f.read()

    updated = False
    for _ in range(10):
        logger.debug("Getting updated CRL from %s", dist_path)
        response = tornado_requests.request("GET", dist_path, None, None, None)
        if response.status_code != 200:
            logger.warning("Unable to get updated CRL from %s.  Code %d",
                           dist_path, response.status_code)
            time.sleep(1)
            continue
        if response.body == oldcrl:
            logger.warning("CRL not yet updated, trying again in 1 second...")
            time.sleep(1)
            continue

        # write out the updated CRL
        logger.debug("Updating CRL in %s/unzipped/cacrl.der", secdir)
        with open(os.path.join(secdir, "unzipped", "cacrl.der"), "wb") as f:
            f.write(response.body)
        ca_util.convert_crl_to_pem(
            os.path.join(secdir, "unzipped", "cacrl.der"),
            os.path.join(secdir, "unzipped", "cacrl.pem"))
        updated = True
        break

    if not updated:
        logger.error(
            "Unable to load new CRL from %s after receiving notice of a revocation",
            dist_path)
Exemple #13
0
    def test_check_mount_secure_already_created(
        self, _cmd_exec_mock, exists_mock, config_mock, check_mounted_mock, _logger_mock
    ):
        """Test when mounting in tmpfs but the mount point is present."""
        exists_mock.return_value = True
        config_mock.MOUNT_SECURE = True
        check_mounted_mock.return_value = False

        with tempfile.TemporaryDirectory() as tmpdirname:
            config_mock.WORK_DIR = tmpdirname
            self.assertEqual(secure_mount.mount(), f"{tmpdirname}/secure")
            # pylint: disable=protected-access
            self.assertEqual(secure_mount._MOUNTED, [f"{tmpdirname}/secure"])
Exemple #14
0
    def test_010_reg_agent_post(self):
        """Test registrar's POST /agents/{UUID} Interface"""
        global keyblob, tpm_instance, ek_tpm, aik_tpm
        contact_ip = "127.0.0.1"
        contact_port = 9002
        tpm_instance = tpm_main.tpm()

        # Change CWD for TPM-related operations
        cwd = os.getcwd()
        fs_util.ch_dir(config.WORK_DIR)
        _ = secure_mount.mount()

        # Create a mTLS cert for testing
        global mtls_cert
        rsa_key = crypto.rsa_generate(2048)
        valid_util = datetime.datetime.utcnow() + datetime.timedelta(days=(360 * 5))
        mtls_cert = crypto.generate_selfsigned_cert("TEST_CERT", rsa_key, valid_util).public_bytes(
            serialization.Encoding.PEM
        )

        # Initialize the TPM with AIK
        (ekcert, ek_tpm, aik_tpm) = tpm_instance.tpm_init(
            self_activate=False, config_pw=config.get("cloud_agent", "tpm_ownerpassword")
        )

        # Handle emulated TPMs
        if ekcert is None:
            if tpm_instance.is_emulator():
                ekcert = "emulator"

        # Get back to our original CWD
        fs_util.ch_dir(cwd)

        data = {"ekcert": ekcert, "aik_tpm": aik_tpm, "ip": contact_ip, "port": contact_port, "mtls_cert": mtls_cert}
        if ekcert is None or ekcert == "emulator":
            data["ek_tpm"] = ek_tpm

        test_010_reg_agent_post = RequestsClient(tenant_templ.registrar_base_url, tls_enabled=False)
        response = test_010_reg_agent_post.post(
            f"/v{self.api_version}/agents/{tenant_templ.agent_uuid}", data=json.dumps(data), cert="", verify=False
        )

        self.assertEqual(response.status_code, 200, "Non-successful Registrar agent Add return code!")
        json_response = response.json()

        # Ensure response is well-formed
        self.assertIn("results", json_response, "Malformed response body!")
        self.assertIn("blob", json_response["results"], "Malformed response body!")

        keyblob = json_response["results"]["blob"]
        self.assertIsNotNone(keyblob, "Malformed response body!")
Exemple #15
0
async def execute(revocation):
    json_meta = json.loads(revocation["meta_data"])
    serial = json_meta["cert_serial"]
    if revocation.get("type", None) != "revocation" or serial is None:
        logger.error("Unsupported revocation message: %s" % revocation)

    # load up the ca cert
    secdir = secure_mount.mount()
    ca = ca_util.load_cert_by_path(f"{secdir}/unzipped/cacert.crt")

    # need to find any sa's that were established with that cert serial
    cmd = ("racoonctl", "show-sa", "ipsec")
    output = cmd_exec.run(cmd, raiseOnError=True)["retout"]
    deletelist = set()
    for line in output:
        if not line.startswith(b"\t"):
            cmd = ("racoonctl", "get-cert", "inet", line.strip())
            certder = cmd_exec.run(cmd, raiseOnError=False)["retout"]
            if len(certder) == 0:
                continue

            try:
                certobj = x509.load_der_x509_certificate(
                    data=b"".join(certder),
                    backend=default_backend(),
                )

                # check that CA is the same.
                ca_keyid = ca.extensions.get_extension_for_oid(
                    x509.oid.ExtensionOID.SUBJECT_KEY_IDENTIFIER
                ).value.digest
                cert_authkeyid = certobj.extensions.get_extension_for_oid(
                    x509.oid.ExtensionOID.AUTHORITY_KEY_IDENTIFIER
                ).value.key_identifier
            except (ValueError, x509.extensions.ExtensionNotFound):
                continue

            if ca_keyid != cert_authkeyid:
                continue

            if certobj.serial_number == serial:
                deletelist.add(line.strip())

    for todelete in deletelist:
        logger.info("deleting IPsec sa between %s" % todelete)
        cmd = ("racoonctl", "delete-sa", "isakmp", "inet", todelete)
        cmd_exec.run(cmd)
        tokens = todelete.split()
        cmd = ("racoonctl", "delete-sa", "isakmp", "inet", tokens[1], tokens[0])
        cmd_exec.run(cmd)
Exemple #16
0
    def __init__(self, server_address, RequestHandlerClass, agent_uuid):
        """Constructor overridden to provide ability to pass configuration arguments to the server"""
        secdir = secure_mount.mount()
        keyname = os.path.join(secdir, config.get('cloud_agent',
                                                  'rsa_keyname'))
        certname = os.path.join(secdir, config.get('cloud_agent', 'mtls_cert'))
        # read or generate the key depending on configuration
        if os.path.isfile(keyname):
            # read in private key
            logger.debug("Using existing key in %s", keyname)
            f = open(keyname, "rb")
            rsa_key = crypto.rsa_import_privkey(f.read())
        else:
            logger.debug("key not found, generating a new one")
            rsa_key = crypto.rsa_generate(2048)
            with open(keyname, "wb") as f:
                f.write(crypto.rsa_export_privkey(rsa_key))

        self.rsakey_path = keyname
        self.rsaprivatekey = rsa_key
        self.rsapublickey_exportable = crypto.rsa_export_pubkey(
            self.rsaprivatekey)

        if os.path.isfile(certname):
            logger.debug("Using existing mTLS cert in %s", certname)
            with open(certname, "rb") as f:
                mtls_cert = x509.load_pem_x509_certificate(f.read())
        else:
            logger.debug("No mTLS certificate found generating a new one")
            with open(certname, "wb") as f:
                # By default generate a TLS certificate valid for 5 years
                valid_util = datetime.datetime.utcnow() + datetime.timedelta(
                    days=(360 * 5))
                mtls_cert = crypto.generate_selfsigned_cert(
                    agent_uuid, rsa_key, valid_util)
                f.write(mtls_cert.public_bytes(serialization.Encoding.PEM))

        self.mtls_cert_path = certname
        self.mtls_cert = mtls_cert

        # attempt to get a U value from the TPM NVRAM
        nvram_u = tpm_instance.read_key_nvram()
        if nvram_u is not None:
            logger.info("Existing U loaded from TPM NVRAM")
            self.add_U(nvram_u)
        http.server.HTTPServer.__init__(self, server_address,
                                        RequestHandlerClass)
        self.enc_keyname = config.get('cloud_agent', 'enc_keyname')
        self.agent_uuid = agent_uuid
Exemple #17
0
    def activate_identity(self, keyblob):
        owner_pw = self.get_tpm_metadata('owner_pw')
        keyhandle = self.get_tpm_metadata('aik_handle')

        keyblobFile = None
        secpath = None
        try:
            # write out key blob
            kfd, ktemp = tempfile.mkstemp()
            keyblobFile = open(ktemp, "wb")
            keyblobFile.write(base64.b64decode(keyblob))
            keyblobFile.close()
            os.close(kfd)

            # ok lets write out the key now
            secdir = secure_mount.mount(
            )  # confirm that storage is still securely mounted

            secfd, secpath = tempfile.mkstemp(dir=secdir)

            command = [
                "activateidentity", "-hk", keyhandle, "-pwdo", owner_pw,
                "-pwdk",
                self.get_tpm_metadata('aik_pw'), "-if", keyblobFile.name,
                "-ok", secpath
            ]
            retDict = self.__run(command, outputpaths=secpath)
            fileout = retDict['fileouts'][secpath]
            logger.info("AIK activated.")

            key = base64.b64encode(fileout)
            os.close(secfd)
            os.remove(secpath)

        except Exception as e:
            logger.error("Error decrypting AIK: " + str(e))
            logger.exception(e)
            return None
        finally:
            if keyblobFile is not None:
                os.remove(keyblobFile.name)
            if secpath is not None and os.path.exists(secpath):
                os.remove(secpath)
        return key
Exemple #18
0
def perform_actions(revocation):
    actionlist = []

    # load the actions from inside the keylime module
    actionlisttxt = config.get("cloud_agent", "revocation_actions")
    if actionlisttxt.strip() != "":
        actionlist = actionlisttxt.split(",")
        actionlist = [f"revocation_actions.{i}" % i for i in actionlist]

    # load actions from unzipped
    secdir = secure_mount.mount()
    action_list_path = os.path.join(secdir, "unzipped/action_list")
    if os.path.exists(action_list_path):
        with open(action_list_path, encoding="utf-8") as f:
            actionlisttxt = f.read()
        if actionlisttxt.strip() != "":
            localactions = actionlisttxt.strip().split(",")
            for action in localactions:
                if not action.startswith("local_action_"):
                    logger.warning(
                        "Invalid local action: %s. Must start with local_action_",
                        action)
                else:
                    actionlist.append(action)

            uzpath = os.path.join(secdir, "unzipped")
            if uzpath not in sys.path:
                sys.path.append(uzpath)

    for action in actionlist:
        logger.info("Executing revocation action %s", action)
        try:
            module = importlib.import_module(action)
            execute = getattr(module, "execute")
            loop = asyncio.new_event_loop()
            loop.run_until_complete(execute(revocation))
        except Exception as e:
            logger.warning(
                "Exception during execution of revocation action %s: %s",
                action, e)
Exemple #19
0
def revocation_listener():
    """
    This configures and starts the revocation listener. It is designed to be started in a separate process.
    """

    if config.has_option("cloud_agent", "listen_notifications"):
        if not config.getboolean("cloud_agent", "listen_notifications"):
            return

    # keep old typo "listen_notfications" around for a few versions
    if config.has_option("cloud_agent", "listen_notfications"):
        logger.warning(
            'Option typo "listen_notfications" is deprecated. Please use "listen_notifications" instead.'
        )
        if not config.getboolean("cloud_agent", "listen_notfications"):
            return

    secdir = secure_mount.mount()

    cert_path = config.get("cloud_agent", "revocation_cert")
    if cert_path == "default":
        cert_path = os.path.join(secdir,
                                 "unzipped/RevocationNotifier-cert.crt")
    elif cert_path[0] != "/":
        # if it is a relative, convert to absolute in work_dir
        cert_path = os.path.abspath(os.path.join(config.WORK_DIR, cert_path))

    try:
        while True:
            try:
                revocation_notifier.await_notifications(
                    perform_actions, revocation_cert_path=cert_path)
            except Exception as e:
                logger.exception(e)
                logger.warning(
                    "No connection to revocation server, retrying in 10s...")
                time.sleep(10)
    except (KeyboardInterrupt, SystemExit):
        logger.info("Stopping revocation listener...")
Exemple #20
0
def gencrl(serials, cert, ca_pk):
    request = {
        "certificate": cert,
        "serialNumber": serials,
        "issuingKey": ca_pk,
        "expireTime": ""
    }
    secdir = secure_mount.mount()
    try:
        # need to temporarily write out the private key with no password
        # to tmpfs
        privkey_path = os.path.abspath(f"{secdir}/ca-key.pem")
        with open(privkey_path, 'w', encoding="utf-8") as f:
            f.write(ca_pk)

        cacert_path = os.path.abspath(f"{secdir}/cacert.crt")
        with open(cacert_path, 'w', encoding="utf-8") as f:
            f.write(cert)

        cmdline = f" -ca-key {privkey_path} -ca {cacert_path}"

        start_cfssl(cmdline)
        body = post_cfssl('api/v1/cfssl/gencrl', request)

    finally:
        stop_cfssl()
        # replace with srm
        os.remove(privkey_path)
        os.remove(cacert_path)

    if body['success']:
        retval = base64.b64decode(body['result'])
    else:
        raise Exception(f"Unable to create crl for cert serials {serials}. "
                        f"Error: {body['errors']}")
    return retval
Exemple #21
0
def main():
    for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]:
        if not os.access(ML, os.F_OK):
            logger.warning(
                "Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via \"keylime_tenant\" CLI - will result in agent process dying",
                ML)

    if config.get('cloud_agent', 'agent_uuid') == 'dmidecode':
        if os.getuid() != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but current process is not running as root.')
        cmd = ['which', 'dmidecode']
        ret = cmd_exec.run(cmd, raiseOnError=False)
        if ret['code'] != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but it\'s is not found on the system.')

    # Instanitate TPM class

    instance_tpm = tpm()
    # get params for initialization
    registrar_ip = config.get('cloud_agent', 'registrar_ip')
    registrar_port = config.get('cloud_agent', 'registrar_port')

    # initialize the tmpfs partition to store keys if it isn't already available
    secdir = secure_mount.mount()

    # change dir to working dir
    config.ch_dir(config.WORK_DIR, logger)

    # initialize tpm
    (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init(
        self_activate=False,
        config_pw=config.get('cloud_agent', 'tpm_ownerpassword')
    )  # this tells initialize not to self activate the AIK
    virtual_agent = instance_tpm.is_vtpm()
    # try to get some TPM randomness into the system entropy pool
    instance_tpm.init_system_rand()

    if ekcert is None:
        if virtual_agent:
            ekcert = 'virtual'
        elif instance_tpm.is_emulator():
            ekcert = 'emulator'

    # now we need the UUID
    try:
        agent_uuid = config.get('cloud_agent', 'agent_uuid')
    except configparser.NoOptionError:
        agent_uuid = None
    if agent_uuid == 'openstack':
        agent_uuid = openstack.get_openstack_uuid()
    elif agent_uuid == 'hash_ek':
        agent_uuid = hashlib.sha256(ek_tpm).hexdigest()
    elif agent_uuid == 'generate' or agent_uuid is None:
        agent_uuid = str(uuid.uuid4())
    elif agent_uuid == 'dmidecode':
        cmd = ['dmidecode', '-s', 'system-uuid']
        ret = cmd_exec.run(cmd)
        sys_uuid = ret['retout'].decode('utf-8')
        agent_uuid = sys_uuid.strip()
    elif agent_uuid == 'hostname':
        agent_uuid = socket.getfqdn()
    if config.STUB_VTPM and config.TPM_CANNED_VALUES is not None:
        # Use canned values for stubbing
        jsonIn = config.TPM_CANNED_VALUES
        if "add_vtpm_to_group" in jsonIn:
            # The value we're looking for has been canned!
            agent_uuid = jsonIn['add_vtpm_to_group']['retout']
        else:
            # Our command hasn't been canned!
            raise Exception("Command %s not found in canned json!" %
                            ("add_vtpm_to_group"))

    logger.info("Agent UUID: %s", agent_uuid)

    # register it and get back a blob
    keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port,
                                               agent_uuid, ek_tpm, ekcert,
                                               aik_tpm)

    if keyblob is None:
        instance_tpm.flush_keys()
        raise Exception("Registration failed")

    # get the ephemeral registrar key
    key = instance_tpm.activate_identity(keyblob)

    if key is None:
        instance_tpm.flush_keys()
        raise Exception("Activation failed")

    # tell the registrar server we know the key
    retval = False
    retval = registrar_client.doActivateAgent(registrar_ip, registrar_port,
                                              agent_uuid, key)

    if not retval:
        instance_tpm.flush_keys()
        raise Exception("Registration failed on activate")

    serveraddr = (config.get('cloud_agent', 'cloudagent_ip'),
                  config.getint('cloud_agent', 'cloudagent_port'))
    server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid)
    serverthread = threading.Thread(target=server.serve_forever)

    logger.info("Starting Cloud Agent on %s:%s use <Ctrl-C> to stop",
                serveraddr[0], serveraddr[1])
    serverthread.start()

    # want to listen for revocations?
    if config.getboolean('cloud_agent', 'listen_notfications'):
        cert_path = config.get('cloud_agent', 'revocation_cert')
        if cert_path == "default":
            cert_path = '%s/unzipped/RevocationNotifier-cert.crt' % (secdir)
        elif cert_path[0] != '/':
            # if it is a relative, convert to absolute in work_dir
            cert_path = os.path.abspath('%s/%s' % (config.WORK_DIR, cert_path))

        def perform_actions(revocation):
            actionlist = []

            # load the actions from inside the keylime module
            actionlisttxt = config.get('cloud_agent', 'revocation_actions')
            if actionlisttxt.strip() != "":
                actionlist = actionlisttxt.split(',')
                actionlist = ["revocation_actions.%s" % i for i in actionlist]

            # load actions from unzipped
            if os.path.exists("%s/unzipped/action_list" % secdir):
                with open("%s/unzipped/action_list" % secdir, 'r') as f:
                    actionlisttxt = f.read()
                if actionlisttxt.strip() != "":
                    localactions = actionlisttxt.strip().split(',')
                    for action in localactions:
                        if not action.startswith('local_action_'):
                            logger.warning(
                                "Invalid local action: %s. Must start with local_action_",
                                action)
                        else:
                            actionlist.append(action)

                    uzpath = "%s/unzipped" % secdir
                    if uzpath not in sys.path:
                        sys.path.append(uzpath)

            for action in actionlist:
                logger.info("Executing revocation action %s", action)
                try:
                    module = importlib.import_module(action)
                    execute = getattr(module, 'execute')
                    asyncio.get_event_loop().run_until_complete(
                        execute(revocation))
                except Exception as e:
                    logger.warning(
                        "Exception during execution of revocation action %s: %s",
                        action, e)

        try:
            while True:
                try:
                    revocation_notifier.await_notifications(
                        perform_actions, revocation_cert_path=cert_path)
                except Exception as e:
                    logger.exception(e)
                    logger.warning(
                        "No connection to revocation server, retrying in 10s..."
                    )
                    time.sleep(10)
        except KeyboardInterrupt:
            logger.info("TERM Signal received, shutting down...")
            instance_tpm.flush_keys()
            server.shutdown()
    else:
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            logger.info("TERM Signal received, shutting down...")
            instance_tpm.flush_keys()
            server.shutdown()
Exemple #22
0
def mk_signed_cert(cacert, ca_pk, name, serialnum):
    del serialnum
    csr = {
        "request": {
            "CN":
            name,
            "hosts": [
                name,
            ],
            "key": {
                "algo": "rsa",
                "size": config.getint('ca', 'cert_bits')
            },
            "names": [{
                "C": config.get('ca', 'cert_country'),
                "L": config.get('ca', 'cert_locality'),
                "O": config.get('ca', 'cert_organization'),
                "OU": config.get('ca', 'cert_org_unit'),
                "ST": config.get('ca', 'cert_state')
            }]
        }
    }

    # check CRL distribution point
    disturl = config.get('ca', 'cert_crl_dist')
    if disturl == 'default':
        disturl = f"http://{socket.getfqdn()}:{config.CRL_PORT}/crl.der"

    # set up config for cfssl server
    cfsslconfig = {
        "signing": {
            "default": {
                "usages": [
                    "client auth", "server auth", "key agreement",
                    "key encipherment", "signing", "digital signature",
                    "data encipherment"
                ],
                "expiry":
                "8760h",
                "crl_url":
                disturl,
            }
        }
    }
    secdir = secure_mount.mount()
    try:
        # need to temporarily write out the private key with no password
        # to tmpfs.
        with os.fdopen(
                os.open(f"{secdir}/ca-key.pem", os.O_WRONLY | os.O_CREAT,
                        0o600), 'wb') as f:
            f.write(
                ca_pk.private_bytes(
                    encoding=serialization.Encoding.PEM,
                    format=serialization.PrivateFormat.PKCS8,
                    encryption_algorithm=serialization.NoEncryption(),
                ))

        with open(os.path.join(secdir, 'cfsslconfig.yml'),
                  'w',
                  encoding="utf-8") as f:
            json.dump(cfsslconfig, f)

        with open(f"{secdir}/cacert.crt", 'wb') as f:
            f.write(cacert.public_bytes(serialization.Encoding.PEM))

        cmdline = "-config=%s/cfsslconfig.yml" % secdir

        privkey_path = os.path.abspath(f"{secdir}/ca-key.pem")
        cacert_path = os.path.abspath(f"{secdir}/cacert.crt")

        cmdline += f" -ca-key {privkey_path} -ca {cacert_path}"

        start_cfssl(cmdline)
        body = post_cfssl('api/v1/cfssl/newcert', csr)
    finally:
        stop_cfssl()
        os.remove('%s/ca-key.pem' % secdir)
        os.remove('%s/cfsslconfig.yml' % secdir)
        os.remove('%s/cacert.crt' % secdir)

    if body['success']:
        pk = serialization.load_pem_private_key(
            body['result']['private_key'].encode('utf-8'),
            password=None,
            backend=default_backend(),
        )
        cert = x509.load_pem_x509_certificate(
            data=body['result']['certificate'].encode('utf-8'),
            backend=default_backend(),
        )
        return cert, pk

    raise Exception("Unable to create cert for %s" % name)
def revocation_listener():
    """
    This configures and starts the revocation listener. It is designed to be started in a separate process.
    """

    if config.has_option('cloud_agent', 'listen_notifications'):
        if not config.getboolean('cloud_agent', 'listen_notifications'):
            return

    # keep old typo "listen_notfications" around for a few versions
    if config.has_option('cloud_agent', 'listen_notfications'):
        logger.warning(
            'Option typo "listen_notfications" is deprecated. Please use "listen_notifications" instead.'
        )
        if not config.getboolean('cloud_agent', 'listen_notfications'):
            return

    secdir = secure_mount.mount()

    cert_path = config.get('cloud_agent', 'revocation_cert')
    if cert_path == "default":
        cert_path = os.path.join(secdir,
                                 "unzipped/RevocationNotifier-cert.crt")
    elif cert_path[0] != '/':
        # if it is a relative, convert to absolute in work_dir
        cert_path = os.path.abspath(os.path.join(config.WORK_DIR, cert_path))

    # Callback function handling the revocations
    def perform_actions(revocation):
        actionlist = []

        # load the actions from inside the keylime module
        actionlisttxt = config.get('cloud_agent', 'revocation_actions')
        if actionlisttxt.strip() != "":
            actionlist = actionlisttxt.split(',')
            actionlist = ["revocation_actions.%s" % i for i in actionlist]

        # load actions from unzipped
        action_list_path = os.path.join(secdir, "unzipped/action_list")
        if os.path.exists(action_list_path):
            with open(action_list_path, encoding="utf-8") as f:
                actionlisttxt = f.read()
            if actionlisttxt.strip() != "":
                localactions = actionlisttxt.strip().split(',')
                for action in localactions:
                    if not action.startswith('local_action_'):
                        logger.warning(
                            "Invalid local action: %s. Must start with local_action_",
                            action)
                    else:
                        actionlist.append(action)

                uzpath = "%s/unzipped" % secdir
                if uzpath not in sys.path:
                    sys.path.append(uzpath)

        for action in actionlist:
            logger.info("Executing revocation action %s", action)
            try:
                module = importlib.import_module(action)
                execute = getattr(module, 'execute')
                asyncio.get_event_loop().run_until_complete(
                    execute(revocation))
            except Exception as e:
                logger.warning(
                    "Exception during execution of revocation action %s: %s",
                    action, e)

    try:
        while True:
            try:
                revocation_notifier.await_notifications(
                    perform_actions, revocation_cert_path=cert_path)
            except Exception as e:
                logger.exception(e)
                logger.warning(
                    "No connection to revocation server, retrying in 10s...")
                time.sleep(10)
    except (KeyboardInterrupt, SystemExit):
        logger.info("Stopping revocation listener...")
def main(argv=sys.argv):
    if os.getuid() != 0 and common.REQUIRE_ROOT:
        logger.critical("This process must be run as root.")
        return

    # get params for initialization
    registrar_ip = config.get('general', 'registrar_ip')
    registrar_port = config.get('general', 'registrar_port')

    # initialize the tmpfs partition to store keys if it isn't already available
    secdir = secure_mount.mount()

    # change dir to working dir
    common.ch_dir(common.WORK_DIR, logger)

    #initialize tpm
    (ek, ekcert, aik, ek_tpm, aik_name) = tpm.tpm_init(
        self_activate=False,
        config_pw=config.get('cloud_agent', 'tpm_ownerpassword')
    )  # this tells initialize not to self activate the AIK
    virtual_agent = tpm.is_vtpm()

    # try to get some TPM randomness into the system entropy pool
    tpm.init_system_rand()

    if ekcert is None:
        if virtual_agent:
            ekcert = 'virtual'
        elif tpm.is_emulator():
            ekcert = 'emulator'

    # now we need the UUID
    try:
        agent_uuid = config.get('cloud_agent', 'agent_uuid')
    except configparser.NoOptionError:
        agent_uuid = None
    if agent_uuid == 'openstack':
        agent_uuid = openstack.get_openstack_uuid()
    elif agent_uuid == 'hash_ek':
        agent_uuid = hashlib.sha256(ek).hexdigest()
    elif agent_uuid == 'generate' or agent_uuid is None:
        agent_uuid = str(uuid.uuid4())
    if common.DEVELOP_IN_ECLIPSE:
        agent_uuid = "C432FBB3-D2F1-4A97-9EF7-75BD81C866E9"
    if common.STUB_VTPM and common.TPM_CANNED_VALUES is not None:
        # Use canned values for stubbing
        jsonIn = common.TPM_CANNED_VALUES
        if "add_vtpm_to_group" in jsonIn:
            # The value we're looking for has been canned!
            agent_uuid = jsonIn['add_vtpm_to_group']['retout']
        else:
            # Our command hasn't been canned!
            raise Exception("Command %s not found in canned json!" %
                            ("add_vtpm_to_group"))

    logger.info("Agent UUID: %s" % agent_uuid)

    # register it and get back a blob
    keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port,
                                               agent_uuid, tpm_version, ek,
                                               ekcert, aik, ek_tpm, aik_name)

    if keyblob is None:
        raise Exception("Registration failed")

    # get the ephemeral registrar key
    key = tpm.activate_identity(keyblob)

    if key is None:
        raise Exception("Activation failed")

    # tell the registrar server we know the key
    retval = False
    if virtual_agent:
        deepquote = tpm.create_deep_quote(
            hashlib.sha1(key).hexdigest(), agent_uuid + aik + ek)
        retval = registrar_client.doActivateVirtualAgent(
            registrar_ip, registrar_port, agent_uuid, deepquote)
    else:
        retval = registrar_client.doActivateAgent(registrar_ip, registrar_port,
                                                  agent_uuid, key)

    if not retval:
        raise Exception("Registration failed on activate")

    serveraddr = ('', config.getint('general', 'cloudagent_port'))
    server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid)
    serverthread = threading.Thread(target=server.serve_forever)

    logger.info('Starting Cloud Agent on port %s use <Ctrl-C> to stop' %
                serveraddr[1])
    serverthread.start()

    # want to listen for revocations?
    if config.getboolean('cloud_agent', 'listen_notfications'):
        cert_path = config.get('cloud_agent', 'revocation_cert')
        if cert_path == "default":
            cert_path = '%s/unzipped/RevocationNotifier-cert.crt' % (secdir)
        elif cert_path[0] != '/':
            # if it is a relative, convert to absolute in work_dir
            cert_path = os.path.abspath('%s/%s' % (common.WORK_DIR, cert_path))

        def perform_actions(revocation):
            actionlist = []

            # load the actions from inside the keylime module
            actionlisttxt = config.get('cloud_agent', 'revocation_actions')
            if actionlisttxt.strip() != "":
                actionlist = actionlisttxt.split(',')
                actionlist = ["revocation_actions.%s" % i for i in actionlist]

            # load actions from unzipped
            if os.path.exists("%s/unzipped/action_list" % secdir):
                with open("%s/unzipped/action_list" % secdir, 'r') as f:
                    actionlisttxt = f.read()
                if actionlisttxt.strip() != "":
                    localactions = actionlisttxt.strip().split(',')
                    for action in localactions:
                        if not action.startswith('local_action_'):
                            logger.warning(
                                "invalid local action: %s.  must start with local_action_"
                                % action)
                        else:
                            actionlist.append(action)

                    uzpath = "%s/unzipped" % secdir
                    if uzpath not in sys.path:
                        sys.path.append(uzpath)

            for action in actionlist:
                logger.debug("executing revocation action %s" % action)
                try:
                    module = importlib.import_module(action)
                    execute = getattr(module, 'execute')
                    asyncio.get_event_loop().run_until_complete(
                        execute(revocation))
                except Exception as e:
                    logger.warn(
                        "Exception during execution of revocation action %s: %s"
                        % (action, e))

        try:
            while True:
                try:
                    revocation_notifier.await_notifications(
                        perform_actions, revocation_cert_path=cert_path)
                except Exception as e:
                    logger.exception(e)
                    logger.warn(
                        "No connection to revocation server, retrying in 10s..."
                    )
                    time.sleep(10)
        except KeyboardInterrupt:
            logger.info("TERM Signal received, shutting down...")
            tpm.flush_keys()
            server.shutdown()
    else:
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            logger.info("TERM Signal received, shutting down...")
            tpm.flush_keys()
            server.shutdown()
def main():
    for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]:
        if not os.access(ML, os.F_OK):
            logger.warning(
                "Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via \"keylime_tenant\" CLI - will result in agent process dying",
                ML)

    ima_log_file = None
    if os.path.exists(config.IMA_ML):
        ima_log_file = open(config.IMA_ML, 'r', encoding="utf-8")

    tpm_log_file_data = None
    if os.path.exists(config.MEASUREDBOOT_ML):
        with open(config.MEASUREDBOOT_ML, 'rb') as tpm_log_file:
            tpm_log_file_data = base64.b64encode(tpm_log_file.read())

    if config.get('cloud_agent', 'agent_uuid') == 'dmidecode':
        if os.getuid() != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but current process is not running as root.')
        cmd = ['which', 'dmidecode']
        ret = cmd_exec.run(cmd, raiseOnError=False)
        if ret['code'] != 0:
            raise RuntimeError('agent_uuid is configured to use dmidecode, '
                               'but it\'s is not found on the system.')

    # initialize the tmpfs partition to store keys if it isn't already available
    secdir = secure_mount.mount()

    # Now that operations requiring root privileges are done, drop privileges
    # if 'run_as' is available in the configuration.
    if os.getuid() == 0:
        run_as = config.get('cloud_agent', 'run_as', fallback='')
        if run_as != '':
            user_utils.chown(secdir, run_as)
            user_utils.change_uidgid(run_as)
            logger.info(f"Dropped privileges to {run_as}")
        else:
            logger.warning(
                "Cannot drop privileges since 'run_as' is empty or missing in keylime.conf agent section."
            )

    # Instanitate TPM class

    instance_tpm = tpm()
    # get params for initialization
    registrar_ip = config.get('cloud_agent', 'registrar_ip')
    registrar_port = config.get('cloud_agent', 'registrar_port')

    # get params for the verifier to contact the agent
    contact_ip = os.getenv("KEYLIME_AGENT_CONTACT_IP", None)
    if contact_ip is None and config.has_option('cloud_agent',
                                                'agent_contact_ip'):
        contact_ip = config.get('cloud_agent', 'agent_contact_ip')
    contact_port = os.getenv("KEYLIME_AGENT_CONTACT_PORT", None)
    if contact_port is None and config.has_option('cloud_agent',
                                                  'agent_contact_port'):
        contact_port = config.get('cloud_agent',
                                  'agent_contact_port',
                                  fallback="invalid")

    # change dir to working dir
    fs_util.ch_dir(config.WORK_DIR)

    # set a conservative general umask
    os.umask(0o077)

    # initialize tpm
    (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init(
        self_activate=False,
        config_pw=config.get('cloud_agent', 'tpm_ownerpassword')
    )  # this tells initialize not to self activate the AIK
    virtual_agent = instance_tpm.is_vtpm()

    # Warn if kernel version is <5.10 and another algorithm than SHA1 is used,
    # because otherwise IMA will not work
    kernel_version = tuple(platform.release().split("-")[0].split("."))
    if tuple(map(int, kernel_version)) < (
            5, 10,
            0) and instance_tpm.defaults["hash"] != algorithms.Hash.SHA1:
        logger.warning(
            "IMA attestation only works on kernel versions <5.10 with SHA1 as hash algorithm. "
            "Even if ascii_runtime_measurements shows \"%s\" as the "
            "algorithm, it might be just padding zeros",
            (instance_tpm.defaults["hash"]))

    if ekcert is None:
        if virtual_agent:
            ekcert = 'virtual'
        elif instance_tpm.is_emulator():
            ekcert = 'emulator'

    # now we need the UUID
    try:
        agent_uuid = config.get('cloud_agent', 'agent_uuid')
    except configparser.NoOptionError:
        agent_uuid = None
    if agent_uuid == 'hash_ek':
        ek_pubkey = pubkey_from_tpm2b_public(base64.b64decode(ek_tpm))
        ek_pubkey_pem = ek_pubkey.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo)
        agent_uuid = hashlib.sha256(ek_pubkey_pem).hexdigest()
    elif agent_uuid == 'generate' or agent_uuid is None:
        agent_uuid = str(uuid.uuid4())
    elif agent_uuid == 'dmidecode':
        cmd = ['dmidecode', '-s', 'system-uuid']
        ret = cmd_exec.run(cmd)
        sys_uuid = ret['retout'][0].decode('utf-8')
        agent_uuid = sys_uuid.strip()
        try:
            uuid.UUID(agent_uuid)
        except ValueError as e:
            raise RuntimeError(
                "The UUID returned from dmidecode is invalid: %s" % e)  # pylint: disable=raise-missing-from
    elif agent_uuid == 'hostname':
        agent_uuid = socket.getfqdn()
    elif agent_uuid == 'environment':
        agent_uuid = os.getenv("KEYLIME_AGENT_UUID", None)
        if agent_uuid is None:
            raise RuntimeError(
                "Env variable KEYLIME_AGENT_UUID is empty, but agent_uuid is set to 'environment'"
            )
    elif not validators.valid_uuid(agent_uuid):
        raise RuntimeError("The UUID is not valid")

    if not validators.valid_agent_id(agent_uuid):
        raise RuntimeError(
            "The agent ID set via agent uuid parameter use invalid characters")

    if config.STUB_VTPM and config.TPM_CANNED_VALUES is not None:
        # Use canned values for stubbing
        jsonIn = config.TPM_CANNED_VALUES
        if "add_vtpm_to_group" in jsonIn:
            # The value we're looking for has been canned!
            agent_uuid = jsonIn['add_vtpm_to_group']['retout']
        else:
            # Our command hasn't been canned!
            raise Exception("Command %s not found in canned json!" %
                            ("add_vtpm_to_group"))

    logger.info("Agent UUID: %s", agent_uuid)

    serveraddr = (config.get('cloud_agent', 'cloudagent_ip'),
                  config.getint('cloud_agent', 'cloudagent_port'))

    keylime_ca = config.get('cloud_agent', 'keylime_ca')
    if keylime_ca == "default":
        keylime_ca = os.path.join(config.WORK_DIR, 'cv_ca', 'cacert.crt')

    server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid, contact_ip,
                                  ima_log_file, tpm_log_file_data)
    context = web_util.generate_mtls_context(server.mtls_cert_path,
                                             server.rsakey_path,
                                             keylime_ca,
                                             logger=logger)
    server.socket = context.wrap_socket(server.socket, server_side=True)
    serverthread = threading.Thread(target=server.serve_forever, daemon=True)

    # register it and get back a blob
    mtls_cert = server.mtls_cert.public_bytes(serialization.Encoding.PEM)
    keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port,
                                               agent_uuid, ek_tpm, ekcert,
                                               aik_tpm, mtls_cert, contact_ip,
                                               contact_port)

    if keyblob is None:
        instance_tpm.flush_keys()
        raise Exception("Registration failed")

    # get the ephemeral registrar key
    key = instance_tpm.activate_identity(keyblob)

    if key is None:
        instance_tpm.flush_keys()
        raise Exception("Activation failed")

    # tell the registrar server we know the key
    retval = registrar_client.doActivateAgent(registrar_ip, registrar_port,
                                              agent_uuid, key)

    if not retval:
        instance_tpm.flush_keys()
        raise Exception("Registration failed on activate")

    # Start revocation listener in a new process to not interfere with tornado
    revocation_process = multiprocessing.Process(target=revocation_listener,
                                                 daemon=True)
    revocation_process.start()

    logger.info(
        "Starting Cloud Agent on %s:%s with API version %s. Use <Ctrl-C> to stop",
        serveraddr[0], serveraddr[1], keylime_api_version.current_version())
    serverthread.start()

    def shutdown_handler(*_):
        logger.info("TERM Signal received, shutting down...")
        logger.debug("Stopping revocation notifier...")
        revocation_process.terminate()
        logger.debug("Shutting down HTTP server...")
        server.shutdown()
        server.server_close()
        serverthread.join()
        logger.debug("HTTP server stopped...")
        revocation_process.join()
        logger.debug("Revocation notifier stopped...")
        secure_mount.umount()
        logger.debug("Umounting directories...")
        instance_tpm.flush_keys()
        logger.debug("Flushed keys successfully")
        sys.exit(0)

    signal.signal(signal.SIGTERM, shutdown_handler)
    signal.signal(signal.SIGQUIT, shutdown_handler)
    signal.signal(signal.SIGINT, shutdown_handler)

    # Keep the main thread alive by waiting for the server thread
    serverthread.join()
    def do_POST(self):
        """This method services the POST 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 parameter.  If the uri or parameters are incorrect, a 400 response is returned.
        """
        rest_params = common.get_restful_params(self.path)

        if rest_params is None:
            common.echo_json_response(self, 405,
                                      "Not Implemented: Use /keys/ interface")
            return

        content_length = int(self.headers.get('Content-Length', 0))
        if content_length <= 0:
            logger.warning(
                'POST returning 400 response, expected content in message. url:  '
                + self.path)
            common.echo_json_response(self, 400, "expected content in message")
            return

        post_body = self.rfile.read(content_length)
        json_body = json.loads(post_body)

        b64_encrypted_key = json_body['encrypted_key']
        decrypted_key = crypto.rsa_decrypt(self.server.rsaprivatekey,
                                           base64.b64decode(b64_encrypted_key))

        have_derived_key = False

        if rest_params["keys"] == "ukey":
            self.server.add_U(decrypted_key)
            self.server.auth_tag = json_body['auth_tag']
            self.server.payload = json_body.get('payload', None)
            have_derived_key = self.server.attempt_decryption(self)
        elif rest_params["keys"] == "vkey":
            self.server.add_V(decrypted_key)
            have_derived_key = self.server.attempt_decryption(self)
        else:
            logger.warning('POST returning  response. uri not supported: ' +
                           self.path)
            common.echo_json_response(self, 400, "uri not supported")
            return
        logger.info('POST of %s key returning 200' %
                    (('V', 'U')[rest_params["keys"] == "ukey"]))
        common.echo_json_response(self, 200, "Success")

        # no key yet, then we're done
        if not have_derived_key:
            return

        # woo hoo we have a key
        # ok lets write out the key now
        secdir = secure_mount.mount(
        )  # confirm that storage is still securely mounted

        # clean out the secure dir of any previous info before we extract files
        if os.path.isdir("%s/unzipped" % secdir):
            shutil.rmtree("%s/unzipped" % secdir)

        # write out key file
        f = open(secdir + "/" + self.server.enc_keyname, 'w')
        f.write(base64.b64encode(self.server.K).decode())
        f.close()

        #stow the U value for later
        tpm.write_key_nvram(self.server.final_U)

        # optionally extend a hash of they key and payload into specified PCR
        tomeasure = self.server.K

        # if we have a good key, now attempt to write out the encrypted payload
        dec_path = "%s/%s" % (secdir,
                              config.get('cloud_agent', "dec_payload_file"))
        enc_path = "%s/encrypted_payload" % common.WORK_DIR

        dec_payload = None
        enc_payload = None
        if self.server.payload is not None:
            dec_payload = crypto.decrypt(self.server.payload,
                                         bytes(self.server.K))

            enc_payload = self.server.payload
        elif os.path.exists(enc_path):
            # if no payload provided, try to decrypt one from a previous run stored in encrypted_payload
            with open(enc_path, 'rb') as f:
                enc_payload = f.read()
            try:
                dec_payload = crypto.decrypt(enc_payload, self.server.K)
                logger.info("Decrypted previous payload in %s to %s" %
                            (enc_path, dec_path))
            except Exception as e:
                logger.warning(
                    "Unable to decrypt previous payload %s with derived key: %s"
                    % (enc_path, e))
                os.remove(enc_path)
                enc_payload = None

        # also write out encrypted payload to be decrytped next time
        if enc_payload is not None:
            with open(enc_path, 'wb') as f:
                f.write(self.server.payload.encode('utf-8'))

        # deal with payload
        payload_thread = None
        if dec_payload is not None:
            tomeasure = tomeasure + dec_payload
            # see if payload is a zip
            zfio = io.BytesIO(dec_payload)
            if config.getboolean(
                    'cloud_agent',
                    'extract_payload_zip') and zipfile.is_zipfile(zfio):
                logger.info("Decrypting and unzipping payload to %s/unzipped" %
                            secdir)
                with zipfile.ZipFile(zfio, 'r') as f:
                    f.extractall('%s/unzipped' % secdir)

                # run an included script if one has been provided
                initscript = config.get('cloud_agent', 'payload_script')
                if initscript is not "":

                    def initthread():
                        import subprocess
                        env = os.environ.copy()
                        env['AGENT_UUID'] = self.server.agent_uuid
                        proc = subprocess.Popen(["/bin/bash", initscript],
                                                env=env,
                                                shell=False,
                                                cwd='%s/unzipped' % secdir,
                                                stdout=subprocess.PIPE,
                                                stderr=subprocess.STDOUT)
                        while True:
                            line = proc.stdout.readline()
                            if line == '' and proc.poll() is not None:
                                break
                            if line:
                                logger.debug("init-output: %s" % line.strip())
                        # should be a no-op as poll already told us it's done
                        proc.wait()

                    if not os.path.exists("%s/unzipped/%s" %
                                          (secdir, initscript)):
                        logger.info(
                            "No payload script %s found in %s/unzipped" %
                            (initscript, secdir))
                    else:
                        logger.info(
                            "Executing payload script: %s/unzipped/%s" %
                            (secdir, initscript))
                        payload_thread = threading.Thread(target=initthread)
            else:
                logger.info("Decrypting payload to %s" % dec_path)
                with open(dec_path, 'wb') as f:
                    f.write(dec_payload)
            zfio.close()

        # now extend a measurement of the payload and key if there was one
        pcr = config.getint('cloud_agent', 'measure_payload_pcr')
        if pcr > 0 and pcr < 24:
            logger.info("extending measurement of payload into PCR %s" % pcr)
            measured = tpm.hashdigest(tomeasure)
            tpm.extendPCR(pcr, measured)

        if payload_thread is not None:
            payload_thread.start()

        return
Exemple #27
0
def main():
    for ML in [config.MEASUREDBOOT_ML, config.IMA_ML]:
        if not os.access(ML, os.F_OK):
            logger.warning(
                'Measurement list path %s not accessible by agent. Any attempt to instruct it to access this path - via "keylime_tenant" CLI - will result in agent process dying',
                ML,
            )

    ima_log_file = None
    if os.path.exists(config.IMA_ML):
        ima_log_file = open(config.IMA_ML, "r", encoding="utf-8")  # pylint: disable=consider-using-with

    tpm_log_file_data = None
    if os.path.exists(config.MEASUREDBOOT_ML):
        with open(config.MEASUREDBOOT_ML, "rb") as tpm_log_file:
            tpm_log_file_data = base64.b64encode(tpm_log_file.read())

    if config.get("cloud_agent", "agent_uuid") == "dmidecode":
        if os.getuid() != 0:
            raise RuntimeError(
                "agent_uuid is configured to use dmidecode, but current process is not running as root."
            )
        cmd = ["which", "dmidecode"]
        ret = cmd_exec.run(cmd, raiseOnError=False)
        if ret["code"] != 0:
            raise RuntimeError(
                "agent_uuid is configured to use dmidecode, but it's is not found on the system."
            )

    # initialize the tmpfs partition to store keys if it isn't already available
    secdir = secure_mount.mount()

    # Now that operations requiring root privileges are done, drop privileges
    # if 'run_as' is available in the configuration.
    if os.getuid() == 0:
        run_as = config.get("cloud_agent", "run_as", fallback="")
        if run_as != "":
            user_utils.chown(secdir, run_as)
            user_utils.change_uidgid(run_as)
            logger.info("Dropped privileges to %s", run_as)
        else:
            logger.warning(
                "Cannot drop privileges since 'run_as' is empty or missing in keylime.conf agent section."
            )

    # Instanitate TPM class

    instance_tpm = tpm()
    # get params for initialization
    registrar_ip = config.get("cloud_agent", "registrar_ip")
    registrar_port = config.get("cloud_agent", "registrar_port")

    # get params for the verifier to contact the agent
    contact_ip = os.getenv("KEYLIME_AGENT_CONTACT_IP", None)
    if contact_ip is None and config.has_option("cloud_agent",
                                                "agent_contact_ip"):
        contact_ip = config.get("cloud_agent", "agent_contact_ip")
    contact_port = os.getenv("KEYLIME_AGENT_CONTACT_PORT", None)
    if contact_port is None and config.has_option("cloud_agent",
                                                  "agent_contact_port"):
        contact_port = config.get("cloud_agent",
                                  "agent_contact_port",
                                  fallback="invalid")

    # change dir to working dir
    fs_util.ch_dir(config.WORK_DIR)

    # set a conservative general umask
    os.umask(0o077)

    # initialize tpm
    (ekcert, ek_tpm, aik_tpm) = instance_tpm.tpm_init(
        self_activate=False,
        config_pw=config.get("cloud_agent", "tpm_ownerpassword")
    )  # this tells initialize not to self activate the AIK

    # Warn if kernel version is <5.10 and another algorithm than SHA1 is used,
    # because otherwise IMA will not work
    kernel_version = tuple(platform.release().split("-")[0].split("."))
    if tuple(map(int, kernel_version)) < (
            5, 10,
            0) and instance_tpm.defaults["hash"] != algorithms.Hash.SHA1:
        logger.warning(
            "IMA attestation only works on kernel versions <5.10 with SHA1 as hash algorithm. "
            'Even if ascii_runtime_measurements shows "%s" as the '
            "algorithm, it might be just padding zeros",
            (instance_tpm.defaults["hash"]),
        )

    if ekcert is None and instance_tpm.is_emulator():
        ekcert = "emulator"

    # now we need the UUID
    try:
        agent_uuid = config.get("cloud_agent", "agent_uuid")
    except configparser.NoOptionError:
        agent_uuid = None
    if agent_uuid == "hash_ek":
        ek_pubkey = pubkey_from_tpm2b_public(base64.b64decode(ek_tpm))
        ek_pubkey_pem = ek_pubkey.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo)
        agent_uuid = hashlib.sha256(ek_pubkey_pem).hexdigest()
    elif agent_uuid == "generate" or agent_uuid is None:
        agent_uuid = str(uuid.uuid4())
    elif agent_uuid == "dmidecode":
        cmd = ["dmidecode", "-s", "system-uuid"]
        ret = cmd_exec.run(cmd)
        sys_uuid = ret["retout"][0].decode("utf-8")
        agent_uuid = sys_uuid.strip()
        try:
            uuid.UUID(agent_uuid)
        except ValueError as e:
            raise RuntimeError(  # pylint: disable=raise-missing-from
                f"The UUID returned from dmidecode is invalid: {str(e)}")
    elif agent_uuid == "hostname":
        agent_uuid = socket.getfqdn()
    elif agent_uuid == "environment":
        agent_uuid = os.getenv("KEYLIME_AGENT_UUID", None)
        if agent_uuid is None:
            raise RuntimeError(
                "Env variable KEYLIME_AGENT_UUID is empty, but agent_uuid is set to 'environment'"
            )
    elif not validators.valid_uuid(agent_uuid):
        raise RuntimeError("The UUID is not valid")

    if not validators.valid_agent_id(agent_uuid):
        raise RuntimeError(
            "The agent ID set via agent uuid parameter use invalid characters")

    logger.info("Agent UUID: %s", agent_uuid)

    serveraddr = (config.get("cloud_agent", "cloudagent_ip"),
                  config.getint("cloud_agent", "cloudagent_port"))

    keylime_ca = config.get("cloud_agent", "keylime_ca")
    if keylime_ca == "default":
        keylime_ca = os.path.join(config.WORK_DIR, "cv_ca", "cacert.crt")

    server = CloudAgentHTTPServer(serveraddr, Handler, agent_uuid, contact_ip,
                                  ima_log_file, tpm_log_file_data)
    if server.mtls_cert_enabled:
        context = web_util.generate_mtls_context(server.mtls_cert_path,
                                                 server.rsakey_path,
                                                 keylime_ca,
                                                 logger=logger)
        server.socket = context.wrap_socket(server.socket, server_side=True)
    else:
        if (not config.getboolean(
                "cloud_agent", "enable_insecure_payload", fallback=False)
                and config.get("cloud_agent", "payload_script") != ""):
            raise RuntimeError(
                "agent mTLS is disabled, while a tenant can instruct the agent to execute code on the node. "
                'In order to allow the running of the agent, "enable_insecure_payload" has to be set to "True"'
            )

    serverthread = threading.Thread(target=server.serve_forever, daemon=True)

    # register it and get back a blob
    mtls_cert = "disabled"
    if server.mtls_cert:
        mtls_cert = server.mtls_cert.public_bytes(serialization.Encoding.PEM)

    keyblob = registrar_client.doRegisterAgent(registrar_ip, registrar_port,
                                               agent_uuid, ek_tpm, ekcert,
                                               aik_tpm, mtls_cert, contact_ip,
                                               contact_port)

    if keyblob is None:
        instance_tpm.flush_keys()
        raise Exception("Registration failed")

    # get the ephemeral registrar key
    key = instance_tpm.activate_identity(keyblob)

    if key is None:
        instance_tpm.flush_keys()
        raise Exception("Activation failed")

    # tell the registrar server we know the key
    retval = registrar_client.doActivateAgent(registrar_ip, registrar_port,
                                              agent_uuid, key)

    if not retval:
        instance_tpm.flush_keys()
        raise Exception("Registration failed on activate")

    # Start revocation listener in a new process to not interfere with tornado
    revocation_process = multiprocessing.Process(target=revocation_listener,
                                                 daemon=True)
    revocation_process.start()

    logger.info(
        "Starting Cloud Agent on %s:%s with API version %s. Use <Ctrl-C> to stop",
        serveraddr[0],
        serveraddr[1],
        keylime_api_version.current_version(),
    )
    serverthread.start()

    def shutdown_handler(*_):
        logger.info("TERM Signal received, shutting down...")
        logger.debug("Stopping revocation notifier...")
        revocation_process.terminate()
        logger.debug("Shutting down HTTP server...")
        server.shutdown()
        server.server_close()
        serverthread.join()
        logger.debug("HTTP server stopped...")
        revocation_process.join()
        logger.debug("Revocation notifier stopped...")
        secure_mount.umount()
        logger.debug("Umounting directories...")
        instance_tpm.flush_keys()
        logger.debug("Flushed keys successfully")
        sys.exit(0)

    signal.signal(signal.SIGTERM, shutdown_handler)
    signal.signal(signal.SIGQUIT, shutdown_handler)
    signal.signal(signal.SIGINT, shutdown_handler)

    # Keep the main thread alive by waiting for the server thread
    serverthread.join()
Exemple #28
0
    def __init__(self, server_address, RequestHandlerClass, agent_uuid,
                 contact_ip, ima_log_file, tpm_log_file_data):
        """Constructor overridden to provide ability to pass configuration arguments to the server"""
        # Find the locations for the U/V transport and mTLS key and certificate.
        # They are either relative to secdir (/var/lib/keylime/secure) or absolute paths.
        secdir = secure_mount.mount()
        keyname = config.get("cloud_agent", "rsa_keyname")
        if not os.path.isabs(keyname):
            keyname = os.path.join(secdir, keyname)

        # read or generate the key depending on configuration
        if os.path.isfile(keyname):
            # read in private key
            logger.info("Using existing key in %s", keyname)
            with open(keyname, "rb") as f:
                rsa_key = crypto.rsa_import_privkey(f.read())
        else:
            logger.info(
                "Key for U/V transport and mTLS certificate not found, generating a new one"
            )
            rsa_key = crypto.rsa_generate(2048)
            with open(keyname, "wb") as f:
                f.write(crypto.rsa_export_privkey(rsa_key))

        self.rsakey_path = keyname
        self.rsaprivatekey = rsa_key
        self.rsapublickey_exportable = crypto.rsa_export_pubkey(
            self.rsaprivatekey)

        self.mtls_cert_enabled = config.getboolean("cloud_agent",
                                                   "mtls_cert_enabled",
                                                   fallback=False)
        if self.mtls_cert_enabled:
            certname = config.get("cloud_agent", "mtls_cert")

            if not os.path.isabs(certname):
                certname = os.path.join(secdir, certname)

            if os.path.isfile(certname):
                logger.info("Using existing mTLS cert in %s", certname)
                with open(certname, "rb") as f:
                    mtls_cert = x509.load_pem_x509_certificate(
                        f.read(), backend=default_backend())
            else:
                logger.info("No mTLS certificate found, generating a new one")
                agent_ips = [server_address[0]]
                if contact_ip is not None:
                    agent_ips.append(contact_ip)
                with open(certname, "wb") as f:
                    # By default generate a TLS certificate valid for 5 years
                    valid_util = datetime.datetime.utcnow(
                    ) + datetime.timedelta(days=(360 * 5))
                    mtls_cert = crypto.generate_selfsigned_cert(
                        agent_uuid, rsa_key, valid_util, agent_ips)
                    f.write(mtls_cert.public_bytes(serialization.Encoding.PEM))

            self.mtls_cert_path = certname
            self.mtls_cert = mtls_cert
        else:
            self.mtls_cert_path = None
            self.mtls_cert = None
            logger.info(
                "WARNING: mTLS disabled, Tenant and Verifier will reach out to agent via HTTP"
            )

        self.revocation_cert_path = config.get("cloud_agent",
                                               "revocation_cert")
        if self.revocation_cert_path == "default":
            self.revocation_cert_path = os.path.join(
                secdir, "unzipped/RevocationNotifier-cert.crt")
        elif self.revocation_cert_path[0] != "/":
            # if it is a relative, convert to absolute in work_dir
            self.revocation_cert_path = os.path.abspath(
                os.path.join(config.WORK_DIR, self.revocation_cert_path))

        # attempt to get a U value from the TPM NVRAM
        nvram_u = tpm_instance.read_key_nvram()
        if nvram_u is not None:
            logger.info("Existing U loaded from TPM NVRAM")
            self.add_U(nvram_u)
        http.server.HTTPServer.__init__(self, server_address,
                                        RequestHandlerClass)
        self.enc_keyname = config.get("cloud_agent", "enc_keyname")
        self.agent_uuid = agent_uuid
        self.ima_log_file = ima_log_file
        self.tpm_log_file_data = tpm_log_file_data
Exemple #29
0
    def do_POST(self):
        """This method services the POST 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 parameter.  If the uri or parameters are incorrect, a 400 response is returned.
        """
        rest_params = web_util.get_restful_params(self.path)

        if rest_params is None:
            web_util.echo_json_response(
                self, 405,
                "Not Implemented: Use /keys/ or /notifications/ interface")
            return

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

        content_length = int(self.headers.get("Content-Length", 0))
        if content_length <= 0:
            logger.warning(
                "POST returning 400 response, expected content in message. url: %s",
                self.path)
            web_util.echo_json_response(self, 400,
                                        "expected content in message")
            return

        post_body = self.rfile.read(content_length)
        try:
            json_body = json.loads(post_body)
        except Exception as e:
            logger.warning(
                "POST returning 400 response, could not parse body data: %s",
                e)
            web_util.echo_json_response(self, 400, "content is invalid")
            return

        if "notifications" in rest_params:
            if rest_params["notifications"] == "revocation":
                revocation_notifier.process_revocation(
                    json_body,
                    perform_actions,
                    cert_path=self.server.revocation_cert_path)
                web_util.echo_json_response(self, 200, "Success")
            else:
                web_util.echo_json_response(
                    self, 400, "Only /notifications/revocation is supported")
            return

        if rest_params.get("keys", None) not in ["ukey", "vkey"]:
            web_util.echo_json_response(
                self, 400, "Only /keys/ukey or /keys/vkey are supported")
            return

        try:
            b64_encrypted_key = json_body["encrypted_key"]
            decrypted_key = crypto.rsa_decrypt(
                self.server.rsaprivatekey, base64.b64decode(b64_encrypted_key))
        except (ValueError, KeyError, TypeError) as e:
            logger.warning(
                "POST returning 400 response, could not parse body data: %s",
                e)
            web_util.echo_json_response(self, 400, "content is invalid")
            return

        have_derived_key = False

        if rest_params["keys"] == "ukey":
            if "auth_tag" not in json_body:
                logger.warning(
                    "POST returning 400 response, U key provided without an auth_tag"
                )
                web_util.echo_json_response(self, 400, "auth_tag is missing")
                return
            self.server.add_U(decrypted_key)
            self.server.auth_tag = json_body["auth_tag"]
            self.server.payload = json_body.get("payload", None)
            have_derived_key = self.server.attempt_decryption()
        elif rest_params["keys"] == "vkey":
            self.server.add_V(decrypted_key)
            have_derived_key = self.server.attempt_decryption()
        else:
            logger.warning("POST returning  response. uri not supported: %s",
                           self.path)
            web_util.echo_json_response(self, 400, "uri not supported")
            return
        logger.info("POST of %s key returning 200",
                    ("V", "U")[rest_params["keys"] == "ukey"])
        web_util.echo_json_response(self, 200, "Success")

        # no key yet, then we're done
        if not have_derived_key:
            return

        # woo hoo we have a key
        # ok lets write out the key now
        secdir = secure_mount.mount(
        )  # confirm that storage is still securely mounted

        # clean out the secure dir of any previous info before we extract files
        if os.path.isdir(os.path.join(secdir, "unzipped")):
            shutil.rmtree(os.path.join(secdir, "unzipped"))

        # write out key file
        with open(os.path.join(secdir, self.server.enc_keyname),
                  "w",
                  encoding="utf-8") as f:
            f.write(base64.b64encode(self.server.K).decode())

        # stow the U value for later
        tpm_instance.write_key_nvram(self.server.final_U)

        # optionally extend a hash of they key and payload into specified PCR
        tomeasure = self.server.K

        # if we have a good key, now attempt to write out the encrypted payload
        dec_path = os.path.join(secdir,
                                config.get("cloud_agent", "dec_payload_file"))
        enc_path = os.path.join(config.WORK_DIR, "encrypted_payload")

        dec_payload = None
        enc_payload = None
        if self.server.payload is not None:
            if not self.server.mtls_cert_enabled and not config.getboolean(
                    "cloud_agent", "enable_insecure_payload", fallback=False):
                logger.warning(
                    'agent mTLS is disabled, and unless "enable_insecure_payload" is set to "True", payloads cannot be deployed'
                )
                enc_payload = None
            else:
                dec_payload = crypto.decrypt(self.server.payload,
                                             bytes(self.server.K))
                enc_payload = self.server.payload

        elif os.path.exists(enc_path):
            # if no payload provided, try to decrypt one from a previous run stored in encrypted_payload
            with open(enc_path, "rb") as f:
                enc_payload = f.read()
            try:
                dec_payload = crypto.decrypt(enc_payload, self.server.K)
                logger.info("Decrypted previous payload in %s to %s", enc_path,
                            dec_path)
            except Exception as e:
                logger.warning(
                    "Unable to decrypt previous payload %s with derived key: %s",
                    enc_path, e)
                os.remove(enc_path)
                enc_payload = None

        # also write out encrypted payload to be decrytped next time
        if enc_payload is not None:
            with open(enc_path, "wb") as f:
                f.write(self.server.payload.encode("utf-8"))

        # deal with payload
        payload_thread = None
        if dec_payload is not None:
            tomeasure = tomeasure + dec_payload
            # see if payload is a zip
            zfio = io.BytesIO(dec_payload)
            if config.getboolean(
                    "cloud_agent",
                    "extract_payload_zip") and zipfile.is_zipfile(zfio):
                logger.info("Decrypting and unzipping payload to %s/unzipped",
                            secdir)
                with zipfile.ZipFile(zfio, "r") as f:
                    f.extractall(os.path.join(secdir, "unzipped"))

                # run an included script if one has been provided
                initscript = config.get("cloud_agent", "payload_script")
                if initscript != "":

                    def initthread():
                        env = os.environ.copy()
                        env["AGENT_UUID"] = self.server.agent_uuid
                        with subprocess.Popen(
                            ["/bin/bash", initscript],
                                env=env,
                                shell=False,
                                cwd=os.path.join(secdir, "unzipped"),
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT,
                        ) as proc:
                            for line in iter(proc.stdout.readline, b""):
                                logger.debug("init-output: %s", line.strip())
                            # should be a no-op as poll already told us it's done
                            proc.wait()

                    if not os.path.exists(
                            os.path.join(secdir, "unzipped", initscript)):
                        logger.info(
                            "No payload script %s found in %s/unzipped",
                            initscript, secdir)
                    else:
                        logger.info("Executing payload script: %s/unzipped/%s",
                                    secdir, initscript)
                        payload_thread = threading.Thread(target=initthread,
                                                          daemon=True)
            else:
                logger.info("Decrypting payload to %s", dec_path)
                with open(dec_path, "wb") as f:
                    f.write(dec_payload)
            zfio.close()

        # now extend a measurement of the payload and key if there was one
        pcr = config.getint("cloud_agent", "measure_payload_pcr")
        if 0 < pcr < 24:
            logger.info("extending measurement of payload into PCR %s", pcr)
            measured = tpm_instance.hashdigest(tomeasure)
            tpm_instance.extendPCR(pcr, measured)

        if payload_thread is not None:
            payload_thread.start()

        return
Exemple #30
0
def mk_signed_cert(cacert,ca_pk,name,serialnum):
    csr = {"request": {
            "CN": name,
            "hosts": [
            name,
            ],
           "key": {
               "algo": "rsa",
               "size": config.getint('ca','cert_bits')
               },
           "names": [
               {
                   "C": config.get('ca','cert_country'),
                   "L": config.get('ca','cert_locality'),
                   "O": config.get('ca','cert_organization'),
                   "OU": config.get('ca','cert_org_unit'),
                   "ST": config.get('ca','cert_state')
                   }
                     ]
            }
           }

    # check CRL distribution point
    disturl = config.get('ca','cert_crl_dist')
    if disturl == 'default':
        disturl = "http://%s:%s/crl.der"%(socket.getfqdn(),common.CRL_PORT)

    # set up config for cfssl server
    cfsslconfig  = {
        "signing": {
            "default": {
                "usages": ["client auth","server auth","key agreement","key encipherment","signing","digital signature","data encipherment"],
                "expiry": "8760h",
                "crl_url": disturl,
            }
        }
    }
    secdir = secure_mount.mount()
    try:
        # need to temporarily write out the private key with no password
        # to tmpfs
        ca_pk.save_key('%s/ca-key.pem'%secdir, None)
        with open('%s/cfsslconfig.yml'%secdir,'w') as f:
            json.dump(cfsslconfig, f)

        cmdline = "-config=%s/cfsslconfig.yml"%secdir

        priv_key = os.path.abspath("%s/ca-key.pem"%secdir)
        cmdline += " -ca-key %s -ca cacert.crt"%(priv_key)

        start_cfssl(cmdline)
        body = post_cfssl('api/v1/cfssl/newcert',csr)
    finally:
        stop_cfssl()
        os.remove('%s/ca-key.pem'%secdir)
        os.remove('%s/cfsslconfig.yml'%secdir)

    if body['success']:
        pk = EVP.load_key_string(body['result']['private_key'].encode('utf-8'))
        cert = X509.load_cert_string(body['result']['certificate'].encode("utf-8"))
        return cert, pk
    else:
        raise Exception("Unable to create cert for %s"%name)