diff --git a/.github/examples-manifest.yml b/.github/examples-manifest.yml index d36a76a5c..ea79bea78 100644 --- a/.github/examples-manifest.yml +++ b/.github/examples-manifest.yml @@ -841,6 +841,16 @@ examples: stdin: "shutdown\n" server_exit: clean expect: "Server: I hear ya fa shizzle" + # server-cert.pem has SAN DNS:example.com, so wolfSSL skips the CN and the + # client's domain check would already reject it for www.wolfssl.com. + # OU=ECC (printed via KEEP_PEER_CERT, set by opensslall) also shows + # explicitly that the server picked the non-default vhost from the SNI. + - pair: + server: [./server-tls-sni] + client: [./client-tls-sni, 127.0.0.1, www.wolfssl.com] + stdin: "shutdown\n" + server_exit: clean + expect: "OU=ECC" - pair: server: [./server-tls-threaded] client: [./client-tls, 127.0.0.1] diff --git a/.gitignore b/.gitignore index 145f1b5f0..c32a807db 100644 --- a/.gitignore +++ b/.gitignore @@ -122,6 +122,8 @@ android/wolfssljni-ndk-sample/proguard-project.txt /tls/server-tls-writedup /tls/client-tls13-certauth-clienthello /tls/server-tls13-certauth-clienthello +/tls/server-tls-sni +/tls/client-tls-sni /tls/client-ech /tls/client-ech-local /tls/server-ech-local diff --git a/tls/README.md b/tls/README.md index 5b0513d6d..f815f8e73 100644 --- a/tls/README.md +++ b/tls/README.md @@ -119,6 +119,7 @@ into. 3. [Running](#run-ecc) 6. [Encrypted Client Hello](#ech) +7. [Server Name Indication](#sni) @@ -1488,6 +1489,98 @@ The printed base64 config can then be passed directly to `client-ech-local`: ./client-ech-local ``` +## Server Name Indication + +`client-tls-sni` and `server-tls-sni` demonstrate Server Name Indication +(SNI, RFC 6066): the client names the host it wants in the ClientHello, and a +server hosting several names on one port presents the matching certificate. + +`server-tls-sni` listens on port 11111 and serves two names, each from its own +`WOLFSSL_CTX`: + +| Host name | Certificate | +|---|---| +| `example.com` (default) | `../certs/server-cert.pem` (RSA) | +| `www.wolfssl.com` | `../certs/server-ecc.pem` (ECC) | + +Before calling `wolfSSL_accept()` the server peeks at the ClientHello with +`MSG_PEEK`, extracts the name with `wolfSSL_SNI_GetFromBuffer()` and creates +the `WOLFSSL` from the matching context. wolfSSL then checks the name against +the one registered with `wolfSSL_CTX_UseSNI()` and applies the policy set with +`wolfSSL_CTX_SNI_SetOptions()`. After the handshake the server prints +`wolfSSL_SNI_GetRequest()` and `wolfSSL_SNI_Status()`. + +`client-tls-sni` sends the name with `wolfSSL_UseSNI()` and requires the +server certificate to be valid for it with `wolfSSL_check_domain_name()`. +Without that check the client would accept any certificate its CAs signed, +whatever name it asked for. + +The server takes the policy as its only argument: + +| Policy | Option bits | Unknown name | No SNI | +|---|---|---|---| +| `strict` | none | fatal `unrecognized_name` alert | default host | +| `continue` (default) | `WOLFSSL_SNI_CONTINUE_ON_MISMATCH` | default host, status `NO_MATCH` | default host | +| `answer` | `WOLFSSL_SNI_ANSWER_ON_MISMATCH` | default host, status `FAKE_MATCH` | default host | +| `abort` | `WOLFSSL_SNI_ABORT_ON_ABSENCE` + continue | default host, status `NO_MATCH` | fatal alert | + +SNI is enabled by default on x86_64, x86, aarch64 and amd64; elsewhere build +wolfSSL with `--enable-sni`. The client also prints the server certificate's +subject when wolfSSL has `KEEP_PEER_CERT` (set by `--enable-opensslextra`). + +Build: + +```sh +make server-tls-sni client-tls-sni +``` + +Run the server in one terminal: + +```sh +./server-tls-sni +Serving example.com with ../certs/server-cert.pem +Serving www.wolfssl.com with ../certs/server-ecc.pem +Waiting for a connection... +``` + +Run the client in a second terminal with the name to request: + +```sh +./client-tls-sni 127.0.0.1 www.wolfssl.com +Connecting with SNI "www.wolfssl.com" +Connected with TLSv1.3, cipher TLS13-AES256-GCM-SHA384 +Message for server: hello +Server: I hear ya fa shizzle! +``` + +The server reports which host it chose and how wolfSSL matched the name: + +``` +ClientHello requests "www.wolfssl.com", using host www.wolfssl.com +Client connected successfully +SNI request: www.wolfssl.com +SNI status: WOLFSSL_SNI_REAL_MATCH +Client: hello +``` + +Ask for a name the server does not host to see both sides reject it. Under the +default policy the server answers with the `example.com` certificate, and the +client refuses it: + +```sh +./client-tls-sni 127.0.0.1 other.com +Connecting with SNI "other.com" +ERROR: failed to connect to wolfSSL: -322 (peer subject name mismatch) +``` + +Under `./server-tls-sni strict` the server rejects the name itself and the +client gets `-313 (received alert fatal error)`. Omit the host name +(`./client-tls-sni 127.0.0.1`) to connect without SNI; only the `abort` +policy rejects that. + +Send `shutdown` as the message to stop the server. A rejected client does not +stop it. + ## TLS Example with Post-Handshake Authentication See `client-tls-posthsauth.c` and `server-tls-posthsauth.c`. These server and client applications show how to do a handshake without the server authenticating the client. Then after the handshake is complete, the server requests authentication and the client authenticates itself to the server. This is mutual authentication with a faster handshake because the client authentication is done later. This can lead to a better user experience if there are conditions where the client need not be authenticated. diff --git a/tls/client-tls-sni.c b/tls/client-tls-sni.c new file mode 100644 index 000000000..39ce319a5 --- /dev/null +++ b/tls/client-tls-sni.c @@ -0,0 +1,264 @@ +/* client-tls-sni.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* TLS v1.3 client demonstrating Server Name Indication (SNI, RFC 6066). + * + * The client tells the server which host name it wants in the ClientHello, + * so a server hosting several names on one address can present the matching + * certificate. The client then checks that the certificate it received is + * valid for that same name. + * + * APIs shown: + * wolfSSL_UseSNI() send a host name for this connection + * (wolfSSL_CTX_UseSNI() does it for every + * connection made from a context) + * wolfSSL_check_domain_name() require the server certificate to match + * + * Pair with server-tls-sni, which serves two names: + * example.com RSA certificate (../certs/server-cert.pem) + * www.wolfssl.com ECC certificate (../certs/server-ecc.pem) + * Run without a host name to see how the server handles an absent SNI. + * + * Requires wolfSSL built with SNI, which is on by default. + */ + +/* the usual suspects */ +#include +#include +#include + +/* socket includes */ +#include +#include +#include +#include + +/* wolfSSL */ +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include + +#define DEFAULT_PORT 11111 + +/* Both server certificates chain to different CAs, so load both */ +#define CA_FILE_RSA "../certs/ca-cert.pem" +#define CA_FILE_ECC "../certs/ca-ecc-cert.pem" + +#if defined(WOLFSSL_TLS13) && defined(HAVE_SNI) + +int main(int argc, char** argv) +{ + int ret = 0; + int sockfd = SOCKET_INVALID; + struct sockaddr_in servAddr; + char buff[256]; + size_t len; + const char* host = (argc == 3) ? argv[2] : NULL; + + /* declare wolfSSL objects */ + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + /* Check for proper calling convention */ + if (argc < 2 || argc > 3) { + printf("usage: %s [host name]\n", argv[0]); + printf(" host name: sent as SNI and required to match the server\n" + " certificate. Omit it to connect without SNI.\n"); + return 0; + } + + /* Create a socket that uses an internet IPv4 address, + * Sets the socket to be stream based (TCP), + * 0 means choose the default protocol. */ + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; goto exit; + } + + /* Initialize the server address struct with zeros */ + memset(&servAddr, 0, sizeof(servAddr)); + + /* Fill in the server address */ + servAddr.sin_family = AF_INET; /* using IPv4 */ + servAddr.sin_port = htons(DEFAULT_PORT); /* on DEFAULT_PORT */ + + /* Get the server IPv4 address from the command line call */ + if (inet_pton(AF_INET, argv[1], &servAddr.sin_addr) != 1) { + fprintf(stderr, "ERROR: invalid address\n"); + ret = -1; goto exit; + } + + /* Connect to the server */ + if ((ret = connect(sockfd, (struct sockaddr*) &servAddr, sizeof(servAddr))) + == -1) { + fprintf(stderr, "ERROR: failed to connect\n"); + goto exit; + } + + /*---------------------------------*/ + /* Start of wolfSSL initialization and configuration */ + /*---------------------------------*/ +#if 0 + wolfSSL_Debugging_ON(); +#endif + + /* Initialize wolfSSL */ + if ((ret = wolfSSL_Init()) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: Failed to initialize the library\n"); + goto exit; + } + + /* Create and initialize WOLFSSL_CTX */ + if ((ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + ret = -1; goto exit; + } + + /* Load the CA certificates for both server certificates */ + if ((ret = wolfSSL_CTX_load_verify_locations(ctx, CA_FILE_RSA, NULL)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s, please check the file.\n", + CA_FILE_RSA); + goto exit; + } + if ((ret = wolfSSL_CTX_load_verify_locations(ctx, CA_FILE_ECC, NULL)) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s, please check the file.\n", + CA_FILE_ECC); + goto exit; + } + + /* Create a WOLFSSL object */ + if ((ssl = wolfSSL_new(ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + ret = -1; goto exit; + } + + if (host != NULL) { + /* Ask for this host name in the ClientHello */ + if ((ret = wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, host, + (unsigned short)strlen(host))) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: wolfSSL_UseSNI failed\n"); + goto exit; + } + + /* And insist that the certificate we get back is for that name. + * Without this, any certificate the CA signed would be accepted. */ + if ((ret = wolfSSL_check_domain_name(ssl, host)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: wolfSSL_check_domain_name failed\n"); + goto exit; + } + printf("Connecting with SNI \"%s\"\n", host); + } + else { + printf("Connecting without SNI\n"); + } + + /* Attach wolfSSL to the socket */ + if ((ret = wolfSSL_set_fd(ssl, sockfd)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: Failed to set the file descriptor\n"); + goto exit; + } + + /* Connect to wolfSSL on the server side */ + if ((ret = wolfSSL_connect(ssl)) != WOLFSSL_SUCCESS) { + int err = wolfSSL_get_error(ssl, ret); + fprintf(stderr, "ERROR: failed to connect to wolfSSL: %d (%s)\n", + err, wolfSSL_ERR_reason_error_string((unsigned long)err)); + goto exit; + } + + printf("Connected with %s, cipher %s\n", wolfSSL_get_version(ssl), + wolfSSL_get_cipher_name(ssl)); + +#ifdef KEEP_PEER_CERT + /* Show which certificate the server chose for our SNI */ + { + WOLFSSL_X509* peer = wolfSSL_get_peer_certificate(ssl); + if (peer != NULL) { + char subject[256]; + if (wolfSSL_X509_NAME_oneline(wolfSSL_X509_get_subject_name(peer), + subject, sizeof(subject)) != NULL) { + printf("Server certificate subject: %s\n", subject); + } + wolfSSL_X509_free(peer); + } + } +#endif + + /* Get a message for the server from stdin */ + printf("Message for server: "); + memset(buff, 0, sizeof(buff)); + if (fgets(buff, sizeof(buff), stdin) == NULL) { + fprintf(stderr, "ERROR: failed to get message for server\n"); + ret = -1; goto exit; + } + len = strnlen(buff, sizeof(buff)); + + /* Send the message to the server */ + if ((ret = wolfSSL_write(ssl, buff, (int)len)) != (int)len) { + fprintf(stderr, "ERROR: failed to write entire message\n"); + fprintf(stderr, "%d bytes of %d bytes were sent", ret, (int) len); + goto exit; + } + + /* Read the server data into our buff array */ + memset(buff, 0, sizeof(buff)); + if ((ret = wolfSSL_read(ssl, buff, sizeof(buff)-1)) < 0) { + fprintf(stderr, "ERROR: failed to read\n"); + goto exit; + } + + /* Print to stdout any data the server sends */ + printf("Server: %s\n", buff); + + /* Return reporting a success */ + ret = 0; + +exit: + /* Cleanup and return */ + if (sockfd != SOCKET_INVALID) + close(sockfd); /* Close the connection to the server */ + if (ssl) + wolfSSL_free(ssl); /* Free the wolfSSL object */ + if (ctx) + wolfSSL_CTX_free(ctx); /* Free the wolfSSL context object */ + wolfSSL_Cleanup(); /* Cleanup the wolfSSL environment */ + + return ret; +} + +#else + +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + printf("Example requires TLS v1.3 and SNI (build wolfSSL with " + "--enable-sni)\n"); + return 0; +} + +#endif diff --git a/tls/server-tls-sni.c b/tls/server-tls-sni.c new file mode 100644 index 000000000..74bcd0eca --- /dev/null +++ b/tls/server-tls-sni.c @@ -0,0 +1,482 @@ +/* server-tls-sni.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSSL. (formerly known as CyaSSL) + * + * wolfSSL is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * wolfSSL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* TLS v1.3 server demonstrating Server Name Indication (SNI, RFC 6066). + * + * The server hosts two names on one port, each with its own certificate: + * example.com RSA certificate (../certs/server-cert.pem) + * www.wolfssl.com ECC certificate (../certs/server-ecc.pem) + * + * Before handing a connection to wolfSSL it peeks at the raw ClientHello, + * pulls the requested host name out with wolfSSL_SNI_GetFromBuffer(), and + * picks the WOLFSSL_CTX that holds the matching certificate. wolfSSL then + * validates the name against the one registered on that context and applies + * the mismatch policy chosen on the command line. + * + * APIs shown: + * wolfSSL_CTX_UseSNI() name a context answers for + * wolfSSL_CTX_SNI_SetOptions() what to do when the name does not match or + * is absent (wolfSSL_SNI_SetOptions() does the + * same per connection after wolfSSL_UseSNI()) + * wolfSSL_SNI_GetFromBuffer() read the SNI out of a raw ClientHello + * wolfSSL_SNI_Status() how the request matched after the handshake + * wolfSSL_SNI_GetRequest() the name the client asked for + * + * Pair with client-tls-sni. Requires wolfSSL built with SNI, which is on by + * default. + */ + +/* the usual suspects */ +#include +#include +#include +#include + +/* socket includes */ +#include +#include +#include +#include +#include +#include + +/* wolfSSL */ +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include +#include +#include +#include + +#define DEFAULT_PORT 11111 + +#define TLS_HEADER_SZ 5 +#define TLS_MAX_RECORD (TLS_HEADER_SZ + 16384 + 2048) /* header + max ciphertext */ +#define PEEK_TIMEOUT_S 2 + +#if defined(WOLFSSL_TLS13) && defined(HAVE_SNI) && !defined(NO_WOLFSSL_SERVER) + +/* One virtual host: the name it answers for and its credentials */ +typedef struct VHost { + const char* name; + const char* certFile; + const char* keyFile; + WOLFSSL_CTX* ctx; +} VHost; + +static VHost vhosts[] = { + { "example.com", "../certs/server-cert.pem", "../certs/server-key.pem", + NULL }, + { "www.wolfssl.com", "../certs/server-ecc.pem", "../certs/ecc-key.pem", + NULL }, +}; +#define NUM_VHOSTS (sizeof(vhosts) / sizeof(vhosts[0])) +#define DEFAULT_VHOST 0 /* used when the client sends no SNI or an unknown one */ + +/* Map a policy name from the command line to SNI option bits */ +static int ParsePolicy(const char* name, unsigned char* options) +{ + if (strcmp(name, "strict") == 0) { + /* No options: an unknown name gets a fatal unrecognized_name alert, + * a missing SNI is served by the default host. */ + *options = 0; + } + else if (strcmp(name, "continue") == 0) { + /* Serve an unknown name from the default host without acknowledging + * the SNI. wolfSSL_SNI_Status() reports WOLFSSL_SNI_NO_MATCH. */ + *options = WOLFSSL_SNI_CONTINUE_ON_MISMATCH; + } + else if (strcmp(name, "answer") == 0) { + /* Serve an unknown name and acknowledge the SNI as if it matched. + * wolfSSL_SNI_Status() reports WOLFSSL_SNI_FAKE_MATCH. */ + *options = WOLFSSL_SNI_ANSWER_ON_MISMATCH; + } + else if (strcmp(name, "abort") == 0) { + /* Refuse clients that send no SNI at all (fatal missing_extension + * alert in TLS v1.3); unknown names are still served. */ + *options = WOLFSSL_SNI_ABORT_ON_ABSENCE | + WOLFSSL_SNI_CONTINUE_ON_MISMATCH; + } + else { + return -1; + } + return 0; +} + +static const char* StatusName(unsigned char status) +{ + switch (status) { + case WOLFSSL_SNI_NO_MATCH: return "WOLFSSL_SNI_NO_MATCH"; + case WOLFSSL_SNI_FAKE_MATCH: return "WOLFSSL_SNI_FAKE_MATCH"; + case WOLFSSL_SNI_REAL_MATCH: return "WOLFSSL_SNI_REAL_MATCH"; + case WOLFSSL_SNI_FORCE_KEEP: return "WOLFSSL_SNI_FORCE_KEEP"; + default: return "unknown"; + } +} + +/* Peek one complete TLS record without consuming it. The 5 byte header gives + * the record length; MSG_WAITALL then blocks until that many bytes are queued + * (honored with MSG_PEEK on Linux). On success *buf holds a malloc'd copy of + * the record that the caller must free. + * + * Returns the record size, or -1 on timeout, a closed connection, a record + * that is not a handshake, or an allocation failure. */ +static int PeekRecord(int fd, unsigned char** buf) +{ + unsigned char hdr[TLS_HEADER_SZ]; + unsigned char* rec; + int recSz; + + *buf = NULL; + if (recv(fd, hdr, sizeof(hdr), MSG_PEEK | MSG_WAITALL) != TLS_HEADER_SZ) { + return -1; + } + if (hdr[0] != 0x16) { + return -1; /* content type is not handshake */ + } + + /* read wire order len */ + recSz = TLS_HEADER_SZ + ((hdr[3] << 8) | hdr[4]); + if (recSz > TLS_MAX_RECORD) { + return -1; + } + rec = (unsigned char*)malloc((size_t)recSz); + if (rec == NULL) { + return -1; + } + + if (recv(fd, rec, (size_t)recSz, MSG_PEEK | MSG_WAITALL) != recSz) { + free(rec); + return -1; + } + *buf = rec; + return recSz; +} + +/* Peek at the ClientHello waiting on the socket and extract the SNI host + * name without consuming any bytes, so wolfSSL_accept() still sees it. + * + * Returns 1 with the name in sni, 0 if the ClientHello carries no SNI, and + * -1 on timeout, a closed connection, or a malformed record. */ +static int PeekSni(int fd, char* sni, unsigned int sniSz) +{ + unsigned char* hello = NULL; + unsigned int outSz = sniSz - 1; + struct timeval timeout = { PEEK_TIMEOUT_S, 0 }; + struct timeval noTimeout = { 0, 0 }; + int n; + int ret; + + /* Bound the blocking peeks so a stalled client cannot hang the server, + * then clear the timeout so wolfSSL's I/O on this socket is unaffected */ + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + n = PeekRecord(fd, &hello); + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &noTimeout, sizeof(noTimeout)); + if (n < 0) { + return -1; + } + + ret = wolfSSL_SNI_GetFromBuffer(hello, (unsigned int)n, + WOLFSSL_SNI_HOST_NAME, + (unsigned char*)sni, &outSz); + free(hello); + if (ret == WOLFSSL_SUCCESS) { + sni[outSz] = '\0'; + return 1; + } + if (ret == 0) { + return 0; /* complete ClientHello, no SNI extension */ + } + /* Not a ClientHello we can parse, or one fragmented across records */ + return -1; +} + +/* Case-insensitive host name compare. Returns 1 on match, 0 otherwise. */ +static int checkDnsHostNames(const char* n1, const char* n2) +{ + while (*n1 != '\0' && *n2 != '\0') { + if (tolower((unsigned char)*n1++) != tolower((unsigned char)*n2++)) { + return 0; + } + } + if (tolower(*n1) == tolower(*n2)) { + return 1; + } + return 0; +} + +int main(int argc, char** argv) +{ + int ret = 0; + int sockfd = SOCKET_INVALID; + int connd = SOCKET_INVALID; + struct sockaddr_in servAddr; + struct sockaddr_in clientAddr; + socklen_t size = sizeof(clientAddr); + char buff[256]; + size_t len; + int shutdown = 0; + int on = 1; + const char* reply = "I hear ya fa shizzle!\n"; + unsigned char options = WOLFSSL_SNI_CONTINUE_ON_MISMATCH; + size_t i; + + /* declare wolfSSL objects */ + WOLFSSL* ssl = NULL; + + if (argc > 2 || (argc == 2 && ParsePolicy(argv[1], &options) != 0)) { + printf("usage: %s [strict|continue|answer|abort]\n", argv[0]); + printf(" strict: reject unknown names (unrecognized_name alert)\n" + " continue: serve unknown names from the default host " + "(default)\n" + " answer: like continue, but acknowledge the SNI anyway\n" + " abort: reject clients that send no SNI\n"); + return 0; + } + + /* A client that goes away while we write must not kill the server */ + signal(SIGPIPE, SIG_IGN); + + /* Initialize wolfSSL */ + if ((ret = wolfSSL_Init()) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: Failed to initialize the library\n"); + goto exit; + } + + /* Create a socket that uses an internet IPv4 address, + * Sets the socket to be stream based (TCP), + * 0 means choose the default protocol. */ + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "ERROR: failed to create the socket\n"); + ret = -1; goto exit; + } + + /* make sure server is setup for reuse addr/port */ + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, + (socklen_t)sizeof(on)); + + /* One WOLFSSL_CTX per virtual host, each answering for its own name */ + for (i = 0; i < NUM_VHOSTS; i++) { + VHost* vh = &vhosts[i]; + + if ((vh->ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL_CTX\n"); + ret = -1; goto exit; + } + if ((ret = wolfSSL_CTX_use_certificate_file(vh->ctx, vh->certFile, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s, please check the " + "file.\n", vh->certFile); + goto exit; + } + if ((ret = wolfSSL_CTX_use_PrivateKey_file(vh->ctx, vh->keyFile, + WOLFSSL_FILETYPE_PEM)) != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: failed to load %s, please check the " + "file.\n", vh->keyFile); + goto exit; + } + + /* Register the name this context serves. During the handshake + * wolfSSL compares the client's SNI against it. */ + if ((ret = wolfSSL_CTX_UseSNI(vh->ctx, WOLFSSL_SNI_HOST_NAME, vh->name, + (unsigned short)strlen(vh->name))) + != WOLFSSL_SUCCESS) { + fprintf(stderr, "ERROR: wolfSSL_CTX_UseSNI failed\n"); + goto exit; + } + + /* Mismatch/absence policy for that name */ + wolfSSL_CTX_SNI_SetOptions(vh->ctx, WOLFSSL_SNI_HOST_NAME, options); + + printf("Serving %-16s with %s\n", vh->name, vh->certFile); + } + + /* Initialize the server address struct with zeros */ + memset(&servAddr, 0, sizeof(servAddr)); + + /* Fill in the server address */ + servAddr.sin_family = AF_INET; /* using IPv4 */ + servAddr.sin_port = htons(DEFAULT_PORT); /* on DEFAULT_PORT */ + servAddr.sin_addr.s_addr = INADDR_ANY; /* from anywhere */ + + /* Bind the server socket to our port */ + if (bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1) { + fprintf(stderr, "ERROR: failed to bind\n"); + ret = -1; goto exit; + } + + /* Listen for a new connection, allow 5 pending connections */ + if (listen(sockfd, 5) == -1) { + fprintf(stderr, "ERROR: failed to listen\n"); + ret = -1; goto exit; + } + + /* Continue to accept clients until shutdown is issued */ + while (!shutdown) { + char sni[256]; + VHost* vh = &vhosts[DEFAULT_VHOST]; + int peek; + + printf("Waiting for a connection...\n"); + + /* Accept client connections */ + if ((connd = accept(sockfd, (struct sockaddr*)&clientAddr, &size)) + == -1) { + fprintf(stderr, "ERROR: failed to accept the connection\n\n"); + ret = -1; goto exit; + } + + /* Look at the ClientHello before wolfSSL does and pick the context + * whose certificate matches the requested name. */ + peek = PeekSni(connd, sni, sizeof(sni)); + if (peek < 0) { + fprintf(stderr, "Could not read a ClientHello from the client\n\n"); + goto next; + } + if (peek == 0) { + printf("ClientHello has no SNI, using default host %s\n", + vh->name); + } + else { + printf("ClientHello requests \"%s\"", sni); + for (i = 0; i < NUM_VHOSTS; i++) { + if (checkDnsHostNames(vhosts[i].name, sni) == 1) { + vh = &vhosts[i]; + break; + } + } + printf(", using host %s\n", vh->name); + } + + /* Create a WOLFSSL object from the chosen context */ + if ((ssl = wolfSSL_new(vh->ctx)) == NULL) { + fprintf(stderr, "ERROR: failed to create WOLFSSL object\n"); + ret = -1; goto exit; + } + + /* Attach wolfSSL to the socket */ + wolfSSL_set_fd(ssl, connd); + + /* Establish TLS connection. wolfSSL reads the same ClientHello we + * peeked at, checks its SNI against the context's name and applies + * the policy. A rejected client does not stop the server. */ + if ((ret = wolfSSL_accept(ssl)) != WOLFSSL_SUCCESS) { + int err = wolfSSL_get_error(ssl, ret); + fprintf(stderr, "wolfSSL_accept error = %d (%s)\n\n", err, + wolfSSL_ERR_reason_error_string((unsigned long)err)); + goto next; + } + + printf("Client connected successfully\n"); + + /* Report how the SNI was handled */ + { + void* request = NULL; + unsigned short requestSz = wolfSSL_SNI_GetRequest(ssl, + WOLFSSL_SNI_HOST_NAME, &request); + unsigned char status = wolfSSL_SNI_Status(ssl, + WOLFSSL_SNI_HOST_NAME); + + if (requestSz > 0 && request != NULL) { + printf("SNI request: %.*s\n", (int)requestSz, + (const char*)request); + } + else { + printf("SNI request: none recorded\n"); + } + printf("SNI status: %s\n", StatusName(status)); + } + + /* Read the client data into our buff array */ + memset(buff, 0, sizeof(buff)); + if ((ret = wolfSSL_read(ssl, buff, sizeof(buff)-1)) < 0) { + fprintf(stderr, "ERROR: failed to read\n"); + goto next; + } + + /* Print to stdout any data the client sends */ + printf("Client: %s\n", buff); + + /* Check for server shutdown command */ + if (strncmp(buff, "shutdown", 8) == 0) { + printf("Shutdown command issued!\n"); + shutdown = 1; + } + + /* Write our reply into buff */ + memset(buff, 0, sizeof(buff)); + memcpy(buff, reply, strlen(reply)); + len = strnlen(buff, sizeof(buff)); + + /* Reply back to the client */ + if ((ret = wolfSSL_write(ssl, buff, (int)len)) != (int)len) { + fprintf(stderr, "ERROR: failed to write\n"); + goto next; + } + + /* Notify the client that the connection is ending */ + wolfSSL_shutdown(ssl); + +next: + /* Cleanup after this connection */ + if (ssl) { + wolfSSL_free(ssl); /* Free the wolfSSL object */ + ssl = NULL; + } + if (connd != SOCKET_INVALID) { + close(connd); /* Close the connection to the client */ + connd = SOCKET_INVALID; + } + } + + printf("Shutdown complete\n"); + ret = 0; + +exit: + /* Cleanup and return */ + if (ssl) + wolfSSL_free(ssl); /* Free the wolfSSL object */ + if (connd != SOCKET_INVALID) + close(connd); /* Close the connection to the client */ + if (sockfd != SOCKET_INVALID) + close(sockfd); /* Close the socket listening for clients */ + for (i = 0; i < NUM_VHOSTS; i++) { + if (vhosts[i].ctx) + wolfSSL_CTX_free(vhosts[i].ctx); + } + wolfSSL_Cleanup(); /* Cleanup the wolfSSL environment */ + + return ret; +} + +#else + +int main(int argc, char** argv) +{ + (void)argc; + (void)argv; + printf("Example requires TLS v1.3 and SNI (build wolfSSL with " + "--enable-sni)\n"); + return 0; +} + +#endif