2017-04-27 22:58:53 +02:00
|
|
|
# Simple one-time-pad library, to be used for encrypting Zulip API
|
|
|
|
# keys when sending them to the mobile apps via new standard mobile
|
|
|
|
# authentication flow. This encryption is used to protect against
|
|
|
|
# credential-stealing attacks where a malicious app registers the
|
|
|
|
# zulip:// URL on a device, which might otherwise allow it to hijack a
|
|
|
|
# user's API key.
|
|
|
|
#
|
|
|
|
# The decryption logic here isn't actually used by the flow; we just
|
|
|
|
# have it here as part of testing the overall library.
|
|
|
|
import binascii
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2017-04-27 22:58:53 +02:00
|
|
|
from zerver.models import UserProfile
|
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def xor_hex_strings(bytes_a: str, bytes_b: str) -> str:
|
2017-04-27 22:58:53 +02:00
|
|
|
"""Given two hex strings of equal length, return a hex string with
|
|
|
|
the bitwise xor of the two hex strings."""
|
|
|
|
assert len(bytes_a) == len(bytes_b)
|
2020-06-10 06:41:04 +02:00
|
|
|
return ''.join([f"{int(x, 16) ^ int(y, 16):x}"
|
2017-04-27 22:58:53 +02:00
|
|
|
for x, y in zip(bytes_a, bytes_b)])
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def ascii_to_hex(input_string: str) -> str:
|
2017-04-27 22:58:53 +02:00
|
|
|
"""Given an ascii string, encode it as a hex string"""
|
|
|
|
return "".join([hex(ord(c))[2:].zfill(2) for c in input_string])
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def hex_to_ascii(input_string: str) -> str:
|
2017-04-27 22:58:53 +02:00
|
|
|
"""Given a hex array, decode it back to a string"""
|
2017-12-23 16:59:59 +01:00
|
|
|
return binascii.unhexlify(input_string).decode('utf8')
|
2017-04-27 22:58:53 +02:00
|
|
|
|
2018-08-01 11:45:52 +02:00
|
|
|
def otp_encrypt_api_key(api_key: str, otp: str) -> str:
|
2017-04-27 22:58:53 +02:00
|
|
|
assert len(otp) == UserProfile.API_KEY_LENGTH * 2
|
2018-08-01 11:45:52 +02:00
|
|
|
hex_encoded_api_key = ascii_to_hex(api_key)
|
2017-04-27 22:58:53 +02:00
|
|
|
assert len(hex_encoded_api_key) == UserProfile.API_KEY_LENGTH * 2
|
|
|
|
return xor_hex_strings(hex_encoded_api_key, otp)
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def otp_decrypt_api_key(otp_encrypted_api_key: str, otp: str) -> str:
|
2017-04-27 22:58:53 +02:00
|
|
|
assert len(otp) == UserProfile.API_KEY_LENGTH * 2
|
|
|
|
assert len(otp_encrypted_api_key) == UserProfile.API_KEY_LENGTH * 2
|
|
|
|
hex_encoded_api_key = xor_hex_strings(otp_encrypted_api_key, otp)
|
|
|
|
return hex_to_ascii(hex_encoded_api_key)
|
|
|
|
|
2017-11-05 11:15:10 +01:00
|
|
|
def is_valid_otp(otp: str) -> bool:
|
2017-04-27 22:58:53 +02:00
|
|
|
try:
|
|
|
|
assert len(otp) == UserProfile.API_KEY_LENGTH * 2
|
|
|
|
[int(c, 16) for c in otp]
|
|
|
|
return True
|
|
|
|
except Exception:
|
|
|
|
return False
|