def test_errors(self): encrypt(None, get_random_bytes(32)) with self.assertRaises(Exception): decrypt("", None) invalid = base64.b64encode(get_random_bytes(45)) with self.assertRaises(Exception): decrypt(invalid, None)
def encrypt(contents): k = crypto.generate_random_key(32) v = crypto.generate_random_key(32) u = crypto.strbitxor(k, v) ciphertext = crypto.encrypt(contents, k) try: recovered = crypto.decrypt(ciphertext, k).decode('utf-8') except UnicodeDecodeError: recovered = crypto.decrypt(ciphertext, k) if recovered != contents: raise Exception("Test decryption failed") return {'u': u, 'v': v, 'k': k, 'ciphertext': ciphertext}
def encrypt(contents): k = crypto.generate_random_key(32) v = crypto.generate_random_key(32) u = crypto.strbitxor(k, v) ciphertext = crypto.encrypt(contents, k) try: recovered = crypto.decrypt(ciphertext, k).decode("utf-8") except UnicodeDecodeError: recovered = crypto.decrypt(ciphertext, k) if recovered != contents: raise Exception("Test decryption failed") return {"u": u, "v": v, "k": k, "ciphertext": ciphertext}
def read_private(warn=False): global global_password if global_password is None: setpassword( getpass.getpass( "Please enter the password to decrypt your keystore: ")) if os.path.exists('private.yml'): with open('private.yml', 'r') as f: toread = yaml.load(f, Loader=SafeLoader) key = crypto.kdf(global_password, toread['salt']) try: plain = crypto.decrypt(toread['priv'], key) except ValueError: raise Exception("Invalid password for keystore") return yaml.load(plain, Loader=SafeLoader), toread['salt'] if warn: # file doesn't exist, just invent a salt logger.warning("Private certificate data %s does not exist yet." % os.path.abspath("private.yml")) logger.warning( "Keylime will attempt to load private certificate data again when it is needed." ) return { 'revoked_keys': [] }, base64.b64encode(crypto.generate_random_key()).decode()
def read_private(): global global_password if global_password is None: setpassword(getpass.getpass("Please enter the password to decrypt your keystore: ")) if os.path.exists('private.yml'): with open('private.yml','r') as f: toread = yaml.load(f, Loader=SafeLoader) key = crypto.kdf(global_password,toread['salt']) try: plain = crypto.decrypt(toread['priv'],key) except ValueError: raise Exception("Invalid password for keystore") return yaml.load(plain, Loader=SafeLoader),toread['salt'] else: #file doesn't exist, just invent a salt return {'revoked_keys':[]},base64.b64encode(crypto.generate_random_key()).decode()
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
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
def test_aes(self): message = b"a secret message!" aeskey = get_random_bytes(32) ciphertext = encrypt(message, aeskey) plaintext = decrypt(ciphertext, aeskey) self.assertEqual(plaintext, message)