Path: blob/main/singlestoredb/mysql/_auth.py
469 views
# type: ignore1"""2Implements auth methods3"""4from .err import OperationalError56try:7from cryptography.hazmat.backends import default_backend8from cryptography.hazmat.primitives import serialization, hashes9from cryptography.hazmat.primitives.asymmetric import padding1011_have_cryptography = True12except ImportError:13_have_cryptography = False1415from functools import partial16import hashlib1718from ..config import get_option192021DEBUG = get_option('debug.connection')22SCRAMBLE_LENGTH = 2023sha1_new = partial(hashlib.new, 'sha1')242526# mysql_native_password27# https://dev.mysql.com/doc/internals/en/secure-password-authentication.html#packet-Authentication::Native41282930def scramble_native_password(password, message):31"""Scramble used for mysql_native_password."""32if not password:33return b''3435stage1 = sha1_new(password).digest()36stage2 = sha1_new(stage1).digest()37s = sha1_new()38s.update(message[:SCRAMBLE_LENGTH])39s.update(stage2)40result = s.digest()41return _my_crypt(result, stage1)424344def _my_crypt(message1, message2):45result = bytearray(message1)4647for i in range(len(result)):48result[i] ^= message2[i]4950return bytes(result)515253# MariaDB's client_ed25519-plugin54# https://mariadb.com/kb/en/library/connection/#client_ed25519-plugin5556_nacl_bindings = False575859def _init_nacl():60global _nacl_bindings61try:62from nacl import bindings6364_nacl_bindings = bindings65except ImportError:66raise RuntimeError(67"'pynacl' package is required for ed25519_password auth method",68)697071def _scalar_clamp(s32):72ba = bytearray(s32)73ba0 = bytes(bytearray([ba[0] & 248]))74ba31 = bytes(bytearray([(ba[31] & 127) | 64]))75return ba0 + bytes(s32[1:31]) + ba31767778def ed25519_password(password, scramble):79"""80Sign a random scramble with elliptic curve Ed25519.8182Secret and public key are derived from password.8384"""85# variable names based on rfc8032 section-5.1.686#87if not _nacl_bindings:88_init_nacl()8990# h = SHA512(password)91h = hashlib.sha512(password).digest()9293# s = prune(first_half(h))94s = _scalar_clamp(h[:32])9596# r = SHA512(second_half(h) || M)97r = hashlib.sha512(h[32:] + scramble).digest()9899# R = encoded point [r]B100r = _nacl_bindings.crypto_core_ed25519_scalar_reduce(r)101R = _nacl_bindings.crypto_scalarmult_ed25519_base_noclamp(r)102103# A = encoded point [s]B104A = _nacl_bindings.crypto_scalarmult_ed25519_base_noclamp(s)105106# k = SHA512(R || A || M)107k = hashlib.sha512(R + A + scramble).digest()108109# S = (k * s + r) mod L110k = _nacl_bindings.crypto_core_ed25519_scalar_reduce(k)111ks = _nacl_bindings.crypto_core_ed25519_scalar_mul(k, s)112S = _nacl_bindings.crypto_core_ed25519_scalar_add(ks, r)113114# signature = R || S115return R + S116117118# sha256_password119120121def _roundtrip(conn, send_data):122conn.write_packet(send_data)123pkt = conn._read_packet()124pkt.check_error()125return pkt126127128def _xor_password(password, salt):129# Trailing NUL character will be added in Auth Switch Request.130# See https://github.com/mysql/mysql-server/blob/131# 7d10c82196c8e45554f27c00681474a9fb86d137/sql/auth/sha2_password.cc#L939-L945132salt = salt[:SCRAMBLE_LENGTH]133password_bytes = bytearray(password)134# salt = bytearray(salt) # for PY2 compat.135salt_len = len(salt)136for i in range(len(password_bytes)):137password_bytes[i] ^= salt[i % salt_len]138return bytes(password_bytes)139140141def sha2_rsa_encrypt(password, salt, public_key):142"""143Encrypt password with salt and public_key.144145Used for sha256_password and caching_sha2_password.146147"""148if not _have_cryptography:149raise RuntimeError(150"'cryptography' package is required for sha256_password or "151'caching_sha2_password auth methods',152)153message = _xor_password(password + b'\0', salt)154rsa_key = serialization.load_pem_public_key(public_key, default_backend())155return rsa_key.encrypt(156message,157padding.OAEP(158mgf=padding.MGF1(algorithm=hashes.SHA1()),159algorithm=hashes.SHA1(),160label=None,161),162)163164165def sha256_password_auth(conn, pkt):166if conn._secure:167if DEBUG:168print('sha256: Sending plain password')169data = conn.password + b'\0'170return _roundtrip(conn, data)171172if pkt.is_auth_switch_request():173conn.salt = pkt.read_all()174if not conn.server_public_key and conn.password:175# Request server public key176if DEBUG:177print('sha256: Requesting server public key')178pkt = _roundtrip(conn, b'\1')179180if pkt.is_extra_auth_data():181conn.server_public_key = pkt._data[1:]182if DEBUG:183print('Received public key:\n', conn.server_public_key.decode('ascii'))184185if conn.password:186if not conn.server_public_key:187raise OperationalError("Couldn't receive server's public key")188189data = sha2_rsa_encrypt(conn.password, conn.salt, conn.server_public_key)190else:191data = b''192193return _roundtrip(conn, data)194195196def scramble_caching_sha2(password, nonce):197# (bytes, bytes) -> bytes198"""199Scramble algorithm used in cached_sha2_password fast path.200201XOR(SHA256(password), SHA256(SHA256(SHA256(password)), nonce))202203"""204if not password:205return b''206207p1 = hashlib.sha256(password).digest()208p2 = hashlib.sha256(p1).digest()209p3 = hashlib.sha256(p2 + nonce).digest()210211res = bytearray(p1)212for i in range(len(p3)):213res[i] ^= p3[i]214215return bytes(res)216217218def caching_sha2_password_auth(conn, pkt):219# No password fast path220if not conn.password:221return _roundtrip(conn, b'')222223if pkt.is_auth_switch_request():224# Try from fast auth225if DEBUG:226print('caching sha2: Trying fast path')227conn.salt = pkt.read_all()228scrambled = scramble_caching_sha2(conn.password, conn.salt)229pkt = _roundtrip(conn, scrambled)230# else: fast auth is tried in initial handshake231232if not pkt.is_extra_auth_data():233raise OperationalError(234'caching sha2: Unknown packet for fast auth: %s' % pkt._data[:1],235)236237# magic numbers:238# 2 - request public key239# 3 - fast auth succeeded240# 4 - need full auth241242pkt.advance(1)243n = pkt.read_uint8()244245if n == 3:246if DEBUG:247print('caching sha2: succeeded by fast path.')248pkt = conn._read_packet()249pkt.check_error() # pkt must be OK packet250return pkt251252if n != 4:253raise OperationalError('caching sha2: Unknwon result for fast auth: %s' % n)254255if DEBUG:256print('caching sha2: Trying full auth...')257258if conn._secure:259if DEBUG:260print('caching sha2: Sending plain password via secure connection')261return _roundtrip(conn, conn.password + b'\0')262263if not conn.server_public_key:264pkt = _roundtrip(conn, b'\x02') # Request public key265if not pkt.is_extra_auth_data():266raise OperationalError(267'caching sha2: Unknown packet for public key: %s' % pkt._data[:1],268)269270conn.server_public_key = pkt._data[1:]271if DEBUG:272print(conn.server_public_key.decode('ascii'))273274data = sha2_rsa_encrypt(conn.password, conn.salt, conn.server_public_key)275pkt = _roundtrip(conn, data)276277278def gssapi_auth(data):279try:280import gssapi.raw281except ImportError:282raise ImportError(283'The `gssapi` package must be '284'installed for Kerberos authentication',285)286287ctx = None288try:289ctx = gssapi.raw.init_sec_context(290gssapi.raw.import_name(data, gssapi.raw.NameType.user),291flags=[0], lifetime=0,292)293return ctx.token294295finally:296if ctx is not None:297gssapi.raw.delete_sec_context(ctx.context)298299300