Skip to content

Commit

Permalink
TlsContext: allow disabling verify peer name
Browse files Browse the repository at this point in the history
Motivation: servers accepting connections from trusted peers do not know the
expected peer name in advance. Therefore, it must be possible to accept incoming
connections (validating their client certificate) without being forced to
specify an expected client name.

You do not need this when using amphp/socket to run your very own public web
server, but it is a requirement when running every other kind of service based
on trusted client certificates (with more than one client). Similar (but not the
same) use cases applies with clients, that should connect to trusted peers, w/o
knowing their name in advance.

This patch tries to address this, while preserving compatibility with the
current behaviour.
  • Loading branch information
Thomas-Gelf committed Aug 5, 2024
1 parent 58e0422 commit ff30eac
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 2 deletions.
40 changes: 39 additions & 1 deletion src/ClientTlsContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ final class ClientTlsContext

private bool $verifyPeer = true;

private bool $verifyPeerName = true;

private int $verifyDepth = 10;

private ?array $peerFingerprint = null;
Expand Down Expand Up @@ -127,6 +129,8 @@ public function withoutPeerVerification(): self
{
$clone = clone $this;
$clone->verifyPeer = false;
// This is for compatibility with the former behaviour:
$clone->verifyPeerName = false;

return $clone;
}
Expand All @@ -139,6 +143,40 @@ public function hasPeerVerification(): bool
return $this->verifyPeer;
}

/**
* Enable peer name verification, this is the default with verifyPeer enabled.
*
* @return self Cloned, modified instance.
*/
public function withPeerNameVerification(): self
{
$clone = clone $this;
$clone->verifyPeerName = true;

return $clone;
}

/**
* Disable peer name verification.
*
* @return self Cloned, modified instance.
*/
public function withoutPeerNameVerification(): self
{
$clone = clone $this;
$clone->verifyPeerName = false;

return $clone;
}

/**
* @return bool Whether peer verification is enabled.
*/
public function hasPeerNameVerification(): bool
{
return $this->verifyPeerName;
}

/**
* Maximum chain length the peer might present including the certificates in the local trust store.
*
Expand Down Expand Up @@ -452,7 +490,7 @@ public function toStreamContextArray(): array
'crypto_method' => $this->toStreamCryptoMethod(),
'peer_name' => $this->peerName,
'verify_peer' => $this->verifyPeer,
'verify_peer_name' => $this->verifyPeer,
'verify_peer_name' => $this->verifyPeerName,
'verify_depth' => $this->verifyDepth,
'ciphers' => $this->ciphers ?? \OPENSSL_DEFAULT_STREAM_CIPHERS,
'capture_peer_cert' => $this->capturePeer,
Expand Down
38 changes: 37 additions & 1 deletion src/ServerTlsContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public static function fromServerResource($socket): ?self

private bool $verifyPeer = false;

private bool $verifyPeerName = true;

private int $verifyDepth = 10;

private ?string $ciphers = null;
Expand Down Expand Up @@ -166,6 +168,40 @@ public function hasPeerVerification(): bool
return $this->verifyPeer;
}

/**
* Enable peer name verification, this is the default with verifyPeer enabled.
*
* @return self Cloned, modified instance.
*/
public function withPeerNameVerification(): self
{
$clone = clone $this;
$clone->verifyPeerName = true;

return $clone;
}

/**
* Disable peer name verification.
*
* @return self Cloned, modified instance.
*/
public function withoutPeerNameVerification(): self
{
$clone = clone $this;
$clone->verifyPeerName = false;

return $clone;
}

/**
* @return bool Whether peer verification is enabled.
*/
public function hasPeerNameVerification(): bool
{
return $this->verifyPeer && $this->verifyPeerName;
}

/**
* Maximum chain length the peer might present including the certificates in the local trust store.
*
Expand Down Expand Up @@ -437,7 +473,7 @@ public function toStreamContextArray(): array
'crypto_method' => $this->toStreamCryptoMethod(),
'peer_name' => $this->peerName,
'verify_peer' => $this->verifyPeer,
'verify_peer_name' => $this->verifyPeer,
'verify_peer_name' => $this->verifyPeer && $this->verifyPeerName,
'verify_depth' => $this->verifyDepth,
'ciphers' => $this->ciphers ?? \OPENSSL_DEFAULT_STREAM_CIPHERS,
'honor_cipher_order' => true,
Expand Down
25 changes: 25 additions & 0 deletions test/ClientTlsContextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ public function testWithoutPeerVerification(): void
self::assertFalse($clonedContext->hasPeerVerification());
}

public function testDefaultPeerNameVerification(): void
{
$context = new ClientTlsContext;
self::assertTrue($context->hasPeerNameVerification());
}

public function testWithoutPeerNameVerification(): void
{
$context = new ClientTlsContext;
$clonedContext = $context->withPeerVerification()->withoutPeerNameVerification();

self::assertTrue($clonedContext->hasPeerVerification());
self::assertFalse($clonedContext->hasPeerNameVerification());
}

public function testContextOptionsWithoutPeerNameVerification(): void
{
$context = new ClientTlsContext;
$clonedContext = $context->withPeerVerification()->withoutPeerNameVerification();
$options = $clonedContext->toStreamContextArray()['ssl'];

self::assertTrue($options['verify_peer']);
self::assertFalse($options['verify_peer_name']);
}

public function certificateDataProvider(): array
{
return [
Expand Down
25 changes: 25 additions & 0 deletions test/ServerTlsContextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ public function testWithoutPeerVerification(): void
self::assertFalse($clonedContext->hasPeerVerification());
}

public function testDefaultPeerNameVerification(): void
{
$context = new ServerTlsContext;
self::assertFalse($context->hasPeerNameVerification());
}

public function testWithoutPeerNameVerification(): void
{
$context = new ServerTlsContext;
$clonedContext = $context->withPeerVerification()->withoutPeerNameVerification();

self::assertTrue($clonedContext->hasPeerVerification());
self::assertFalse($clonedContext->hasPeerNameVerification());
}

public function testContextOptionsWithoutPeerNameVerification(): void
{
$context = new ServerTlsContext;
$clonedContext = $context->withPeerVerification()->withoutPeerNameVerification();
$options = $clonedContext->toStreamContextArray()['ssl'];

self::assertTrue($options['verify_peer']);
self::assertFalse($options['verify_peer_name']);
}

public function verifyDepthDataProvider(): array
{
return [
Expand Down

0 comments on commit ff30eac

Please sign in to comment.