async def caching_sha2_password_auth(self, pkt): # No password fast path if not self._password: self.write_packet(b'') pkt = await self._read_packet() pkt.check_error() return pkt if pkt.is_auth_switch_request(): # Try from fast auth logger.debug("caching sha2: Trying fast path") self.salt = pkt.read_all() scrambled = _auth.scramble_caching_sha2( self._password.encode('latin1'), self.salt) self.write_packet(scrambled) pkt = await self._read_packet() pkt.check_error() # else: fast auth is tried in initial handshake if not pkt.is_extra_auth_data(): raise OperationalError("caching sha2: Unknown packet " "for fast auth: {0}".format(pkt._data[:1])) # magic numbers: # 2 - request public key # 3 - fast auth succeeded # 4 - need full auth pkt.advance(1) n = pkt.read_uint8() if n == 3: logger.debug("caching sha2: succeeded by fast path.") pkt = await self._read_packet() pkt.check_error() # pkt must be OK packet return pkt if n != 4: raise OperationalError("caching sha2: Unknown " "result for fast auth: {0}".format(n)) logger.debug("caching sha2: Trying full auth...") if self._ssl_context: logger.debug("caching sha2: Sending plain " "password via secure connection") self.write_packet(self._password.encode('latin1') + b'\0') pkt = await self._read_packet() pkt.check_error() return pkt if not self.server_public_key: self.write_packet(b'\x02') pkt = await self._read_packet() # Request public key pkt.check_error() if not pkt.is_extra_auth_data(): raise OperationalError("caching sha2: Unknown packet " "for public key: {0}".format( pkt._data[:1])) self.server_public_key = pkt._data[1:] logger.debug(self.server_public_key.decode('ascii')) data = _auth.sha2_rsa_encrypt(self._password.encode('latin1'), self.salt, self.server_public_key) self.write_packet(data) pkt = await self._read_packet() pkt.check_error()
async def _request_authentication(self): # https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse if self.server_version.split('.', 1)[0] == 'Inception2': self.client_flag |= CLIENT.MULTI_RESULTS elif int(self.server_version.split('.', 1)[0]) >= 5: self.client_flag |= CLIENT.MULTI_RESULTS if self.user is None: raise ValueError("Did not specify a username") if self._ssl_context: # capablities, max packet, charset data = struct.pack('<IIB', self.client_flag, 16777216, 33) data += b'\x00' * (32 - len(data)) self.write_packet(data) # Stop sending events to data_received self._writer.transport.pause_reading() # Get the raw socket from the transport raw_sock = self._writer.transport.get_extra_info('socket', default=None) if raw_sock is None: raise RuntimeError("Transport does not expose socket instance") raw_sock = raw_sock.dup() self._writer.transport.close() # MySQL expects TLS negotiation to happen in the middle of a # TCP connection not at start. Passing in a socket to # open_connection will cause it to negotiate TLS on an existing # connection not initiate a new one. self._reader, self._writer = await asyncio.open_connection( sock=raw_sock, ssl=self._ssl_context, loop=self._loop, server_hostname=self._host) charset_id = charset_by_name(self.charset).id if isinstance(self.user, str): _user = self.user.encode(self.encoding) else: _user = self.user data_init = struct.pack('<iIB23s', self.client_flag, MAX_PACKET_LEN, charset_id, b'') data = data_init + _user + b'\0' authresp = b'' auth_plugin = self._client_auth_plugin if not self._client_auth_plugin: # Contains the auth plugin from handshake auth_plugin = self._server_auth_plugin if auth_plugin in ('', 'mysql_native_password'): authresp = _auth.scramble_native_password( self._password.encode('latin1'), self.salt) elif auth_plugin == 'caching_sha2_password': if self._password: authresp = _auth.scramble_caching_sha2( self._password.encode('latin1'), self.salt) # Else: empty password elif auth_plugin == 'sha256_password': if self._ssl_context and self.server_capabilities & CLIENT.SSL: authresp = self._password.encode('latin1') + b'\0' elif self._password: authresp = b'\1' # request public key else: authresp = b'\0' # empty password elif auth_plugin in ('', 'mysql_clear_password'): authresp = self._password.encode('latin1') + b'\0' if self.server_capabilities & CLIENT.PLUGIN_AUTH_LENENC_CLIENT_DATA: data += lenenc_int(len(authresp)) + authresp elif self.server_capabilities & CLIENT.SECURE_CONNECTION: data += struct.pack('B', len(authresp)) + authresp else: # pragma: no cover # not testing against servers without secure auth (>=5.0) data += authresp + b'\0' if self._db and self.server_capabilities & CLIENT.CONNECT_WITH_DB: if isinstance(self._db, str): db = self._db.encode(self.encoding) else: db = self._db data += db + b'\0' if self.server_capabilities & CLIENT.PLUGIN_AUTH: name = auth_plugin if isinstance(name, str): name = name.encode('ascii') data += name + b'\0' self._auth_plugin_used = auth_plugin # Sends the server a few pieces of client info if self.server_capabilities & CLIENT.CONNECT_ATTRS: connect_attrs = b'' for k, v in self._connect_attrs.items(): k, v = k.encode('utf8'), v.encode('utf8') connect_attrs += struct.pack('B', len(k)) + k connect_attrs += struct.pack('B', len(v)) + v data += struct.pack('B', len(connect_attrs)) + connect_attrs self.write_packet(data) auth_packet = await self._read_packet() # if authentication method isn't accepted the first byte # will have the octet 254 if auth_packet.is_auth_switch_request(): # https://dev.mysql.com/doc/internals/en/ # connection-phase-packets.html#packet-Protocol::AuthSwitchRequest auth_packet.read_uint8() # 0xfe packet identifier plugin_name = auth_packet.read_string() if (self.server_capabilities & CLIENT.PLUGIN_AUTH and plugin_name is not None): await self._process_auth(plugin_name, auth_packet) else: # send legacy handshake data = _auth.scramble_old_password( self._password.encode('latin1'), auth_packet.read_all()) + b'\0' self.write_packet(data) await self._read_packet() elif auth_packet.is_extra_auth_data(): if auth_plugin == "caching_sha2_password": await self.caching_sha2_password_auth(auth_packet) elif auth_plugin == "sha256_password": await self.sha256_password_auth(auth_packet) else: raise OperationalError( "Received extra packet " "for auth method %r", auth_plugin)