Skip to content

Commit

Permalink
Support for SCRAM_SHA_256
Browse files Browse the repository at this point in the history
  • Loading branch information
zwirek committed Aug 1, 2023
1 parent 696b766 commit 7889cef
Show file tree
Hide file tree
Showing 11 changed files with 422 additions and 5 deletions.
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,9 @@
"require-dev": {
"phpunit/phpunit": ">=8.5.23 || ^6.5.5",
"react/dns": "^1.0"
},
"scripts": {
"docker-up": "cd docker && docker-compose up -d",
"docker-down": "cd docker && docker-compose down"
}
}
13 changes: 13 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,16 @@ services:
- ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
ports:
- "5432:5432"

pgasync-postgres-15:
container_name: pgasync-postgres-15
image: postgres:15
environment:
- PGDATA=/database
- POSTGRES_USER=sampleuser
- POSTGRES_PASSWORD=some_password
- TZ=America/New_York
volumes:
- .:/app
ports:
- "5415:5432"
41 changes: 41 additions & 0 deletions src/PgAsync/Command/SaslInitialResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);

namespace PgAsync\Command;

use PgAsync\ScramSha256;

class SaslInitialResponse implements CommandInterface
{
use CommandTrait;

const SCRAM_SHA_256 = "SCRAM-SHA-256";

/**
* @var ScramSha256
*/
private $scramSha265;

public function __construct(ScramSha256 $scramSha265)
{
$this->scramSha265 = $scramSha265;
}

public function encodedMessage(): string
{
$mechanism = self::SCRAM_SHA_256 . "\0";
$clientFirstMessage = $this->scramSha265->getClientFirstMessage();

$message = "p";
$messageLength = strlen($mechanism) + strlen($clientFirstMessage) + 8;
$message .= pack("N", $messageLength) . $mechanism;
$message .= pack("N", strlen($clientFirstMessage)) . $clientFirstMessage;

return $message;
}

public function shouldWaitForComplete(): bool
{
return false;
}
}
39 changes: 39 additions & 0 deletions src/PgAsync/Command/SaslResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);

namespace PgAsync\Command;

use PgAsync\ScramSha256;

class SaslResponse implements CommandInterface
{
use CommandTrait;

/**
* @var ScramSha256
*/
private $scramSha265;

public function __construct(ScramSha256 $scramSha265)
{
$this->scramSha265 = $scramSha265;
}

public function encodedMessage(): string
{
$clientFinalMessage = $this->createClientFinalMessage();
$messageLength = strlen($clientFinalMessage) + 4;

return 'p' . pack('N', $messageLength) . $clientFinalMessage;
}

public function shouldWaitForComplete(): bool
{
return false;
}

private function createClientFinalMessage(): string
{
return $this->scramSha265->getClientFirstMessageWithoutProof() . ',p=' . base64_encode($this->scramSha265->getClientProof());
}
}
32 changes: 31 additions & 1 deletion src/PgAsync/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use PgAsync\Command\Execute;
use PgAsync\Command\Parse;
use PgAsync\Command\PasswordMessage;
use PgAsync\Command\SaslInitialResponse;
use PgAsync\Command\SaslResponse;
use PgAsync\Command\Sync;
use PgAsync\Command\Terminate;
use PgAsync\Message\Authentication;
Expand Down Expand Up @@ -119,6 +121,9 @@ class Connection extends EventEmitter
/** @var bool */
private $cancelRequested;

/** @var ScramSha256 */
private $scramSha256;

/**
* Can be 'I' for Idle, 'T' if in transactions block
* or 'E' if in failed transaction block (queries will fail until end of trans)
Expand Down Expand Up @@ -174,6 +179,7 @@ public function __construct(array $parameters, LoopInterface $loop, ConnectorInt
$this->cancelRequested = false;

$this->parameters = $parameters;
$this->scramSha256 = new ScramSha256($parameters['user'], $this->password ?: '');
}

private function start()
Expand Down Expand Up @@ -268,7 +274,9 @@ private function processData($data)

$type = $data[0];

$message = Message::createMessageFromIdentifier($type);
$message = Message::createMessageFromIdentifier($type, [
'SCRAM_SHA_256' => $this->scramSha256
]);
if ($message !== false) {
$this->currentMessage = $message;
return $data;
Expand Down Expand Up @@ -388,6 +396,28 @@ private function handleAuthentication(Authentication $message)
return;
}

if ($message->getAuthCode() === $message::AUTH_SCRAM) {
$saslInitialResponse = new SaslInitialResponse($this->scramSha256);
$this->stream->write($saslInitialResponse->encodedMessage());

return;
}

if ($message->getAuthCode() === $message::AUTH_SCRAM_CONTINUE) {
$saslResponse = new SaslResponse($this->scramSha256);
$this->stream->write($saslResponse->encodedMessage());

return;
}

if ($message->getAuthCode() === $message::AUTH_SCRAM_FIN) {
if ($this->scramSha256->verify()) {
return;
}

$this->lastError = 'Invalid server signature sent by server on SCRAM FIN stage';
}

$this->connStatus = $this::CONNECTION_BAD;
$this->failAllCommandsWith(new \Exception($this->lastError));
$this->emit('error', [new \Exception($this->lastError)]);
Expand Down
40 changes: 40 additions & 0 deletions src/PgAsync/Message/Authentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace PgAsync\Message;

use PgAsync\ScramSha256;

class Authentication extends Message
{
const AUTH_OK = 0; // AuthenticationOk
Expand All @@ -12,11 +14,26 @@ class Authentication extends Message
const AUTH_GSS = 7; // AuthenticationGSS
const AUTH_GSS_CONTINUE = 8; // AuthenticationGSSContinue
const AUTH_SSPI = 9; // AuthenticationSSPI
const AUTH_SCRAM = 10; // AuthenticationSASL
const AUTH_SCRAM_CONTINUE = 11; // AuthenticationSASLContinue
const AUTH_SCRAM_FIN = 12; // AuthenticationSASLFinal

private $authCode;

private $salt;

/** @var ScramSha256 */
private $scramSha256;

private $iteration;

private $nonce;

public function __construct(ScramSha256 $scramSha265)
{
$this->scramSha256 = $scramSha265;
}

/**
* @inheritDoc
* @throws \InvalidArgumentException
Expand Down Expand Up @@ -47,6 +64,23 @@ public function parseMessage(string $rawMessage)
break; // AuthenticationGSSContinue
case $this::AUTH_SSPI:
break; // AuthenticationSSPI
case $this::AUTH_SCRAM:
$this->scramSha256->beginFirstClientMessageStage();
break;
case $this::AUTH_SCRAM_CONTINUE:
$content = $this->getContent($rawMessage);
$parts = explode(',', $content);
$this->scramSha256->beginFinalClientMessageStage(
substr($parts[0], 2),
substr($parts[1], 2),
(int) substr($parts[2], 2)
);

break;
case $this::AUTH_SCRAM_FIN:
$content = $this->getContent($rawMessage);
$this->scramSha256->beginVerificationStage(substr($content, 2));
break;
}

$this->authCode = $authCode;
Expand All @@ -70,4 +104,10 @@ public function getSalt(): string

return $this->salt;
}

private function getContent(string $rawMessage): string
{
$messageLength = unpack('N', substr($rawMessage, 1, 4))[1];
return substr($rawMessage, 9, $messageLength - 8);
}
}
4 changes: 2 additions & 2 deletions src/PgAsync/Message/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ public static function prependLengthInt32(string $s): string
return Message::int32($len + 4) . $s;
}

public static function createMessageFromIdentifier(string $identifier): ParserInterface
public static function createMessageFromIdentifier(string $identifier, array $dependencies): ParserInterface
{
switch ($identifier) {
case 'R':
return new Authentication();
return new Authentication($dependencies['SCRAM_SHA_256']);
case 'K':
return new BackendKeyData();
case 'C':
Expand Down
Loading

0 comments on commit 7889cef

Please sign in to comment.