Skip to content

Commit

Permalink
feat: auto authorisation packet switching
Browse files Browse the repository at this point in the history
  • Loading branch information
krowinski committed Jan 31, 2024
1 parent 9202753 commit 9183948
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 27 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release Notes

## v8.0.1 (2024-01-31)

- Added: auto authorisation packet switching

## v8.0.0 (2024-01-29)

- Change: drop support for < 8.2
Expand Down
15 changes: 15 additions & 0 deletions src/MySQLReplication/BinLog/BinLogAuthPluginMode.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,23 @@

namespace MySQLReplication\BinLog;

use MySQLReplication\Exception\MySQLReplicationException;

enum BinLogAuthPluginMode: string
{
case MysqlNativePassword = 'mysql_native_password';
case CachingSha2Password = 'caching_sha2_password';

public static function make(string $authPluginName): self
{
$authPlugin = self::tryFrom($authPluginName);
if ($authPlugin === null) {
throw new MySQLReplicationException(
MySQLReplicationException::BINLOG_AUTH_NOT_SUPPORTED,
MySQLReplicationException::BINLOG_AUTH_NOT_SUPPORTED_CODE
);
}

return $authPlugin;
}
}
4 changes: 2 additions & 2 deletions src/MySQLReplication/BinLog/BinLogServerInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function __construct(
public string $serverVersion,
public int $connectionId,
public string $salt,
public ?BinLogAuthPluginMode $authPlugin,
public BinLogAuthPluginMode $authPlugin,
public string $versionName,
public float $versionRevision
) {
Expand Down Expand Up @@ -94,7 +94,7 @@ public static function make(string $data, string $version): self
$serverVersion,
$connectionId,
$salt,
BinLogAuthPluginMode::tryFrom($authPlugin),
BinLogAuthPluginMode::make($authPlugin),
self::parseVersion($serverVersion),
self::parseRevision($version)
);
Expand Down
68 changes: 43 additions & 25 deletions src/MySQLReplication/BinLog/BinLogSocketConnect.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use MySQLReplication\BinaryDataReader\BinaryDataReader;
use MySQLReplication\Config\Config;
use MySQLReplication\Exception\MySQLReplicationException;
use MySQLReplication\Gtid\GtidCollection;
use MySQLReplication\Repository\RepositoryInterface;
use MySQLReplication\Socket\SocketInterface;
Expand All @@ -17,6 +16,7 @@ class BinLogSocketConnect
private const COM_BINLOG_DUMP = 0x12;
private const COM_REGISTER_SLAVE = 0x15;
private const COM_BINLOG_DUMP_GTID = 0x1e;
private const AUTH_SWITCH_PACKET = 254;
/**
* https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase.html 00 FE
*/
Expand Down Expand Up @@ -47,7 +47,8 @@ public function __construct(
'Server version name: ' . $this->binLogServerInfo->versionName . ', revision: ' . $this->binLogServerInfo->versionRevision
);

$this->authenticate();

$this->authenticate($this->binLogServerInfo->authPlugin);
$this->getBinlogStream();
}

Expand Down Expand Up @@ -110,56 +111,60 @@ private function isWriteSuccessful(string $data): void
}
}

private function authenticate(): void
private function authenticate(BinLogAuthPluginMode $authPlugin): void
{
if ($this->binLogServerInfo->authPlugin === null) {
throw new MySQLReplicationException(
MySQLReplicationException::BINLOG_AUTH_NOT_SUPPORTED,
MySQLReplicationException::BINLOG_AUTH_NOT_SUPPORTED_CODE
);
}

$this->logger->info(
'Trying to authenticate user: ' . $this->config->user . ' using ' . $this->binLogServerInfo->authPlugin->value . ' plugin'
'Trying to authenticate user: ' . $this->config->user . ' using ' . $authPlugin->value . ' default plugin'
);

$data = pack('L', self::getCapabilities());
$data .= pack('L', $this->binaryDataMaxLength);
$data .= chr(33);
$data .= str_repeat(chr(0), 23);
$data .= $this->config->user . chr(0);

$auth = '';
if ($this->binLogServerInfo->authPlugin === BinLogAuthPluginMode::MysqlNativePassword) {
$auth = $this->authenticateMysqlNativePasswordPlugin();
} elseif ($this->binLogServerInfo->authPlugin === BinLogAuthPluginMode::CachingSha2Password) {
$auth = $this->authenticateCachingSha2PasswordPlugin();
}

$auth = $this->getAuthData($authPlugin, $this->binLogServerInfo->salt);
$data .= chr(strlen($auth)) . $auth;
$data .= $this->binLogServerInfo->authPlugin->value . chr(0);
$data .= $authPlugin->value . chr(0);
$str = pack('L', strlen($data));
$s = $str[0] . $str[1] . $str[2];
$data = $s . chr(1) . $data;

$this->socket->writeToSocket($data);
$this->getResponse();
$response = $this->getResponse();

// Check for AUTH_SWITCH_PACKET
if (isset($response[0]) && ord($response[0]) === self::AUTH_SWITCH_PACKET) {
$this->switchAuth($response);
}

$this->logger->info('User authenticated');
}

private function authenticateCachingSha2PasswordPlugin(): string
private function getAuthData(?BinLogAuthPluginMode $authPlugin, string $salt): string
{
if ($authPlugin === BinLogAuthPluginMode::MysqlNativePassword) {
return $this->authenticateMysqlNativePasswordPlugin($salt);
}

if ($authPlugin === BinLogAuthPluginMode::CachingSha2Password) {
return $this->authenticateCachingSha2PasswordPlugin($salt);
}

return '';
}

private function authenticateCachingSha2PasswordPlugin(string $salt): string
{
$hash1 = hash('sha256', $this->config->password, true);
$hash2 = hash('sha256', $hash1, true);
$hash3 = hash('sha256', $hash2 . $this->binLogServerInfo->salt, true);
$hash3 = hash('sha256', $hash2 . $salt, true);
return $hash1 ^ $hash3;
}

private function authenticateMysqlNativePasswordPlugin(): string
private function authenticateMysqlNativePasswordPlugin(string $salt): string
{
$hash1 = sha1($this->config->password, true);
$hash2 = sha1($this->binLogServerInfo->salt . sha1(sha1($this->config->password, true), true), true);
$hash2 = sha1($salt . sha1(sha1($this->config->password, true), true), true);
return $hash1 ^ $hash2;
}

Expand Down Expand Up @@ -316,4 +321,17 @@ private function setBinLogDump(): void

$this->logger->info('Set binlog to start from: ' . $binFileName . ':' . $binFilePos);
}

private function switchAuth(string $response): void
{
// skip AUTH_SWITCH_PACKET byte
$offset = 1;
$authPluginSwitched = BinLogAuthPluginMode::make(BinaryDataReader::decodeNullLength($response, $offset));
$salt = BinaryDataReader::decodeNullLength($response, $offset);
$auth = $this->getAuthData($authPluginSwitched, $salt);

$this->logger->info('Auth switch packet received, switching to ' . $authPluginSwitched->value);

$this->socket->writeToSocket(pack('L', (strlen($auth)) | (3 << 24)) . $auth);
}
}
14 changes: 14 additions & 0 deletions src/MySQLReplication/BinaryDataReader/BinaryDataReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -325,4 +325,18 @@ public static function unpack(string $format, string $string): array
}
return [];
}

public static function decodeNullLength(string $data, int &$offset = 0): string
{
$length = strpos($data, chr(0), $offset);
if ($length === false) {
return '';
}

$length -= $offset;
$result = substr($data, $offset, $length);
$offset += $length + 1;

return $result;
}
}

0 comments on commit 9183948

Please sign in to comment.