summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ClientCertificate.php28
-rw-r--r--src/Request.php29
-rw-r--r--src/Server.php38
3 files changed, 90 insertions, 5 deletions
diff --git a/src/ClientCertificate.php b/src/ClientCertificate.php
new file mode 100644
index 0000000..d2b9530
--- /dev/null
+++ b/src/ClientCertificate.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace GeminiFoundation;
+
+class ClientCertificate
+{
+ protected \OpenSSLCertificate $certificate;
+
+ public function __construct(\OpenSSLCertificate $certificate)
+ {
+ $this->certificate = $certificate;
+ }
+
+ public function getObject(): \OpenSSLCertificate
+ {
+ return $this->certificate;
+ }
+
+ public function getFingerprint(): string
+ {
+ return openssl_x509_fingerprint($this->certificate);
+ }
+
+ public function parse(): array
+ {
+ return openssl_x509_parse($this->certificate);
+ }
+}
diff --git a/src/Request.php b/src/Request.php
index ceb7dbb..82ce0d3 100644
--- a/src/Request.php
+++ b/src/Request.php
@@ -8,8 +8,9 @@ class Request
protected string $host;
protected string $path;
protected array $query;
+ protected ?ClientCertificate $clientCertificate;
- public function __construct(string $url)
+ public function __construct(string $url, ?ClientCertificate $clientCertificate = null)
{
$requestUrl = parse_url($url);
@@ -29,6 +30,8 @@ class Request
}
}
}
+
+ $this->clientCertificate = $clientCertificate;
}
/**
@@ -36,7 +39,17 @@ class Request
*/
public static function fromResource($resource): static
{
- return Request::fromString(fread($resource, 1024));
+ $request = Request::fromString(fread($resource, 1024));
+
+ $clientCertificate = null;
+ $streamParams = stream_context_get_params($resource);
+ $peerCertificate = $streamParams['options']['ssl']['peer_certificate'] ?? null;
+ if ($peerCertificate) {
+ $clientCertificate = new ClientCertificate($peerCertificate);
+ $request->setClientCertificate($clientCertificate);
+ }
+
+ return $request;
}
public static function fromString(string $string): static
@@ -67,4 +80,16 @@ class Request
return $this;
}
+
+ public function getClientCertificate(): ?ClientCertificate
+ {
+ return $this->clientCertificate;
+ }
+
+ public function setClientCertificate(ClientCertificate $clientCertificate): self
+ {
+ $this->clientCertificate = $clientCertificate;
+
+ return $this;
+ }
}
diff --git a/src/Server.php b/src/Server.php
index 46c7dca..102c4fd 100644
--- a/src/Server.php
+++ b/src/Server.php
@@ -12,19 +12,29 @@ class Server
'passphrase' => null,
];
+ protected array $options = [];
+
protected array $requestHandlers = [];
/**
* @param array $certificate
* @param string $hostname
+ * @param array $options
*/
public function __construct(
array $certificate,
- string $hostname = 'localhost'
+ string $hostname = 'localhost',
+ array $options = []
)
{
$this->certificate = $certificate;
$this->hostname = $hostname;
+
+ $this->options = array_replace_recursive([
+ 'context' => [],
+ 'client_certificate_support_workaround' => false, # "support" until https://bugs.php.net/bug.php?id=80770
+ 'client_certificate_support_workaround_timeout' => 3,
+ ], $options);
}
public function setCertificate(string $certificateFile, string $keyFile, string $passphrase = ''): static
@@ -47,7 +57,7 @@ class Server
public function listen(int $port = 1965): void
{
- $context = stream_context_create(options: [
+ $contextOptions = array_replace_recursive([
'ssl' => [
'local_cert' => $this->certificate['file'],
'local_pk' => $this->certificate['key'],
@@ -55,8 +65,11 @@ class Server
'allow_self_signed' => true,
'verify_peer' => false,
+ 'capture_peer_cert' => true,
],
- ]);
+ ], $this->options['context']);
+
+ $context = stream_context_create(options: $contextOptions);
$socket = stream_socket_server(
address: "tls://{$this->hostname}:{$port}",
@@ -66,6 +79,11 @@ class Server
$connections = [];
while (true) {
+ if ($this->options['client_certificate_support_workaround']) {
+ stream_context_set_option($socket, 'ssl', 'verify_peer', true);
+ }
+
+ echo "Listening for connections.\n";
$connection = stream_socket_accept(
socket: $socket,
timeout: empty($connections) ? -1 : 0,
@@ -74,6 +92,20 @@ class Server
if ($connection) {
$connections[$peer] = $connection;
+ } else if ($this->options['client_certificate_support_workaround']) {
+ echo "Enabling client certificate \"support\" workaround.\n";
+ echo "Listening without verifying peer.\n";
+
+ stream_context_set_option($socket, 'ssl', 'verify_peer', false);
+ $connection = stream_socket_accept(
+ socket: $socket,
+ timeout: $this->options['client_certificate_support_workaround_timeout'],
+ peer_name: $peer
+ );
+
+ if ($connection) {
+ $connections[$peer] = $connection;
+ }
}
if (count($connections) == 0) {