Changeset View
Changeset View
Standalone View
Standalone View
head/contrib/bearssl/samples/server_basic.c
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/* | |||||
* Copyright (c) 2016 Thomas Pornin <pornin@bolet.org> | |||||
* | |||||
* Permission is hereby granted, free of charge, to any person obtaining | |||||
* a copy of this software and associated documentation files (the | |||||
* "Software"), to deal in the Software without restriction, including | |||||
* without limitation the rights to use, copy, modify, merge, publish, | |||||
* distribute, sublicense, and/or sell copies of the Software, and to | |||||
* permit persons to whom the Software is furnished to do so, subject to | |||||
* the following conditions: | |||||
* | |||||
* The above copyright notice and this permission notice shall be | |||||
* included in all copies or substantial portions of the Software. | |||||
* | |||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||
* SOFTWARE. | |||||
*/ | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <stdint.h> | |||||
#include <errno.h> | |||||
#include <signal.h> | |||||
#include <sys/types.h> | |||||
#include <sys/socket.h> | |||||
#include <netdb.h> | |||||
#include <netinet/in.h> | |||||
#include <arpa/inet.h> | |||||
#include <unistd.h> | |||||
#include "bearssl.h" | |||||
/* | |||||
* This sample code can use three possible certificate chains: | |||||
* -- A full-RSA chain (server key is RSA, certificates are signed with RSA) | |||||
* -- A full-EC chain (server key is EC, certificates are signed with ECDSA) | |||||
* -- A mixed chain (server key is EC, certificates are signed with RSA) | |||||
* | |||||
* The macros below define which chain is selected. This impacts the list | |||||
* of supported cipher suites. | |||||
* | |||||
* Other macros, which can be defined (with a non-zero value): | |||||
* | |||||
* SERVER_PROFILE_MIN_FS | |||||
* Select a "minimal" profile with forward security (ECDHE cipher | |||||
* suite). | |||||
* | |||||
* SERVER_PROFILE_MIN_NOFS | |||||
* Select a "minimal" profile without forward security (RSA or ECDH | |||||
* cipher suite, but not ECDHE). | |||||
* | |||||
* SERVER_CHACHA20 | |||||
* If SERVER_PROFILE_MIN_FS is selected, then this macro selects | |||||
* a cipher suite with ChaCha20+Poly1305; otherwise, AES/GCM is | |||||
* used. This macro has no effect otherwise, since there is no | |||||
* non-forward secure cipher suite that uses ChaCha20+Poly1305. | |||||
*/ | |||||
#if !(SERVER_RSA || SERVER_EC || SERVER_MIXED) | |||||
#define SERVER_RSA 1 | |||||
#define SERVER_EC 0 | |||||
#define SERVER_MIXED 0 | |||||
#endif | |||||
#if SERVER_RSA | |||||
#include "chain-rsa.h" | |||||
#include "key-rsa.h" | |||||
#define SKEY RSA | |||||
#elif SERVER_EC | |||||
#include "chain-ec.h" | |||||
#include "key-ec.h" | |||||
#define SKEY EC | |||||
#elif SERVER_MIXED | |||||
#include "chain-ec+rsa.h" | |||||
#include "key-ec.h" | |||||
#define SKEY EC | |||||
#else | |||||
#error Must use one of RSA, EC or MIXED chains. | |||||
#endif | |||||
/* | |||||
* Create a server socket bound to the specified host and port. If 'host' | |||||
* is NULL, this will bind "generically" (all addresses). | |||||
* | |||||
* Returned value is the server socket descriptor, or -1 on error. | |||||
*/ | |||||
static int | |||||
host_bind(const char *host, const char *port) | |||||
{ | |||||
struct addrinfo hints, *si, *p; | |||||
int fd; | |||||
int err; | |||||
memset(&hints, 0, sizeof hints); | |||||
hints.ai_family = PF_UNSPEC; | |||||
hints.ai_socktype = SOCK_STREAM; | |||||
err = getaddrinfo(host, port, &hints, &si); | |||||
if (err != 0) { | |||||
fprintf(stderr, "ERROR: getaddrinfo(): %s\n", | |||||
gai_strerror(err)); | |||||
return -1; | |||||
} | |||||
fd = -1; | |||||
for (p = si; p != NULL; p = p->ai_next) { | |||||
struct sockaddr *sa; | |||||
struct sockaddr_in sa4; | |||||
struct sockaddr_in6 sa6; | |||||
size_t sa_len; | |||||
void *addr; | |||||
char tmp[INET6_ADDRSTRLEN + 50]; | |||||
int opt; | |||||
sa = (struct sockaddr *)p->ai_addr; | |||||
if (sa->sa_family == AF_INET) { | |||||
sa4 = *(struct sockaddr_in *)sa; | |||||
sa = (struct sockaddr *)&sa4; | |||||
sa_len = sizeof sa4; | |||||
addr = &sa4.sin_addr; | |||||
if (host == NULL) { | |||||
sa4.sin_addr.s_addr = INADDR_ANY; | |||||
} | |||||
} else if (sa->sa_family == AF_INET6) { | |||||
sa6 = *(struct sockaddr_in6 *)sa; | |||||
sa = (struct sockaddr *)&sa6; | |||||
sa_len = sizeof sa6; | |||||
addr = &sa6.sin6_addr; | |||||
if (host == NULL) { | |||||
sa6.sin6_addr = in6addr_any; | |||||
} | |||||
} else { | |||||
addr = NULL; | |||||
sa_len = p->ai_addrlen; | |||||
} | |||||
if (addr != NULL) { | |||||
inet_ntop(p->ai_family, addr, tmp, sizeof tmp); | |||||
} else { | |||||
sprintf(tmp, "<unknown family: %d>", | |||||
(int)sa->sa_family); | |||||
} | |||||
fprintf(stderr, "binding to: %s\n", tmp); | |||||
fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); | |||||
if (fd < 0) { | |||||
perror("socket()"); | |||||
continue; | |||||
} | |||||
opt = 1; | |||||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); | |||||
opt = 0; | |||||
setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof opt); | |||||
if (bind(fd, sa, sa_len) < 0) { | |||||
perror("bind()"); | |||||
close(fd); | |||||
continue; | |||||
} | |||||
break; | |||||
} | |||||
if (p == NULL) { | |||||
freeaddrinfo(si); | |||||
fprintf(stderr, "ERROR: failed to bind\n"); | |||||
return -1; | |||||
} | |||||
freeaddrinfo(si); | |||||
if (listen(fd, 5) < 0) { | |||||
perror("listen()"); | |||||
close(fd); | |||||
return -1; | |||||
} | |||||
fprintf(stderr, "bound.\n"); | |||||
return fd; | |||||
} | |||||
/* | |||||
* Accept a single client on the provided server socket. This is blocking. | |||||
* On error, this returns -1. | |||||
*/ | |||||
static int | |||||
accept_client(int server_fd) | |||||
{ | |||||
int fd; | |||||
struct sockaddr sa; | |||||
socklen_t sa_len; | |||||
char tmp[INET6_ADDRSTRLEN + 50]; | |||||
const char *name; | |||||
sa_len = sizeof sa; | |||||
fd = accept(server_fd, &sa, &sa_len); | |||||
if (fd < 0) { | |||||
perror("accept()"); | |||||
return -1; | |||||
} | |||||
name = NULL; | |||||
switch (sa.sa_family) { | |||||
case AF_INET: | |||||
name = inet_ntop(AF_INET, | |||||
&((struct sockaddr_in *)&sa)->sin_addr, | |||||
tmp, sizeof tmp); | |||||
break; | |||||
case AF_INET6: | |||||
name = inet_ntop(AF_INET6, | |||||
&((struct sockaddr_in6 *)&sa)->sin6_addr, | |||||
tmp, sizeof tmp); | |||||
break; | |||||
} | |||||
if (name == NULL) { | |||||
sprintf(tmp, "<unknown: %lu>", (unsigned long)sa.sa_family); | |||||
name = tmp; | |||||
} | |||||
fprintf(stderr, "accepting connection from: %s\n", name); | |||||
return fd; | |||||
} | |||||
/* | |||||
* Low-level data read callback for the simplified SSL I/O API. | |||||
*/ | |||||
static int | |||||
sock_read(void *ctx, unsigned char *buf, size_t len) | |||||
{ | |||||
for (;;) { | |||||
ssize_t rlen; | |||||
rlen = read(*(int *)ctx, buf, len); | |||||
if (rlen <= 0) { | |||||
if (rlen < 0 && errno == EINTR) { | |||||
continue; | |||||
} | |||||
return -1; | |||||
} | |||||
return (int)rlen; | |||||
} | |||||
} | |||||
/* | |||||
* Low-level data write callback for the simplified SSL I/O API. | |||||
*/ | |||||
static int | |||||
sock_write(void *ctx, const unsigned char *buf, size_t len) | |||||
{ | |||||
for (;;) { | |||||
ssize_t wlen; | |||||
wlen = write(*(int *)ctx, buf, len); | |||||
if (wlen <= 0) { | |||||
if (wlen < 0 && errno == EINTR) { | |||||
continue; | |||||
} | |||||
return -1; | |||||
} | |||||
return (int)wlen; | |||||
} | |||||
} | |||||
/* | |||||
* Sample HTTP response to send. | |||||
*/ | |||||
static const char *HTTP_RES = | |||||
"HTTP/1.0 200 OK\r\n" | |||||
"Content-Length: 46\r\n" | |||||
"Connection: close\r\n" | |||||
"Content-Type: text/html; charset=iso-8859-1\r\n" | |||||
"\r\n" | |||||
"<html>\r\n" | |||||
"<body>\r\n" | |||||
"<p>Test!</p>\r\n" | |||||
"</body>\r\n" | |||||
"</html>\r\n"; | |||||
/* | |||||
* Main program: this is a simple program that expects 1 argument: a | |||||
* port number. This will start a simple network server on that port, | |||||
* that expects incoming SSL clients. It handles only one client at a | |||||
* time (handling several would require threads, sub-processes, or | |||||
* multiplexing with select()/poll(), all of which being possible). | |||||
* | |||||
* For each client, the server will wait for two successive newline | |||||
* characters (ignoring CR characters, so CR+LF is accepted), then | |||||
* produce a sample static HTTP response. This is very crude, but | |||||
* sufficient for explanatory purposes. | |||||
*/ | |||||
int | |||||
main(int argc, char *argv[]) | |||||
{ | |||||
const char *port; | |||||
int fd; | |||||
if (argc != 2) { | |||||
return EXIT_FAILURE; | |||||
} | |||||
port = argv[1]; | |||||
/* | |||||
* Ignore SIGPIPE to avoid crashing in case of abrupt socket close. | |||||
*/ | |||||
signal(SIGPIPE, SIG_IGN); | |||||
/* | |||||
* Open the server socket. | |||||
*/ | |||||
fd = host_bind(NULL, port); | |||||
if (fd < 0) { | |||||
return EXIT_FAILURE; | |||||
} | |||||
/* | |||||
* Process each client, one at a time. | |||||
*/ | |||||
for (;;) { | |||||
int cfd; | |||||
br_ssl_server_context sc; | |||||
unsigned char iobuf[BR_SSL_BUFSIZE_BIDI]; | |||||
br_sslio_context ioc; | |||||
int lcwn, err; | |||||
cfd = accept_client(fd); | |||||
if (cfd < 0) { | |||||
return EXIT_FAILURE; | |||||
} | |||||
/* | |||||
* Initialise the context with the cipher suites and | |||||
* algorithms. This depends on the server key type | |||||
* (and, for EC keys, the signature algorithm used by | |||||
* the CA to sign the server's certificate). | |||||
* | |||||
* Depending on the defined macros, we may select one of | |||||
* the "minimal" profiles. Key exchange algorithm depends | |||||
* on the key type: | |||||
* RSA key: RSA or ECDHE_RSA | |||||
* EC key, cert signed with ECDSA: ECDH_ECDSA or ECDHE_ECDSA | |||||
* EC key, cert signed with RSA: ECDH_RSA or ECDHE_ECDSA | |||||
*/ | |||||
#if SERVER_RSA | |||||
#if SERVER_PROFILE_MIN_FS | |||||
#if SERVER_CHACHA20 | |||||
br_ssl_server_init_mine2c(&sc, CHAIN, CHAIN_LEN, &SKEY); | |||||
#else | |||||
br_ssl_server_init_mine2g(&sc, CHAIN, CHAIN_LEN, &SKEY); | |||||
#endif | |||||
#elif SERVER_PROFILE_MIN_NOFS | |||||
br_ssl_server_init_minr2g(&sc, CHAIN, CHAIN_LEN, &SKEY); | |||||
#else | |||||
br_ssl_server_init_full_rsa(&sc, CHAIN, CHAIN_LEN, &SKEY); | |||||
#endif | |||||
#elif SERVER_EC | |||||
#if SERVER_PROFILE_MIN_FS | |||||
#if SERVER_CHACHA20 | |||||
br_ssl_server_init_minf2c(&sc, CHAIN, CHAIN_LEN, &SKEY); | |||||
#else | |||||
br_ssl_server_init_minf2g(&sc, CHAIN, CHAIN_LEN, &SKEY); | |||||
#endif | |||||
#elif SERVER_PROFILE_MIN_NOFS | |||||
br_ssl_server_init_minv2g(&sc, CHAIN, CHAIN_LEN, &SKEY); | |||||
#else | |||||
br_ssl_server_init_full_ec(&sc, CHAIN, CHAIN_LEN, | |||||
BR_KEYTYPE_EC, &SKEY); | |||||
#endif | |||||
#else /* SERVER_MIXED */ | |||||
#if SERVER_PROFILE_MIN_FS | |||||
#if SERVER_CHACHA20 | |||||
br_ssl_server_init_minf2c(&sc, CHAIN, CHAIN_LEN, &SKEY); | |||||
#else | |||||
br_ssl_server_init_minf2g(&sc, CHAIN, CHAIN_LEN, &SKEY); | |||||
#endif | |||||
#elif SERVER_PROFILE_MIN_NOFS | |||||
br_ssl_server_init_minu2g(&sc, CHAIN, CHAIN_LEN, &SKEY); | |||||
#else | |||||
br_ssl_server_init_full_ec(&sc, CHAIN, CHAIN_LEN, | |||||
BR_KEYTYPE_RSA, &SKEY); | |||||
#endif | |||||
#endif | |||||
/* | |||||
* Set the I/O buffer to the provided array. We | |||||
* allocated a buffer large enough for full-duplex | |||||
* behaviour with all allowed sizes of SSL records, | |||||
* hence we set the last argument to 1 (which means | |||||
* "split the buffer into separate input and output | |||||
* areas"). | |||||
*/ | |||||
br_ssl_engine_set_buffer(&sc.eng, iobuf, sizeof iobuf, 1); | |||||
/* | |||||
* Reset the server context, for a new handshake. | |||||
*/ | |||||
br_ssl_server_reset(&sc); | |||||
/* | |||||
* Initialise the simplified I/O wrapper context. | |||||
*/ | |||||
br_sslio_init(&ioc, &sc.eng, sock_read, &cfd, sock_write, &cfd); | |||||
/* | |||||
* Read bytes until two successive LF (or CR+LF) are received. | |||||
*/ | |||||
lcwn = 0; | |||||
for (;;) { | |||||
unsigned char x; | |||||
if (br_sslio_read(&ioc, &x, 1) < 0) { | |||||
goto client_drop; | |||||
} | |||||
if (x == 0x0D) { | |||||
continue; | |||||
} | |||||
if (x == 0x0A) { | |||||
if (lcwn) { | |||||
break; | |||||
} | |||||
lcwn = 1; | |||||
} else { | |||||
lcwn = 0; | |||||
} | |||||
} | |||||
/* | |||||
* Write a response and close the connection. | |||||
*/ | |||||
br_sslio_write_all(&ioc, HTTP_RES, strlen(HTTP_RES)); | |||||
br_sslio_close(&ioc); | |||||
client_drop: | |||||
err = br_ssl_engine_last_error(&sc.eng); | |||||
if (err == 0) { | |||||
fprintf(stderr, "SSL closed (correctly).\n"); | |||||
} else { | |||||
fprintf(stderr, "SSL error: %d\n", err); | |||||
} | |||||
close(cfd); | |||||
} | |||||
} |