Skip to content

Add SecureServer for secure TLS connections #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and [`Stream`](https://github.com/reactphp/stream) components.
* [Quickstart example](#quickstart-example)
* [Usage](#usage)
* [Server](#server)
* [SecureServer](#secureserver)
* [ConnectionInterface](#connectioninterface)
* [getRemoteAddress()](#getremoteaddress)
* [Install](#install)
Expand Down Expand Up @@ -77,10 +78,70 @@ instance implementing [`ConnectionInterface`](#connectioninterface):

```php
$server->on('connection', function (ConnectionInterface $connection) {
echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;

$connection->write('hello there!' . PHP_EOL);
});
```

### SecureServer

The `SecureServer` class implements the `ServerInterface` and is responsible
for providing a secure TLS (formerly known as SSL) server.

It does so by wrapping a [`Server`](#server) instance which waits for plaintext
TCP/IP connections and then performs a TLS handshake for each connection.
It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php),
which in its most basic form may look something like this if you're using a
PEM encoded certificate file:

```php
$server = new Server($loop);

$server = new SecureServer($server, $loop, array(
'local_cert' => 'server.pem'
));

$server->listen(8000);
```

> Note that the certificate file will not be loaded on instantiation but when an
incoming connection initializes its TLS context.
This implies that any invalid certificate file paths or contents will only cause
an `error` event at a later time.

If your private key is encrypted with a passphrase, you have to specify it
like this:

```php
$server = new SecureServer($server, $loop, array(
'local_cert' => 'server.pem',
'passphrase' => 'secret'
));
```

Whenever a client completes the TLS handshake, it will emit a `connection` event
with a connection instance implementing [`ConnectionInterface`](#connectioninterface):

```php
$server->on('connection', function (ConnectionInterface $connection) {
echo 'Secure connection from' . $connection->getRemoteAddress() . PHP_EOL;

$connection->write('hello there!' . PHP_EOL);
});
```

Whenever a client fails to perform a successful TLS handshake, it will emit an
`error` event and then close the underlying TCP/IP connection:

```php
$server->on('error', function (Exception $e) {
echo 'Error' . $e->getMessage() . PHP_EOL;
});
```

### ConnectionInterface

The `ConnectionInterface` is used to represent any incoming connection.
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"php": ">=5.3.0",
"evenement/evenement": "~2.0|~1.0",
"react/event-loop": "0.4.*|0.3.*",
"react/stream": "^0.4.2"
"react/stream": "^0.4.5",
"react/promise": "^2.0 || ^1.1"
},
"require-dev": {
"react/socket-client": "^0.5.1",
Expand Down
18 changes: 17 additions & 1 deletion examples/01-echo.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,39 @@
//
// $ php examples/01-echo.php 8000
// $ telnet localhost 8000
//
// You can also run a secure TLS echo server like this:
//
// $ php examples/01-echo.php 8000 examples/localhost.pem
// $ openssl s_client -connect localhost:8000

use React\EventLoop\Factory;
use React\Socket\Server;
use React\Socket\ConnectionInterface;
use React\Socket\SecureServer;

require __DIR__ . '/../vendor/autoload.php';

$loop = Factory::create();

$server = new Server($loop);

// secure TLS mode if certificate is given as second parameter
if (isset($argv[2])) {
$server = new SecureServer($server, $loop, array(
'local_cert' => $argv[2]
));
}

$server->listen(isset($argv[1]) ? $argv[1] : 0);

$server->on('connection', function (ConnectionInterface $conn) use ($loop) {
echo '[connected]' . PHP_EOL;
$conn->pipe($conn);
});

echo 'Listening on ' . $server->getPort() . PHP_EOL;
$server->on('error', 'printf');

echo 'bound to ' . $server->getPort() . PHP_EOL;

$loop->run();
16 changes: 16 additions & 0 deletions examples/02-chat-server.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,30 @@
//
// $ php examples/02-chat-server.php 8000
// $ telnet localhost 8000
//
// You can also run a secure TLS chat server like this:
//
// $ php examples/02-chat-server.php 8000 examples/localhost.pem
// $ openssl s_client -connect localhost:8000

use React\EventLoop\Factory;
use React\Socket\Server;
use React\Socket\ConnectionInterface;
use React\Socket\SecureServer;

require __DIR__ . '/../vendor/autoload.php';

$loop = Factory::create();

$server = new Server($loop);

// secure TLS mode if certificate is given as second parameter
if (isset($argv[2])) {
$server = new SecureServer($server, $loop, array(
'local_cert' => $argv[2]
));
}

$server->listen(isset($argv[1]) ? $argv[1] : 0, '0.0.0.0');

$clients = array();
Expand Down Expand Up @@ -44,6 +58,8 @@
});
});

$server->on('error', 'printf');

echo 'Listening on ' . $server->getPort() . PHP_EOL;

$loop->run();
16 changes: 16 additions & 0 deletions examples/03-benchmark.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,32 @@
// $ telnet localhost 8000
// $ echo hello world | nc -v localhost 8000
// $ dd if=/dev/zero bs=1M count=1000 | nc -v localhost 8000
//
// You can also run a secure TLS benchmarking server like this:
//
// $ php examples/03-benchmark.php 8000 examples/localhost.pem
// $ openssl s_client -connect localhost:8000
// $ echo hello world | openssl s_client -connect localhost:8000
// $ dd if=/dev/zero bs=1M count=1000 | openssl s_client -connect localhost:8000

use React\EventLoop\Factory;
use React\Socket\Server;
use React\Socket\ConnectionInterface;
use React\Socket\SecureServer;

require __DIR__ . '/../vendor/autoload.php';

$loop = Factory::create();

$server = new Server($loop);

// secure TLS mode if certificate is given as second parameter
if (isset($argv[2])) {
$server = new SecureServer($server, $loop, array(
'local_cert' => $argv[2]
));
}

$server->listen(isset($argv[1]) ? $argv[1] : 0);

$server->on('connection', function (ConnectionInterface $conn) use ($loop) {
Expand Down
31 changes: 31 additions & 0 deletions examples/10-generate-self-signed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

// A very simple helper script used to generate self-signed certificates.
// Accepts the CN and an optional passphrase to encrypt the private key.
//
// $ php 10-generate-self-signed.php localhost swordfish > secret.pem

// certificate details (Distinguished Name)
// (OpenSSL applies defaults to missing fields)
$dn = array(
"commonName" => isset($argv[1]) ? $argv[1] : "localhost",
// "countryName" => "AU",
// "stateOrProvinceName" => "Some-State",
// "localityName" => "London",
// "organizationName" => "Internet Widgits Pty Ltd",
// "organizationalUnitName" => "R&D",
// "emailAddress" => "admin@example.com"
);

// create certificate which is valid for ~10 years
$privkey = openssl_pkey_new();
$cert = openssl_csr_new($dn, $privkey);
$cert = openssl_csr_sign($cert, null, $privkey, 3650);

// export public and (optionally encrypted) private key in PEM format
openssl_x509_export($cert, $out);
echo $out;

$passphrase = isset($argv[2]) ? $argv[2] : null;
openssl_pkey_export($privkey, $out, $passphrase);
echo $out;
49 changes: 49 additions & 0 deletions examples/localhost.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQ1OTA2WhcNMjYx
MjI4MTQ1OTA2WjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8SZWNS+Ktg0Py
W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
YCUE54G/AgMBAAGjUDBOMB0GA1UdDgQWBBQ2GRz3QsQzdXaTMnPVCKfpigA10DAf
BgNVHSMEGDAWgBQ2GRz3QsQzdXaTMnPVCKfpigA10DAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQA77iZ4KrpPY18Ezjt0mngYAuAxunKddXYdLZ2khywN
0uI/VzYnkFVtrsC7y2jLHSxlmE2/viPPGZDUplENV2acN6JNW+tlt7/bsrQHDQw3
7VCF27EWiDxHsaghhLkqC+kcop5YR5c0oDQTdEWEKSbow2zayUXDYbRRs76SClTe
824Yul+Ts8Mka+AX2PXDg47iZ84fJRN/nKavcJUTJ2iS1uYw0GNnFMge/uwsfMR3
V47qN0X5emky8fcq99FlMCbcy0gHAeSWAjClgr2dd2i0LDatUbj7YmdmFcskOgII
IwGfvuWR2yPevYGAE0QgFeLHniN3RW8zmpnX/XtrJ4a7
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8SZWNS+Ktg0Py
W8dx5uXZ+ZUawd3wnzLMHW7EhoUpIrIdp3kDU9NezF68dOhPMJY/Kh+6btRCxWXN
2OVTqS5Xi826j3TSE07iF83JRLeveW0PcodjUBd+RzdwCWWo2pfMJz4v7x1wu1c9
zNi6JxxpDAXTFSB4GiWsI4tFu2XmMRhfm6LRK4WPfsZIJKokdiG5fKSPDn7nrVj0
UUXr2eBsEAzdwL14U9+mwbLdaAkz3qK3fqi8sEC09lEWm95gKMOhkQf5qvXODtT4
wdVrrKDTyehLv0xaItnUDnXzrkMBU5QS9TQzzqSW6ZaBsSxtONEFUiXiN9dtyXsY
YCUE54G/AgMBAAECggEBAKiO/3FE1CMddkCLZVtUp8ShqJgRokx9WI5ecwFApAkV
ZHsjqDQQYRNmxhDUX/w0tOzLGyhde2xjJyZG29YviKsbHwu6zYwbeOzy/mkGOaK/
g6DmmMmRs9Z6juifoQCu4GIFZ6il2adIL2vF7OeJh+eKudQj/7NFRSB7mXzNrQWK
tZY3eux5zXWmio7pgZrx1HFZQiiL9NVLwT9J7oBnaoO3fREiu5J2xBpljG9Cr0j1
LLiVLhukWJYRlHDtGt1CzI9w8iKo44PCRzpKyxpbsOrQxeSyEWUYQRv9VHA59LC7
tVAJTbnTX1BNHkGZkOkoOpoZLwBaM2XbbDtcOGCAZMECgYEA+mTURFQ85/pxawvk
9ndqZ+5He1u/bMLYIJDp0hdB/vgD+vw3gb2UyRwp0I6Wc6Si4FEEnbY7L0pzWsiR
43CpLs+cyLfnD9NycuIasxs5fKb/1s1nGTkRAp7x9x/ZTtEf8v4YTmmMXFHzdo7V
pv+czO89ppEDkxEtMf/b5SifhO8CgYEAwIDIUvXLduGhL+RPDwjc2SKdydXGV6om
OEdt/V8oS801Z7k8l3gHXFm7zL/MpHmh9cag+F9dHK42kw2RSjDGsBlXXiAO1Z0I
2A34OdPw/kow8fmIKWTMu3+28Kca+3RmUqeyaq0vazQ/bWMO9px+Ud3YfLo1Tn5I
li0MecAx8DECgYEAvsLceKYYtL83c09fg2oc1ctSCCgw4WJcGAtvJ9DyRZacKbXH
b/+H/+OF8879zmKqd+0hcCnqUzAMTCisBLPLIM+o6b45ufPkqKObpcJi/JWaKgLY
vf2c+Psw6o4IF6T5Cz4MNIjzF06UBknxecYZpoPJ20F1kLCwVvxPgfl99l8CgYAb
XfOcv67WTstgiJ+oroTfJamy+P5ClkDqvVTosW+EHz9ZaJ8xlXHOcj9do2LPey9I
Rp250azmF+pQS5x9JKQKgv/FtN8HBVUtigbhCb14GUoODICMCfWFLmnumoMefnTR
iV+3BLn6Dqp5vZxx+NuIffZ5/Or5JsDhALSGVomC8QKBgAi3Z/dNQrDHfkXMNn/L
+EAoLuAbFgLs76r9VGgNaRQ/q5gex2bZEGoBj4Sxvs95NUIcfD9wKT7FF8HdxARv
y3o6Bfc8Xp9So9SlFXrje+gkdEJ0rQR67d+XBuJZh86bXJHVrMwpoNL+ahLGdVSe
81oh1uCH1YPLM29hPyaohxL8
-----END PRIVATE KEY-----
51 changes: 51 additions & 0 deletions examples/localhost_swordfish.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBZMRIwEAYDVQQDDAkxMjcu
MC4wLjExCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK
DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMjMwMTQxMDQzWhcNMjYx
MjI4MTQxMDQzWjBZMRIwEAYDVQQDDAkxMjcuMC4wLjExCzAJBgNVBAYTAkFVMRMw
EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0
eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRXt83SrKIHr/i
3lc8O8pz6NHE1DNHJa4xg2xalXWzCEV6m1qLd9VdaLT9cJD1afNmEMBgY6RblNL/
paJWVoR9MOUeIoYl2PrhUCxsf7h6MRtezQQe3e+n+/0XunF0JUQIZuJqbxfRk5WT
XmYnphqOZKEcistAYvFBjzl/D+Cl/nYsreADc+t9l5Vni89oTWEuqIrsM4WUZqqB
VMAakd2nZJLWIrMxq9hbW1XNukOQfcmZVFTC6CUnLq8qzGbtfZYBuMBACnL1k/E/
yPaAgR46l14VAcndDUJBtMeL2qYuNwvXQhg3KuBmpTUpH+yzxU+4T3lmv0xXmPqu
ySH3xvW3AgMBAAGjUDBOMB0GA1UdDgQWBBRu68WTI4pVeTB7wuG9QGI3Ie441TAf
BgNVHSMEGDAWgBRu68WTI4pVeTB7wuG9QGI3Ie441TAMBgNVHRMEBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQCc4pEjEHO47VRJkbHgC+c2gAVgxekkaA1czBA1uAvh
ILRda0NLlvyftbjaG0zZp2ABUCfRfksl/Pf/PzWLUMEuH/9kEW2rgP43z6YgiL6k
kBPlmAU607UjD726RPGkw8QPSXS/dWiNJ5CBpPWLpxC45pokqItYbY0ijQ5Piq09
TchYlCX044oSRnPiP394PQ3HVdaGhJB2DnjDq3in5dVivFf8EdgzQSvp/wXy3WQs
uFSVonSnrZGY/4AgT3psGaQ6fqKb4SBoqtf5bFQvp1XNNRkuEJnS/0dygEya0c+c
aCe/1gXC2wDjx0/TekY5m1Nyw5SY6z7stOqL/ekwgejt
-----END CERTIFICATE-----
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIG7idPRLgiHkCAggA
MBQGCCqGSIb3DQMHBAg+MLPdepHWSwSCBMgVW9LseCjfTAmF9U1qRnKsq3kIwEnW
6aERBqs/mnmEhrXgZYgcvRRK7kD12TdHt/Nz46Ymu0h+Lrvuwtl1fHQUARTk/gFh
onLhc9kjMUhLRIR007vJe3HvWOb/v+SBSDB38OpUxUwJmBVBuSaYLWVuPR6J5kUj
xOgBS049lN3E9cfrHvb3bF/epIQrU0OgfyyxEvIi5n30y+tlRn3y68PY6Qd46t4Y
UN5VZUwvJBgoRy9TGxSkiSRjhxC2PWpLYq/HMzDbcRcFF5dVAIioUd/VZ7fdgBfA
uMW4SFfpFLDUX0aaYe+ZdA5tM0Bc0cOtG8Z0sc9JYDNNcvjmSiGCi646h8F0D3O6
JKAQMMxQGWiyQeJ979LVjtq4lJESXA8VEKz9rV03y5xunmFCLy6dGt+6GJwXgabn
OH7nvEv4GqAOqKc6E9je4JM+AF/oUazrfPse1KEEtsPKarazjCB/SKYtHyDJaavD
GGjtiU9zWwGMOgIDyNmXe3ga7/TWoGOAg5YlTr6Hbq2Y/5ycgjAgPFjuXtvnoT0+
mF5TnNfMAqTgQsE2gjhonK1pdlOen0lN5FtoUXp3CXU0dOq0J70GiX+1YA7VDn30
n5WNAgfOXX3l3E95jGN370pHXyli5RUNW0NZVHV+22jlNWCVtQHUh+DVswQZg+i5
+DqaIHz2jUetMo7gWtqGn/wwSopOs87VM1rcALhZL4EsJ+Zy81I/hA32RNnGbuol
NAiZh+0KrtTcc/fPunpd8vRtOwGphM11dKucozUufuiPG2inR3aEqt5yNx54ec/f
J6nryWRYiHEA/rCU9MSBM9cqKFtEmy9/8oxV41/SPxhXjHwDlABWTtFuJ3pf2sOF
ILSYYFwB0ZGvdjE5yAJFBr9efno/L9fafmGk7a3vmVgK2AmUC9VNB5XHw1GjF8OP
aQAXe4md9Bh0jk/D/iyp7e7IWNssul/7XejhabidWgFj6EXc9YxE59+FlhDqyMhn
V6houc+QeUXuwsAKgRJJhJtpv/QSZ5BI3esxHHUt3ayGnvhFElpAc0t7C/EiXKIv
DAFYP2jksBqijM8YtEgPWYzEP5buYxZnf/LK7FDocLsNcdF38UaKBbeF90e7bR8j
SHspG9aJWICu8Yawnh8zuy/vQv+h9gWyGodd2p9lQzlbRXrutbwfmPf7xP6nzT9i
9GcugJxTaZgkCfhhHxFk/nRHS2NAzagKVib1xkUlZJg2hX0fIFUdYteL1GGTvOx5
m3mTOino4T19z9SEdZYb2OHYh29e/T74bJiLCYdXwevSYHxfZc8pYAf0jp4UnMT2
f7B0ctX1iXuQ2uZVuxh+U1Mcu+v0gDla1jWh7AhcePSi4xBNUCak0kQip6r5e6Oi
r4MIyMRk/Pc5pzEKo8G6nk26rNvX3aRvECoVfmK7IVdsqZ6IXlt9kOmWx3IeKzrO
J5DxpzW+9oIRZJgPTkc4/XRb0tFmFQYTiChiQ1AJUEiCX0GpkFf7cq61aLGYtWyn
vL2lmQhljzjrDo15hKErvk7eBZW7GW/6j/m/PfRdcBI4ceuP9zWQXnDOd9zmaE4b
q3bJ+IbbyVZA2WwyzN7umCKWghsiPMAolxEnYM9JRf8BcqeqQiwVZlfO5KFuN6Ze
le4=
-----END ENCRYPTED PRIVATE KEY-----
Loading