zulip/zerver/lib/mobile_auth_otp.py

56 lines
2.1 KiB
Python

# 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
from six.moves import zip
from zerver.lib.str_utils import force_str
from zerver.models import UserProfile
def xor_hex_strings(bytes_a, bytes_b):
# type: (str, str) -> str
"""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)
return ''.join(["%x" % (int(x, 16) ^ int(y, 16))
for x, y in zip(bytes_a, bytes_b)])
def ascii_to_hex(input_string):
# type: (str) -> str
"""Given an ascii string, encode it as a hex string"""
return "".join([hex(ord(c))[2:].zfill(2) for c in input_string])
def hex_to_ascii(input_string):
# type: (str) -> str
"""Given a hex array, decode it back to a string"""
return force_str(binascii.unhexlify(input_string))
def otp_encrypt_api_key(user_profile, otp):
# type: (UserProfile, str) -> str
assert len(otp) == UserProfile.API_KEY_LENGTH * 2
hex_encoded_api_key = ascii_to_hex(force_str(user_profile.api_key))
assert len(hex_encoded_api_key) == UserProfile.API_KEY_LENGTH * 2
return xor_hex_strings(hex_encoded_api_key, otp)
def otp_decrypt_api_key(otp_encrypted_api_key, otp):
# type: (str, str) -> str
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)
def is_valid_otp(otp):
# type: (str) -> bool
try:
assert len(otp) == UserProfile.API_KEY_LENGTH * 2
[int(c, 16) for c in otp]
return True
except Exception:
return False