Last active
January 3, 2022 11:44
-
-
Save alphamarket/bb168d48bd51e03bddc0545497d53435 to your computer and use it in GitHub Desktop.
Multi-Host & Multi-Certificate OpenSSL-based Server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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