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']}") # Read command-line path string allowlist al_data = None if "allowlist" in args and args["allowlist"] 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)) 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) # 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.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')))
def init_mtls(section='cloud_verifier', generatedir='cv_ca'): if not config.getboolean('general', "enable_tls"): logger.warning( "TLS is currently disabled, keys will be sent in the clear! Should only be used for testing." ) return None logger.info("Setting up TLS...") my_cert = config.get(section, 'my_cert') ca_cert = config.get(section, 'ca_cert') my_priv_key = config.get(section, 'private_key') my_key_pw = config.get(section, 'private_key_pw') tls_dir = config.get(section, 'tls_dir') if tls_dir == 'generate': if my_cert != 'default' or my_priv_key != 'default' or ca_cert != 'default': raise Exception( "To use tls_dir=generate, options ca_cert, my_cert, and private_key must all be set to 'default'" ) if generatedir[0] != '/': generatedir = os.path.abspath('%s/%s' % (common.WORK_DIR, generatedir)) tls_dir = generatedir ca_path = "%s/cacert.crt" % (tls_dir) if os.path.exists(ca_path): logger.info( "Existing CA certificate found in %s, not generating a new one" % (tls_dir)) else: logger.info( "Generating a new CA in %s and a client certificate for connecting" % tls_dir) logger.info("use keylime_ca -d %s to manage this CA" % tls_dir) if not os.path.exists(tls_dir): os.makedirs(tls_dir, 0o700) if my_key_pw == 'default': logger.warning( "CAUTION: using default password for CA, please set private_key_pw to a strong password" ) ca_util.setpassword(my_key_pw) ca_util.cmd_init(tls_dir) ca_util.cmd_mkcert(tls_dir, socket.gethostname()) ca_util.cmd_mkcert(tls_dir, 'client') if tls_dir == 'CV': if section != 'registrar': raise Exception( "You only use the CV option to tls_dir for the registrar not %s" % section) tls_dir = os.path.abspath('%s/%s' % (common.WORK_DIR, 'cv_ca')) if not os.path.exists("%s/cacert.crt" % (tls_dir)): raise Exception( "It appears that the verifier has not yet created a CA and certificates, please run the verifier first" ) # if it is relative path, convert to absolute in WORK_DIR if tls_dir[0] != '/': tls_dir = os.path.abspath('%s/%s' % (common.WORK_DIR, tls_dir)) if ca_cert == 'default': ca_path = "%s/cacert.crt" % (tls_dir) else: ca_path = "%s/%s" % (tls_dir, ca_cert) if my_cert == 'default': my_cert = "%s/%s-cert.crt" % (tls_dir, socket.gethostname()) else: my_cert = "%s/%s" % (tls_dir, my_cert) if my_priv_key == 'default': my_priv_key = "%s/%s-private.pem" % (tls_dir, socket.gethostname()) else: my_priv_key = "%s/%s" % (tls_dir, my_priv_key) context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) context.load_verify_locations(cafile=ca_path) context.load_cert_chain(certfile=my_cert, keyfile=my_priv_key, password=my_key_pw) context.verify_mode = ssl.CERT_REQUIRED return context
def main(argv=sys.argv): parser = argparse.ArgumentParser(argv[0]) parser.add_argument('-c', '--command',action='store',dest='command',default='add',help="valid commands are add,delete,update,status,list,reactivate,regdelete. defaults to add") parser.add_argument('-t', '--targethost',action='store',dest='agent_ip',help="the IP address of the host to provision") parser.add_argument('-tp', '--targetport',action='store',dest='agent_port',help="the Port of the host to provision") parser.add_argument('--cv_targethost',action='store',default=None,dest='cv_agent_ip',help='the IP address of the host to provision that the verifier will use (optional). Use only if different than argument to option -t/--targethost') parser.add_argument('-v', '--cv',action='store',dest='verifier_ip',help="the IP address of the cloud verifier") parser.add_argument('-u', '--uuid',action='store',dest='agent_uuid',help="UUID for the agent to provision") parser.add_argument('-f', '--file', action='store',default=None,help='Deliver the specified plaintext to the provisioned agent') parser.add_argument('--cert',action='store',dest='ca_dir',default=None,help='Create and deliver a certificate using a CA created by ca-util. Pass in the CA directory or use "default" to use the standard dir') parser.add_argument('-k', '--key',action='store',dest='keyfile',help='an intermedia key file produced by user_data_encrypt') parser.add_argument('-p', '--payload', action='store',default=None,help='Specify the encrypted payload to deliver with encrypted keys specified by -k') parser.add_argument('--include',action='store',dest='incl_dir',default=None,help="Include additional files in provided directory in certificate zip file. Must be specified with --cert") parser.add_argument('--whitelist',action='store',dest='ima_whitelist',default=None,help="Specify the location of an IMA whitelist") parser.add_argument('--exclude',action='store',dest='ima_exclude',default=None,help="Specify the location of an IMA exclude list") parser.add_argument('--tpm_policy',action='store',dest='tpm_policy',default=None,help="Specify a TPM policy in JSON format. e.g., {\"15\":\"0000000000000000000000000000000000000000\"}") parser.add_argument('--vtpm_policy',action='store',dest='vtpm_policy',default=None,help="Specify a vTPM policy in JSON format") parser.add_argument('--verify',action='store_true',default=False,help='Block on cryptographically checked key derivation confirmation from the agent once it has been provisioned') if common.DEVELOP_IN_ECLIPSE and len(argv)==1: ca_util.setpassword('default') #tmp = ['-c','add','-t','127.0.0.1','-v', '127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9','-p','content_payload.txt','-k','content_keys.txt'] #tmp = ['-c','add','-t','127.0.0.1','-v','127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9','-f','tenant.py'] tmp = ['-c','add','-t','127.0.0.1','-v','127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9','--cert','ca/'] #tmp = ['-c','delete','-t','127.0.0.1','-v','127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9'] #tmp = ['-c','reactivate','-t','127.0.0.1','-v','127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9'] #tmp = ['-c','list','-v', '127.0.0.1','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9'] #tmp = ['-c','regdelete','-u','C432FBB3-D2F1-4A97-9EF7-75BD81C866E9'] else: tmp = argv[1:] args = parser.parse_args(tmp) mytenant = Tenant() if args.command not in ['list','regdelete'] and args.agent_ip is None: raise UserError("-t/--targethost is required for command %s"%args.command) if args.agent_uuid is not None: mytenant.agent_uuid = args.agent_uuid # if the uuid is actually a public key, then hash it if mytenant.agent_uuid.startswith('-----BEGIN PUBLIC KEY-----'): mytenant.agent_uuid = hashlib.sha256(mytenant.agent_uuid).hexdigest() else: logger.warning("Using default UUID D432FBB3-D2F1-4A97-9EF7-75BD81C00000") mytenant.agent_uuid = "D432FBB3-D2F1-4A97-9EF7-75BD81C00000" if common.STUB_VTPM and common.TPM_CANNED_VALUES is not None: # Use canned values for agent UUID jsonIn = common.TPM_CANNED_VALUES if "add_vtpm_to_group" in jsonIn: mytenant.agent_uuid = jsonIn['add_vtpm_to_group']['retout'] else: # Our command hasn't been canned! raise UserError("Command %s not found in canned JSON!"%("add_vtpm_to_group")) if args.verifier_ip is not None: mytenant.cloudverifier_ip = args.verifier_ip if args.command=='add': mytenant.init_add(vars(args)) mytenant.preloop() mytenant.do_cv() mytenant.do_quote() if args.verify: mytenant.do_verify() if common.DEVELOP_IN_ECLIPSE: time.sleep(2) mytenant.do_cvstatus() time.sleep(1) #invalidate it eventually logger.debug("invalidating PCR 15, forcing revocation") tpm = tpm_obj.getTPM(need_hw_tpm=True) tpm.extendPCR(15, tpm.hashdigest(b"garbage")) time.sleep(5) logger.debug("Deleting agent from verifier") mytenant.do_cvdelete() elif args.command=='update': mytenant.init_add(vars(args)) mytenant.do_cvdelete() mytenant.preloop() mytenant.do_cv() mytenant.do_quote() if args.verify: mytenant.do_verify() elif args.command=='delete': mytenant.do_cvdelete() elif args.command=='status': mytenant.do_cvstatus() elif args.command=='list': mytenant.do_cvstatus(listing=True) elif args.command=='reactivate': mytenant.do_cvreactivate() elif args.command=='regdelete': mytenant.do_regdelete() else: raise UserError("Invalid command specified: %s"%(args.command))
def init_mtls(section='cloud_verifier', generatedir='cv_ca', logger=None): """ Generates mTLS SSLContext for either the cloud verifier or the registrar. """ if not config.getboolean('general', "enable_tls"): logger.warning( "Warning: TLS is currently disabled, keys will be sent in the clear! This should only be used for testing.") return None if logger: logger.info("Setting up TLS...") my_cert = config.get(section, 'my_cert') ca_cert = config.get(section, 'ca_cert') my_priv_key = config.get(section, 'private_key') my_key_pw = config.get(section, 'private_key_pw') tls_dir = config.get(section, 'tls_dir') if tls_dir == 'generate': if my_cert != 'default' or my_priv_key != 'default' or ca_cert != 'default': raise Exception( "To use tls_dir=generate, options ca_cert, my_cert, and private_key must all be set to 'default'") if generatedir[0] != '/': generatedir = os.path.abspath(os.path.join(config.WORK_DIR, generatedir)) tls_dir = generatedir ca_path = os.path.join(tls_dir, "cacert.crt") if os.path.exists(ca_path): if logger: logger.info("Existing CA certificate found in %s, not generating a new one", tls_dir) else: if logger: logger.info("Generating a new CA in %s and a client certificate for connecting", tls_dir) logger.info("use keylime_ca -d %s to manage this CA", tls_dir) if not os.path.exists(tls_dir): os.makedirs(tls_dir, 0o700) if my_key_pw == 'default': if logger: logger.warning("CAUTION: using default password for CA, please set private_key_pw to a strong password") ca_util.setpassword(my_key_pw) ca_util.cmd_init(tls_dir) ca_util.cmd_mkcert(tls_dir, socket.gethostname()) ca_util.cmd_mkcert(tls_dir, 'client') if tls_dir == 'CV': if section != 'registrar': raise Exception( f"You only use the CV option to tls_dir for the registrar not {section}") tls_dir = os.path.abspath(os.path.join(config.WORK_DIR, 'cv_ca')) if not os.path.exists(os.path.join(tls_dir, "cacert.crt")): raise Exception( "It appears that the verifier has not yet created a CA and certificates, please run the verifier first") # if it is relative path, convert to absolute in WORK_DIR if tls_dir[0] != '/': tls_dir = os.path.abspath(os.path.join(config.WORK_DIR, tls_dir)) if ca_cert == 'default': ca_path = os.path.join(tls_dir, "cacert.crt") elif not os.path.isabs(ca_cert): ca_path = os.path.join(tls_dir, ca_cert) else: ca_path = ca_cert if my_cert == 'default': my_cert = os.path.join(tls_dir, f"{socket.gethostname()}-cert.crt") elif not os.path.isabs(my_cert): my_cert = os.path.join(tls_dir, my_cert) else: pass if my_priv_key == 'default': my_priv_key = os.path.join(tls_dir, f"{socket.gethostname()}-private.pem") elif not os.path.isabs(my_priv_key): my_priv_key = os.path.join(tls_dir, my_priv_key) check_client_cert = (config.has_option(section, 'check_client_cert') and config.getboolean(section, 'check_client_cert')) context = generate_mtls_context(my_cert, my_priv_key, ca_path, check_client_cert, my_key_pw, logger=logger) return context, (my_cert, my_priv_key, my_key_pw)