emme coverage


Directory: src/
File: src/tls.c
Date: 2026-05-14 14:35:13
Exec Total Coverage
Lines: 62 93 66.7%
Functions: 5 6 83.3%
Branches: 16 36 44.4%

Line Branch Exec Source
1 #include "tls.h"
2 #include <openssl/ssl.h>
3 #include <openssl/err.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <stdatomic.h>
7 #include "log.h"
8
9 #define SESSION_TICKET_KEY_SIZE 80
10
11 static const unsigned char protos[] = "\x02h2\x08http/1.1";
12 static const unsigned int protos_len = 12;
13
14 typedef struct {
15 atomic_long new_sessions;
16 atomic_long removed_sessions;
17 atomic_long cache_full;
18 } SSLSessionStats;
19
20 static SSLSessionStats g_session_stats;
21
22 static int alpn_select_cb(SSL *ssl,
23 const unsigned char **out,
24 unsigned char *outlen,
25 const unsigned char *in,
26 unsigned int inlen,
27 void *arg);
28
29 static int session_new_callback(SSL *ssl, SSL_SESSION *session);
30 static void session_remove_callback(SSL_CTX *ctx, SSL_SESSION *session);
31
32 10 SSL_CTX *create_ssl_context(const char *cert_path, const char *key_path, ServerConfig *config)
33 {
34 10 SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
35
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (!ctx)
36 {
37 log_message(LOG_LEVEL_ERROR, "Unable to create SSL context");
38 return NULL;
39 }
40 10 SSL_CTX_set_options(ctx,
41 SSL_OP_NO_SSLv2 |
42 SSL_OP_NO_SSLv3 |
43 SSL_OP_NO_TLSv1 |
44 SSL_OP_NO_TLSv1_1);
45 10 SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
46
47
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if (SSL_CTX_set_alpn_protos(ctx, protos, protos_len) != 0)
48 {
49 log_message(LOG_LEVEL_ERROR, "Failed to set ALPN protocols");
50 SSL_CTX_free(ctx);
51 return NULL;
52 }
53 10 SSL_CTX_set_alpn_select_cb(ctx, alpn_select_cb, NULL);
54
55
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if (SSL_CTX_use_certificate_file(ctx, cert_path, SSL_FILETYPE_PEM) <= 0)
56 {
57 log_message(LOG_LEVEL_ERROR, "Failed to load certificate file: %s",
58 ERR_error_string(ERR_get_error(), NULL));
59 SSL_CTX_free(ctx);
60 return NULL;
61 }
62
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0)
63 {
64 log_message(LOG_LEVEL_ERROR, "Failed to load private key file: %s",
65 ERR_error_string(ERR_get_error(), NULL));
66 SSL_CTX_free(ctx);
67 return NULL;
68 }
69 /* Configure session caching for high-performance resumption */
70 10 SSL_CTX_set_session_cache_mode(ctx,
71 SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL);
72
73 /* Set cache size (100K sessions = ~4MB memory) */
74 10 SSL_CTX_sess_set_cache_size(ctx, config->ssl.session_cache_size);
75
76 /* Set session timeout (5 minutes for security) */
77 10 SSL_CTX_set_timeout(ctx, config->ssl.session_timeout);
78
79 /* Session ID context for virtual host separation */
80 10 unsigned char sess_id_ctx[] = "emme";
81
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if (SSL_CTX_set_session_id_context(ctx, sess_id_ctx, sizeof(sess_id_ctx)) != 1)
82 {
83 log_message(LOG_LEVEL_ERROR, "Failed to set session id context");
84 SSL_CTX_free(ctx);
85 return NULL;
86 }
87
88 /* Configure SSL modes for high-throughput async I/O */
89 10 int ssl_mode = 0;
90
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (config->ssl.enable_partial_write) {
91 10 ssl_mode |= SSL_MODE_ENABLE_PARTIAL_WRITE;
92 10 log_message(LOG_LEVEL_DEBUG, "SSL mode: ENABLE_PARTIAL_WRITE enabled");
93 }
94
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (config->ssl.release_buffers) {
95 10 ssl_mode |= SSL_MODE_RELEASE_BUFFERS;
96 10 log_message(LOG_LEVEL_DEBUG, "SSL mode: RELEASE_BUFFERS enabled (~34KB saved per idle connection)");
97 }
98
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (ssl_mode) {
99 10 SSL_CTX_set_mode(ctx, ssl_mode);
100 }
101
102 /* Set SSL read buffer size for optimal throughput */
103 10 SSL_CTX_set_default_read_buffer_len(ctx, (size_t)config->ssl.read_buffer_size);
104 10 log_message(LOG_LEVEL_INFO, "SSL read buffer size: %d bytes", config->ssl.read_buffer_size);
105
106 /* Load session ticket key for stateless resumption */
107
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (config->ssl.session_ticket_key[0] != '\0')
108 {
109 FILE *f = fopen(config->ssl.session_ticket_key, "rb");
110 if (f)
111 {
112 unsigned char key[SESSION_TICKET_KEY_SIZE];
113 if (fread(key, 1, SESSION_TICKET_KEY_SIZE, f) == SESSION_TICKET_KEY_SIZE)
114 {
115 if (SSL_CTX_set_tlsext_ticket_keys(ctx, key, SESSION_TICKET_KEY_SIZE) == 1)
116 {
117 log_message(LOG_LEVEL_INFO, "Session tickets enabled from %s",
118 config->ssl.session_ticket_key);
119 }
120 else
121 {
122 log_message(LOG_LEVEL_WARN, "Failed to set session ticket keys");
123 }
124 }
125 else
126 {
127 log_message(LOG_LEVEL_WARN, "Invalid ticket key file (expected %d bytes)",
128 SESSION_TICKET_KEY_SIZE);
129 }
130 fclose(f);
131 }
132 else
133 {
134 log_message(LOG_LEVEL_WARN, "Cannot open ticket key file %s, using cache-only mode",
135 config->ssl.session_ticket_key);
136 }
137 }
138 else
139 {
140 10 log_message(LOG_LEVEL_INFO, "Session tickets not configured, using session cache only");
141 }
142
143 /* Register session callbacks for statistics tracking */
144 10 SSL_CTX_sess_set_new_cb(ctx, session_new_callback);
145 10 SSL_CTX_sess_set_remove_cb(ctx, session_remove_callback);
146
147 10 log_message(LOG_LEVEL_INFO, "TLS session cache configured: size=%d timeout=%ds",
148 config->ssl.session_cache_size, config->ssl.session_timeout);
149
150 10 return ctx;
151 }
152
153 3 static int alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen,
154 const unsigned char *in, unsigned int inlen, void *arg)
155 {
156 (void)ssl;
157 (void)arg;
158
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (SSL_select_next_proto((unsigned char **)out, outlen, protos, protos_len, in, inlen)
159 != OPENSSL_NPN_NEGOTIATED)
160 {
161 return SSL_TLSEXT_ERR_NOACK;
162 }
163 3 return SSL_TLSEXT_ERR_OK;
164 }
165
166 /*
167 * Called when a new SSL session is created.
168 * Tracks session count and warns when cache reaches capacity.
169 */
170 20 static int session_new_callback(SSL *ssl, SSL_SESSION *session)
171 {
172 (void)session;
173
174 20 atomic_fetch_add(&g_session_stats.new_sessions, 1);
175
176 20 SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
177
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 if (ctx)
178 {
179 20 long cache_size = SSL_CTX_sess_get_cache_size(ctx);
180 20 long current = SSL_CTX_sess_number(ctx);
181
182
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
20 if (current >= cache_size)
183 {
184 atomic_fetch_add(&g_session_stats.cache_full, 1);
185 log_message(LOG_LEVEL_WARN, "TLS session cache full: %ld/%ld sessions",
186 current, cache_size);
187 }
188 }
189 20 return 0;
190 }
191
192 /*
193 * Called when a session is removed from the cache.
194 * Tracks removal count for observability.
195 */
196 static void session_remove_callback(SSL_CTX *ctx, SSL_SESSION *session)
197 {
198 (void)ctx;
199 (void)session;
200 atomic_fetch_add(&g_session_stats.removed_sessions, 1);
201 }
202
203 /*
204 * Logs TLS session statistics for monitoring and debugging.
205 * Called every 60 seconds during server operation and on shutdown.
206 */
207 20 void log_session_stats(SSL_CTX *ctx)
208 {
209
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 10 times.
20 if (!ctx)
210 10 return;
211
212 10 long num = SSL_CTX_sess_number(ctx);
213 10 long connect = SSL_CTX_sess_connect(ctx);
214 10 long cache_hits = SSL_CTX_sess_hits(ctx);
215 10 long cache_misses = SSL_CTX_sess_misses(ctx);
216 10 long cache_timeouts = SSL_CTX_sess_timeouts(ctx);
217
218 10 double hit_rate = (connect > 0) ?
219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 (100.0 * cache_hits / connect) : 0.0;
220
221 10 log_message(LOG_LEVEL_INFO,
222 "TLS Session Stats: active=%ld total=%ld hits=%ld misses=%ld "
223 "timeouts=%ld hit_rate=%.1f%% new=%ld removed=%ld cache_full=%ld",
224 num, connect, cache_hits, cache_misses, cache_timeouts,
225 hit_rate,
226 10 atomic_load(&g_session_stats.new_sessions),
227 10 atomic_load(&g_session_stats.removed_sessions),
228 10 atomic_load(&g_session_stats.cache_full));
229 }
230
231 10 void cleanup_ssl_context(SSL_CTX *ctx)
232 {
233
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (ctx)
234 {
235 10 log_session_stats(ctx);
236 10 SSL_CTX_free(ctx);
237 }
238 10 EVP_cleanup();
239 10 }
240