1 | /*
|
---|
2 | * Copyright 2022-2024 The OpenSSL Project Authors. All Rights Reserved.
|
---|
3 | *
|
---|
4 | * Licensed under the Apache License 2.0 (the "License"). You may not use
|
---|
5 | * this file except in compliance with the License. You can obtain a copy
|
---|
6 | * in the file LICENSE in the source distribution or at
|
---|
7 | * https://www.openssl.org/source/license.html
|
---|
8 | */
|
---|
9 |
|
---|
10 | #include <stdio.h>
|
---|
11 | #include <unistd.h>
|
---|
12 | #include <string.h>
|
---|
13 | #include <sys/socket.h>
|
---|
14 | #include <arpa/inet.h>
|
---|
15 | #include <netinet/in.h>
|
---|
16 | #include <openssl/ssl.h>
|
---|
17 | #include <openssl/err.h>
|
---|
18 | #include <signal.h>
|
---|
19 |
|
---|
20 | static const int server_port = 4433;
|
---|
21 |
|
---|
22 | typedef unsigned char bool;
|
---|
23 | #define true 1
|
---|
24 | #define false 0
|
---|
25 |
|
---|
26 | /*
|
---|
27 | * This flag won't be useful until both accept/read (TCP & SSL) methods
|
---|
28 | * can be called with a timeout. TBD.
|
---|
29 | */
|
---|
30 | static volatile bool server_running = true;
|
---|
31 |
|
---|
32 | int create_socket(bool isServer)
|
---|
33 | {
|
---|
34 | int s;
|
---|
35 | int optval = 1;
|
---|
36 | struct sockaddr_in addr;
|
---|
37 |
|
---|
38 | s = socket(AF_INET, SOCK_STREAM, 0);
|
---|
39 | if (s < 0) {
|
---|
40 | perror("Unable to create socket");
|
---|
41 | exit(EXIT_FAILURE);
|
---|
42 | }
|
---|
43 |
|
---|
44 | if (isServer) {
|
---|
45 | addr.sin_family = AF_INET;
|
---|
46 | addr.sin_port = htons(server_port);
|
---|
47 | addr.sin_addr.s_addr = INADDR_ANY;
|
---|
48 |
|
---|
49 | /* Reuse the address; good for quick restarts */
|
---|
50 | if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
|
---|
51 | < 0) {
|
---|
52 | perror("setsockopt(SO_REUSEADDR) failed");
|
---|
53 | exit(EXIT_FAILURE);
|
---|
54 | }
|
---|
55 |
|
---|
56 | if (bind(s, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
|
---|
57 | perror("Unable to bind");
|
---|
58 | exit(EXIT_FAILURE);
|
---|
59 | }
|
---|
60 |
|
---|
61 | if (listen(s, 1) < 0) {
|
---|
62 | perror("Unable to listen");
|
---|
63 | exit(EXIT_FAILURE);
|
---|
64 | }
|
---|
65 | }
|
---|
66 |
|
---|
67 | return s;
|
---|
68 | }
|
---|
69 |
|
---|
70 | SSL_CTX* create_context(bool isServer)
|
---|
71 | {
|
---|
72 | const SSL_METHOD *method;
|
---|
73 | SSL_CTX *ctx;
|
---|
74 |
|
---|
75 | if (isServer)
|
---|
76 | method = TLS_server_method();
|
---|
77 | else
|
---|
78 | method = TLS_client_method();
|
---|
79 |
|
---|
80 | ctx = SSL_CTX_new(method);
|
---|
81 | if (ctx == NULL) {
|
---|
82 | perror("Unable to create SSL context");
|
---|
83 | ERR_print_errors_fp(stderr);
|
---|
84 | exit(EXIT_FAILURE);
|
---|
85 | }
|
---|
86 |
|
---|
87 | return ctx;
|
---|
88 | }
|
---|
89 |
|
---|
90 | void configure_server_context(SSL_CTX *ctx)
|
---|
91 | {
|
---|
92 | /* Set the key and cert */
|
---|
93 | if (SSL_CTX_use_certificate_chain_file(ctx, "cert.pem") <= 0) {
|
---|
94 | ERR_print_errors_fp(stderr);
|
---|
95 | exit(EXIT_FAILURE);
|
---|
96 | }
|
---|
97 |
|
---|
98 | if (SSL_CTX_use_PrivateKey_file(ctx, "key.pem", SSL_FILETYPE_PEM) <= 0) {
|
---|
99 | ERR_print_errors_fp(stderr);
|
---|
100 | exit(EXIT_FAILURE);
|
---|
101 | }
|
---|
102 | }
|
---|
103 |
|
---|
104 | void configure_client_context(SSL_CTX *ctx)
|
---|
105 | {
|
---|
106 | /*
|
---|
107 | * Configure the client to abort the handshake if certificate verification
|
---|
108 | * fails
|
---|
109 | */
|
---|
110 | SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
|
---|
111 | /*
|
---|
112 | * In a real application you would probably just use the default system certificate trust store and call:
|
---|
113 | * SSL_CTX_set_default_verify_paths(ctx);
|
---|
114 | * In this demo though we are using a self-signed certificate, so the client must trust it directly.
|
---|
115 | */
|
---|
116 | if (!SSL_CTX_load_verify_locations(ctx, "cert.pem", NULL)) {
|
---|
117 | ERR_print_errors_fp(stderr);
|
---|
118 | exit(EXIT_FAILURE);
|
---|
119 | }
|
---|
120 | }
|
---|
121 |
|
---|
122 | void usage(void)
|
---|
123 | {
|
---|
124 | printf("Usage: sslecho s\n");
|
---|
125 | printf(" --or--\n");
|
---|
126 | printf(" sslecho c ip\n");
|
---|
127 | printf(" c=client, s=server, ip=dotted ip of server\n");
|
---|
128 | exit(EXIT_FAILURE);
|
---|
129 | }
|
---|
130 |
|
---|
131 | int main(int argc, char **argv)
|
---|
132 | {
|
---|
133 | bool isServer;
|
---|
134 | int result;
|
---|
135 |
|
---|
136 | SSL_CTX *ssl_ctx = NULL;
|
---|
137 | SSL *ssl = NULL;
|
---|
138 |
|
---|
139 | int server_skt = -1;
|
---|
140 | int client_skt = -1;
|
---|
141 |
|
---|
142 | /* used by getline relying on realloc, can't be statically allocated */
|
---|
143 | char *txbuf = NULL;
|
---|
144 | size_t txcap = 0;
|
---|
145 | int txlen;
|
---|
146 |
|
---|
147 | char rxbuf[128];
|
---|
148 | size_t rxcap = sizeof(rxbuf);
|
---|
149 | int rxlen;
|
---|
150 |
|
---|
151 | char *rem_server_ip = NULL;
|
---|
152 |
|
---|
153 | struct sockaddr_in addr;
|
---|
154 | unsigned int addr_len = sizeof(addr);
|
---|
155 |
|
---|
156 | /* ignore SIGPIPE so that server can continue running when client pipe closes abruptly */
|
---|
157 | signal(SIGPIPE, SIG_IGN);
|
---|
158 |
|
---|
159 | /* Splash */
|
---|
160 | printf("\nsslecho : Simple Echo Client/Server : %s : %s\n\n", __DATE__,
|
---|
161 | __TIME__);
|
---|
162 |
|
---|
163 | /* Need to know if client or server */
|
---|
164 | if (argc < 2) {
|
---|
165 | usage();
|
---|
166 | /* NOTREACHED */
|
---|
167 | }
|
---|
168 | isServer = (argv[1][0] == 's') ? true : false;
|
---|
169 | /* If client get remote server address (could be 127.0.0.1) */
|
---|
170 | if (!isServer) {
|
---|
171 | if (argc != 3) {
|
---|
172 | usage();
|
---|
173 | /* NOTREACHED */
|
---|
174 | }
|
---|
175 | rem_server_ip = argv[2];
|
---|
176 | }
|
---|
177 |
|
---|
178 | /* Create context used by both client and server */
|
---|
179 | ssl_ctx = create_context(isServer);
|
---|
180 |
|
---|
181 | /* If server */
|
---|
182 | if (isServer) {
|
---|
183 |
|
---|
184 | printf("We are the server on port: %d\n\n", server_port);
|
---|
185 |
|
---|
186 | /* Configure server context with appropriate key files */
|
---|
187 | configure_server_context(ssl_ctx);
|
---|
188 |
|
---|
189 | /* Create server socket; will bind with server port and listen */
|
---|
190 | server_skt = create_socket(true);
|
---|
191 |
|
---|
192 | /*
|
---|
193 | * Loop to accept clients.
|
---|
194 | * Need to implement timeouts on TCP & SSL connect/read functions
|
---|
195 | * before we can catch a CTRL-C and kill the server.
|
---|
196 | */
|
---|
197 | while (server_running) {
|
---|
198 | /* Wait for TCP connection from client */
|
---|
199 | client_skt = accept(server_skt, (struct sockaddr*) &addr,
|
---|
200 | &addr_len);
|
---|
201 | if (client_skt < 0) {
|
---|
202 | perror("Unable to accept");
|
---|
203 | exit(EXIT_FAILURE);
|
---|
204 | }
|
---|
205 |
|
---|
206 | printf("Client TCP connection accepted\n");
|
---|
207 |
|
---|
208 | /* Create server SSL structure using newly accepted client socket */
|
---|
209 | ssl = SSL_new(ssl_ctx);
|
---|
210 | SSL_set_fd(ssl, client_skt);
|
---|
211 |
|
---|
212 | /* Wait for SSL connection from the client */
|
---|
213 | if (SSL_accept(ssl) <= 0) {
|
---|
214 | ERR_print_errors_fp(stderr);
|
---|
215 | server_running = false;
|
---|
216 | } else {
|
---|
217 |
|
---|
218 | printf("Client SSL connection accepted\n\n");
|
---|
219 |
|
---|
220 | /* Echo loop */
|
---|
221 | while (true) {
|
---|
222 | /* Get message from client; will fail if client closes connection */
|
---|
223 | if ((rxlen = SSL_read(ssl, rxbuf, rxcap)) <= 0) {
|
---|
224 | if (rxlen == 0) {
|
---|
225 | printf("Client closed connection\n");
|
---|
226 | } else {
|
---|
227 | printf("SSL_read returned %d\n", rxlen);
|
---|
228 | }
|
---|
229 | ERR_print_errors_fp(stderr);
|
---|
230 | break;
|
---|
231 | }
|
---|
232 | /* Insure null terminated input */
|
---|
233 | rxbuf[rxlen] = 0;
|
---|
234 | /* Look for kill switch */
|
---|
235 | if (strcmp(rxbuf, "kill\n") == 0) {
|
---|
236 | /* Terminate...with extreme prejudice */
|
---|
237 | printf("Server received 'kill' command\n");
|
---|
238 | server_running = false;
|
---|
239 | break;
|
---|
240 | }
|
---|
241 | /* Show received message */
|
---|
242 | printf("Received: %s", rxbuf);
|
---|
243 | /* Echo it back */
|
---|
244 | if (SSL_write(ssl, rxbuf, rxlen) <= 0) {
|
---|
245 | ERR_print_errors_fp(stderr);
|
---|
246 | }
|
---|
247 | }
|
---|
248 | }
|
---|
249 | if (server_running) {
|
---|
250 | /* Cleanup for next client */
|
---|
251 | SSL_shutdown(ssl);
|
---|
252 | SSL_free(ssl);
|
---|
253 | close(client_skt);
|
---|
254 | }
|
---|
255 | }
|
---|
256 | printf("Server exiting...\n");
|
---|
257 | }
|
---|
258 | /* Else client */
|
---|
259 | else {
|
---|
260 |
|
---|
261 | printf("We are the client\n\n");
|
---|
262 |
|
---|
263 | /* Configure client context so we verify the server correctly */
|
---|
264 | configure_client_context(ssl_ctx);
|
---|
265 |
|
---|
266 | /* Create "bare" socket */
|
---|
267 | client_skt = create_socket(false);
|
---|
268 | /* Set up connect address */
|
---|
269 | addr.sin_family = AF_INET;
|
---|
270 | inet_pton(AF_INET, rem_server_ip, &addr.sin_addr.s_addr);
|
---|
271 | addr.sin_port = htons(server_port);
|
---|
272 | /* Do TCP connect with server */
|
---|
273 | if (connect(client_skt, (struct sockaddr*) &addr, sizeof(addr)) != 0) {
|
---|
274 | perror("Unable to TCP connect to server");
|
---|
275 | goto exit;
|
---|
276 | } else {
|
---|
277 | printf("TCP connection to server successful\n");
|
---|
278 | }
|
---|
279 |
|
---|
280 | /* Create client SSL structure using dedicated client socket */
|
---|
281 | ssl = SSL_new(ssl_ctx);
|
---|
282 | SSL_set_fd(ssl, client_skt);
|
---|
283 | /* Set hostname for SNI */
|
---|
284 | SSL_set_tlsext_host_name(ssl, rem_server_ip);
|
---|
285 | /* Configure server hostname check */
|
---|
286 | SSL_set1_host(ssl, rem_server_ip);
|
---|
287 |
|
---|
288 | /* Now do SSL connect with server */
|
---|
289 | if (SSL_connect(ssl) == 1) {
|
---|
290 |
|
---|
291 | printf("SSL connection to server successful\n\n");
|
---|
292 |
|
---|
293 | /* Loop to send input from keyboard */
|
---|
294 | while (true) {
|
---|
295 | /* Get a line of input */
|
---|
296 | txlen = getline(&txbuf, &txcap, stdin);
|
---|
297 | /* Exit loop on error */
|
---|
298 | if (txlen < 0 || txbuf == NULL) {
|
---|
299 | break;
|
---|
300 | }
|
---|
301 | /* Exit loop if just a carriage return */
|
---|
302 | if (txbuf[0] == '\n') {
|
---|
303 | break;
|
---|
304 | }
|
---|
305 | /* Send it to the server */
|
---|
306 | if ((result = SSL_write(ssl, txbuf, txlen)) <= 0) {
|
---|
307 | printf("Server closed connection\n");
|
---|
308 | ERR_print_errors_fp(stderr);
|
---|
309 | break;
|
---|
310 | }
|
---|
311 |
|
---|
312 | /* Wait for the echo */
|
---|
313 | rxlen = SSL_read(ssl, rxbuf, rxcap);
|
---|
314 | if (rxlen <= 0) {
|
---|
315 | printf("Server closed connection\n");
|
---|
316 | ERR_print_errors_fp(stderr);
|
---|
317 | break;
|
---|
318 | } else {
|
---|
319 | /* Show it */
|
---|
320 | rxbuf[rxlen] = 0;
|
---|
321 | printf("Received: %s", rxbuf);
|
---|
322 | }
|
---|
323 | }
|
---|
324 | printf("Client exiting...\n");
|
---|
325 | } else {
|
---|
326 |
|
---|
327 | printf("SSL connection to server failed\n\n");
|
---|
328 |
|
---|
329 | ERR_print_errors_fp(stderr);
|
---|
330 | }
|
---|
331 | }
|
---|
332 | exit:
|
---|
333 | /* Close up */
|
---|
334 | if (ssl != NULL) {
|
---|
335 | SSL_shutdown(ssl);
|
---|
336 | SSL_free(ssl);
|
---|
337 | }
|
---|
338 | SSL_CTX_free(ssl_ctx);
|
---|
339 |
|
---|
340 | if (client_skt != -1)
|
---|
341 | close(client_skt);
|
---|
342 | if (server_skt != -1)
|
---|
343 | close(server_skt);
|
---|
344 |
|
---|
345 | if (txbuf != NULL && txcap > 0)
|
---|
346 | free(txbuf);
|
---|
347 |
|
---|
348 | printf("sslecho exiting\n");
|
---|
349 |
|
---|
350 | return EXIT_SUCCESS;
|
---|
351 | }
|
---|