Skip to content

Instantly share code, notes, and snippets.

@alphamarket
Last active January 3, 2022 11:44
Show Gist options
  • Save alphamarket/bb168d48bd51e03bddc0545497d53435 to your computer and use it in GitHub Desktop.
Save alphamarket/bb168d48bd51e03bddc0545497d53435 to your computer and use it in GitHub Desktop.
Multi-Host & Multi-Certificate OpenSSL-based Server
#include <stdio.h>
#include <sstream>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unordered_map>
#include <openssl/ssl.h>
#include <openssl/err.h>
std::unordered_map<std::string, SSL_CTX*> ctxs;
int create_socket(int port)
{
int s;
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0) {
perror("Unable to create socket");
exit(EXIT_FAILURE);
}
if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("Unable to bind");
exit(EXIT_FAILURE);
}
if (listen(s, 1) < 0) {
perror("Unable to listen");
exit(EXIT_FAILURE);
}
return s;
}
SSL_CTX *create_context()
{
const SSL_METHOD *method;
SSL_CTX *ctx;
method = TLS_server_method();
ctx = SSL_CTX_new(method);
if (!ctx) {
perror("Unable to create SSL context");
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
void configure_context(SSL_CTX *ctx, const std::string& cert, const std::string& key)
{
/* Set the key and cert */
if (SSL_CTX_use_certificate_file(ctx, cert.c_str(), SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
if (SSL_CTX_use_PrivateKey_file(ctx, key.c_str(), SSL_FILETYPE_PEM) <= 0 ) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
}
SSL_CTX* GetServerContext(const std::string& host_name) {
// try to fetch the right context
for(auto& host_ctx : ctxs) {
if(host_ctx.first == host_name) {
std::cerr << "Switching context to: " << host_ctx.first << std::endl;
return host_ctx.second;
}
}
return nullptr;
}
int ServerNameCallback(SSL *ssl, int *, void *) {
std::cerr << "ServerNameCallback\n";
if (ssl == NULL)
return SSL_TLSEXT_ERR_NOACK;
const char* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (!servername || servername[0] == '\0')
return SSL_TLSEXT_ERR_NOACK;
/* Need a certificate and context for this domain */
SSL_CTX* ctx = GetServerContext(servername);
if (ctx == NULL)
return SSL_TLSEXT_ERR_NOACK;
#if 0
/* We should not be peeking into the object like this... */
if(ctx != ssl->ctx) {
throw std::runtime_error("Picked the same context...");
}
#endif
/* Useless return value */
SSL_set_SSL_CTX(ssl, ctx);
return SSL_TLSEXT_ERR_OK;
}
int main(int, char**)
{
ctxs = {
{ "default", create_context() },
{ "site1.com", create_context() },
{ "site2.com", create_context() }
};
auto default_ctx = ctxs["default"];
configure_context(default_ctx, "cert.pem", "key.pem");
configure_context(ctxs["site1.com"], "site1-cert.pem", "site1-key.pem");
configure_context(ctxs["site2.com"], "site2-cert.pem", "site2-key.pem");
SSL_CTX_set_tlsext_servername_callback(default_ctx, ServerNameCallback);
auto sock = create_socket(4433);
/* Handle connections */
while(1) {
struct sockaddr_in addr;
unsigned int len = sizeof(addr);
SSL *ssl;
const char reply[] = "HTTP/2 200\n\ntest\n";
int client = accept(sock, (struct sockaddr*)&addr, &len);
if (client < 0) {
perror("Unable to accept");
exit(EXIT_FAILURE);
}
ssl = SSL_new(default_ctx);
SSL_set_fd(ssl, client);
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
} else {
const char* host_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if(host_name)
std::cerr << "Received host name: " << host_name << std::endl;
std::stringstream ss;
ss << reply << host_name << "\n";
std::cerr << "Reply: " << ss.str() << "----------------------------\n";
SSL_write(ssl, ss.str().c_str(), ss.str().length());
}
SSL_shutdown(ssl);
SSL_free(ssl);
close(client);
}
close(sock);
SSL_CTX_free(default_ctx);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment