Skip to content

Commit 67f3786

Browse files
committed
Add example sslecho, ported from OpenSSL
1 parent 0d96a18 commit 67f3786

File tree

5 files changed

+344
-0
lines changed

5 files changed

+344
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
/.dub/
66
/openssl
77
/*-test-library
8+
9+
/examples/sslecho/sslecho

examples/sslecho/.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.dub
2+
docs.json
3+
__dummy.html
4+
docs/
5+
/sslecho
6+
sslecho.so
7+
sslecho.dylib
8+
sslecho.dll
9+
sslecho.a
10+
sslecho.lib
11+
sslecho-test-*
12+
*.exe
13+
*.o
14+
*.obj
15+
*.lst

examples/sslecho/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# sslecho: A simple echo server
2+
3+
This example was ported from [the official OpenSSL repository](https://github.com/openssl/openssl/tree/ef8040bce02758de86fc55412ee4ac9102f9ffab/demos/sslecho).
4+
The certificates can be generated using:
5+
```shell
6+
openssl req -batch -newkey rsa:4096 -x509 -sha256 -days 3650 -subj "/C=FR/CN=localhost" -nodes -out cert.pem -keyout key.pem
7+
```
8+
And 'localhost' was used as domain.
9+
10+
The server and client need to be called with the following commands (respectively):
11+
```shell
12+
$ ./sslecho s # Starts the server
13+
$ ./sslecho c localhost
14+
```
15+
Note that using `127.0.0.1` will not work.

examples/sslecho/dub.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "sslecho",
3+
"description": "Port of OpenSSL demo of the same name",
4+
5+
"dependencies": {
6+
"openssl": {
7+
"path": "../../"
8+
}
9+
}
10+
}

examples/sslecho/source/app.d

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
/// https://github.com/openssl/openssl/tree/master/demos/sslecho
2+
module app;
3+
4+
import core.stdc.stdio;
5+
import core.sys.posix.netinet.in_;
6+
import core.sys.posix.stdio : getline;
7+
import core.sys.posix.unistd;
8+
9+
import std.algorithm : startsWith;
10+
import std.stdio : write, writeln, writefln;
11+
12+
import deimos.openssl.opensslv;
13+
import deimos.openssl.err;
14+
import deimos.openssl.ssl;
15+
16+
const ushort server_port = 4433;
17+
18+
int create_socket(bool isServer)
19+
{
20+
int optval = 1;
21+
sockaddr_in addr;
22+
23+
int s = socket(AF_INET, SOCK_STREAM, 0);
24+
if (s < 0) {
25+
perror("Unable to create socket");
26+
return -1;
27+
}
28+
29+
if (!isServer)
30+
return s;
31+
32+
addr.sin_family = AF_INET;
33+
addr.sin_port = htons(server_port);
34+
addr.sin_addr.s_addr = INADDR_ANY;
35+
36+
/* Reuse the address; good for quick restarts */
37+
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, optval.sizeof) < 0) {
38+
perror("setsockopt(SO_REUSEADDR) failed");
39+
return -1;
40+
}
41+
42+
if (bind(s, cast(sockaddr*) &addr, addr.sizeof) < 0) {
43+
perror("Unable to bind");
44+
return -1;
45+
}
46+
47+
if (listen(s, 1) < 0) {
48+
perror("Unable to listen");
49+
return -1;
50+
}
51+
52+
return s;
53+
}
54+
55+
private int usage()
56+
{
57+
writeln("Usage: sslecho s");
58+
writeln(" --or--");
59+
writeln(" sslecho c ip");
60+
writeln(" c=client, s=server, ip=dotted ip of server");
61+
return 1;
62+
}
63+
64+
int main(string[] args)
65+
{
66+
int result;
67+
68+
/* used by getline relying on realloc, can't be statically allocated */
69+
// char *txbuf = NULL;
70+
// size_t txcap = 0;
71+
// int txlen;
72+
73+
// struct sockaddr_in addr;
74+
// unsigned int addr_len = sizeof(addr);
75+
76+
/* Splash */
77+
writefln("sslecho : Simple Echo Client/Server (OpenSSL %s): %s %s",
78+
OpenSSLVersion.text, __DATE__, __TIME__);
79+
80+
/* Need to know if client or server */
81+
if (args.length < 2)
82+
return usage();
83+
84+
if (const isServer = args[1].startsWith('s'))
85+
return runServer();
86+
87+
/* If client get remote server address (should be localhost) */
88+
if (args.length != 3)
89+
return usage();
90+
91+
const remote_server_ip = args[2];
92+
return runClient(remote_server_ip);
93+
}
94+
95+
private int runServer (ushort port = server_port)
96+
{
97+
char[256] buffer;
98+
int result;
99+
100+
writeln("We are the server on port: ", port);
101+
102+
const SSL_METHOD* method = TLS_server_method();
103+
SSL_CTX* ctx = SSL_CTX_new(method);
104+
if (ctx is null)
105+
{
106+
perror("Unable to create SSL context");
107+
ERR_print_errors_fp(stderr);
108+
return 1;
109+
}
110+
scope (exit) SSL_CTX_free(ctx);
111+
112+
/* Configure server context with appropriate key files */
113+
if (SSL_CTX_use_certificate_chain_file(ctx, "cert.pem".ptr) <= 0) {
114+
ERR_print_errors_fp(stderr);
115+
return 1;
116+
}
117+
if (SSL_CTX_use_PrivateKey_file(ctx, "key.pem".ptr, SSL_FILETYPE_PEM) <= 0) {
118+
ERR_print_errors_fp(stderr);
119+
return 1;
120+
}
121+
122+
/* Create server socket; will bind with server port and listen */
123+
int server_skt = create_socket(true);
124+
if (server_skt < 0) return 1;
125+
scope (exit) close(server_skt);
126+
127+
/*
128+
* Loop to accept clients.
129+
* Need to implement timeouts on TCP & SSL connect/read functions
130+
* before we can catch a CTRL-C and kill the server.
131+
*/
132+
while (true) {
133+
/* Wait for TCP connection from client */
134+
sockaddr_in addr;
135+
socklen_t addr_len = sockaddr_in.sizeof;
136+
int client_skt = accept(server_skt, cast(sockaddr*) &addr, &addr_len);
137+
138+
if (client_skt < 0) {
139+
perror("Unable to accept");
140+
return 1;
141+
}
142+
143+
writeln("Client TCP connection accepted");
144+
scope (exit) close(client_skt);
145+
146+
/* Create server SSL structure using newly accepted client socket */
147+
SSL* ssl = SSL_new(ctx);
148+
SSL_set_fd(ssl, client_skt);
149+
scope (exit) {
150+
/* Cleanup for next client */
151+
SSL_shutdown(ssl);
152+
SSL_free(ssl);
153+
}
154+
155+
/* Wait for SSL connection from the client */
156+
if (SSL_accept(ssl) <= 0) {
157+
ERR_print_errors_fp(stderr);
158+
break;
159+
}
160+
161+
writeln("Client SSL connection accepted");
162+
163+
/* Echo loop */
164+
while (true) {
165+
/* Get message from client; will fail if client closes connection */
166+
if ((result = SSL_read(ssl, buffer.ptr, buffer.length)) <= 0) {
167+
if (result == 0) {
168+
writeln("Client closed connection");
169+
}
170+
ERR_print_errors_fp(stderr);
171+
break;
172+
}
173+
174+
const rcvd = buffer[0 .. result];
175+
/* Look for kill switch */
176+
if (rcvd == "kill\n") {
177+
/* Terminate...with extreme prejudice */
178+
writeln("Server received 'kill' command");
179+
return 0;
180+
}
181+
/* Show received message */
182+
writefln("Received %s bytes:", rcvd.length);
183+
write(rcvd);
184+
/* Echo it back */
185+
if (SSL_write(ssl, rcvd.ptr, result) <= 0) {
186+
ERR_print_errors_fp(stderr);
187+
}
188+
}
189+
}
190+
writeln("Server exiting...");
191+
return 0;
192+
}
193+
194+
private int runClient (string remote)
195+
{
196+
char[256] buffer;
197+
writeln("We are the client");
198+
199+
const SSL_METHOD* method = TLS_client_method();
200+
SSL_CTX* ctx = SSL_CTX_new(method);
201+
if (ctx is null)
202+
{
203+
perror("Unable to create SSL context");
204+
ERR_print_errors_fp(stderr);
205+
return 1;
206+
}
207+
scope (exit) SSL_CTX_free(ctx);
208+
209+
/*
210+
* Configure the client to abort the handshake if certificate verification
211+
* fails
212+
*/
213+
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, null);
214+
/*
215+
* In a real application you would probably just use the default system certificate trust store and call:
216+
* SSL_CTX_set_default_verify_paths(ctx);
217+
* In this demo though we are using a self-signed certificate, so the client must trust it directly.
218+
*/
219+
if (!SSL_CTX_load_verify_locations(ctx, "cert.pem".ptr, null)) {
220+
ERR_print_errors_fp(stderr);
221+
return 1;
222+
}
223+
224+
/* Create "bare" socket */
225+
int client_skt = create_socket(false);
226+
if (client_skt < 0) return 1;
227+
scope (exit) close(client_skt);
228+
229+
/* Set up connect address */
230+
sockaddr_in addr;
231+
addr.sin_family = AF_INET;
232+
// Note: The runtime ensures that `args` are `\0` terminated
233+
inet_pton(AF_INET, remote.ptr, &addr.sin_addr.s_addr);
234+
addr.sin_port = htons(server_port);
235+
/* Do TCP connect with server */
236+
if (connect(client_skt, cast(sockaddr*) &addr, addr.sizeof) != 0) {
237+
perror("Unable to TCP connect to server");
238+
return 1;
239+
}
240+
writeln("TCP connection to server successful");
241+
242+
/* Create client SSL structure using dedicated client socket */
243+
SSL* ssl = SSL_new(ctx);
244+
SSL_set_fd(ssl, client_skt);
245+
scope (exit)
246+
{
247+
SSL_shutdown(ssl);
248+
SSL_free(ssl);
249+
}
250+
251+
/* Set host name for SNI */
252+
SSL_set_tlsext_host_name(ssl, remote.ptr);
253+
/* Configure server hostname check */
254+
static if (OPENSSL_VERSION_AT_LEAST(1, 1, 0))
255+
SSL_set1_host(ssl, remote.ptr);
256+
257+
/* Now do SSL connect with server */
258+
if (SSL_connect(ssl) != 1) {
259+
writeln("SSL connection to server failed");
260+
ERR_print_errors_fp(stderr);
261+
return 1;
262+
}
263+
264+
writeln("SSL connection to server successful");
265+
266+
/* Loop to send input from keyboard */
267+
while (true) {
268+
/* Get a line of input */
269+
auto len = buffer.length;
270+
auto pptr = buffer.ptr;
271+
ssize_t txlen = getline(&pptr, &len, stdin);
272+
273+
/* Exit loop on error */
274+
if (txlen < 1 || pptr is null)
275+
break;
276+
/* Exit loop if just a carriage return */
277+
if (buffer[0] == '\n')
278+
break;
279+
assert(txlen <= int.max);
280+
281+
/* Send it to the server */
282+
auto result = SSL_write(ssl, buffer.ptr, cast(int) txlen);
283+
if (result <= 0) {
284+
writeln("Server closed connection");
285+
ERR_print_errors_fp(stderr);
286+
break;
287+
}
288+
289+
/* Wait for the echo */
290+
auto rxlen = SSL_read(ssl, buffer.ptr, cast(int) buffer.length);
291+
if (rxlen <= 0) {
292+
writeln("Server closed connection");
293+
ERR_print_errors_fp(stderr);
294+
break;
295+
}
296+
/* Show it */
297+
writefln("Received %s bytes (sent: %s bytes):", rxlen, txlen);
298+
writeln(buffer[0 .. rxlen]);
299+
}
300+
writeln("Client exiting...");
301+
return 0;
302+
}

0 commit comments

Comments
 (0)