def write_secret(passphrase: str, secret: str, expire: int, tries: int, haveibeenpwned: bool) -> Tuple[WriteResponse, int]: """Write a secret. Args: passphrase (str): Passphrase needed to encrypt the secret. secret (str): Secret to encrypt. expire (int): Number of days the secret will be stored. tries (int): Number of tries to read the secret before it gets deleted. haveibeenpwned (bool): Passphrase has been checked with haveibeenpwned. """ now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") exp_date = datetime.strptime(now, "%Y-%m-%d %H:%M:%S") + timedelta(days=expire) slug = generate_unique_slug() Entries.create( slug_link=slug, encrypted_text=Secret(secret.encode(), passphrase).encrypt(), date_created=now, date_expires=exp_date, tries=tries, haveibeenpwned=haveibeenpwned, ) app.logger.info(f"{slug} created and expires on {exp_date}") timez = datetime.now(timezone.utc).astimezone().tzname() expires_on = f"{exp_date.strftime('%Y-%m-%d at %H:%M')} {timez}" return ( WriteResponse(Status.CREATED.value, Messages.CREATED.value, slug, expires_on), HTTPStatus.CREATED.value, )
def read_secret(slug: str, passphrase: str) -> Tuple[ReadResponse, int]: """Read a secret. Args: slug (str): Unique slug link to access the secret. passphrase (str): Passphrase needed to decrypt the secret. """ secret = Entries.query.filter_by(slug_link=slug).first() if not secret: app.logger.info(f"{slug} tried to read but do not exists in database") return ( ReadResponse(Status.EXPIRED.value, Messages.NOT_FOUND.value), HTTPStatus.NOT_FOUND.value, ) try: msg = Secret(secret.encrypted_text, passphrase).decrypt() except InvalidToken: remaining = secret.tries - 1 if remaining == 0: # Number of tries exceeded, delete secret app.logger.warning(f"{slug} tries to open secret exceeded") secret.delete() return ( ReadResponse(Status.INVALID.value, Messages.EXCEEDED.value), HTTPStatus.UNAUTHORIZED.value, ) secret.update(tries=remaining) app.logger.info( f"{slug} wrong passphrase used. Number of tries remaining: {remaining}" ) return ( ReadResponse( Status.INVALID.value, Messages.INVALID.value.format(remaining=remaining), ), HTTPStatus.UNAUTHORIZED.value, ) secret.delete() # Delete message after it's read app.logger.info(f"{slug} was decrypted and deleted") return ( ReadResponse(Status.SUCCESS.value, html.escape(msg)), HTTPStatus.OK.value, )
def test_decryption(self): self.assertEqual( Secret(self.encrypted_text, self.passphrase).decrypt(), self.secret)
def test_wrong_passphrase(self): with self.assertRaises(InvalidToken): Secret(self.encrypted_text, "wrongPassphrase").decrypt()
def test_unique_encryption(self): encrypted = Secret(self.secret.encode(), self.passphrase).encrypt() self.assertNotEqual(encrypted, self.encrypted_text)