Code

SSL utils: Added init() and shutdown() functions for global setup/shutdown.
[sysdb.git] / src / utils / ssl.c
1 /*
2  * SysDB - src/utils/ssl.c
3  * Copyright (C) 2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 #if HAVE_CONFIG_H
29 #       include "config.h"
30 #endif
32 #include "utils/ssl.h"
33 #include "utils/error.h"
35 #include <errno.h>
37 #include <stdlib.h>
38 #include <string.h>
40 #include <openssl/ssl.h>
41 #include <openssl/bio.h>
42 #include <openssl/x509.h>
43 #include <openssl/err.h>
45 /*
46  * data types
47  */
49 struct sdb_ssl_client {
50         SSL_CTX *ctx;
51         sdb_ssl_options_t opts;
52 };
54 struct sdb_ssl_server {
55         SSL_CTX *ctx;
56         sdb_ssl_options_t opts;
57 };
59 struct sdb_ssl_session {
60         SSL *ssl;
61 };
63 /*
64  * private helper functions
65  */
67 /* log all pending SSL errors */
68 static void
69 ssl_log(int prio, const char *prefix, ...)
70 {
71         char msg[1024];
72         va_list ap;
74         va_start(ap, prefix);
75         vsnprintf(msg, sizeof(msg), prefix, ap);
76         msg[sizeof(msg) - 1] = '\0';
77         va_end(ap);
79         while (42) {
80                 unsigned long e = ERR_get_error();
81                 if (! e)
82                         break;
83                 sdb_log(prio, "%s: %s", msg, ERR_reason_error_string(e));
84         }
85 } /* ssl_log */
87 static void
88 ssl_log_err(int prio, SSL *ssl, int status, const char *prefix, ...)
89 {
90         int err = SSL_get_error(ssl, status);
91         char msg[1024];
92         va_list ap;
94         va_start(ap, prefix);
95         vsnprintf(msg, sizeof(msg), prefix, ap);
96         msg[sizeof(msg) - 1] = '\0';
97         va_end(ap);
99         errno = 0;
100         switch (err) {
101                 case SSL_ERROR_NONE:
102                         sdb_log(prio, "%s: success", msg);
103                         break;
104                 case SSL_ERROR_ZERO_RETURN:
105                         errno = ECONNRESET;
106                         break;
107                 case SSL_ERROR_WANT_READ:
108                 case SSL_ERROR_WANT_WRITE:
109                         errno = EWOULDBLOCK;
110                         break;
111                 case SSL_ERROR_WANT_CONNECT:
112                 case SSL_ERROR_WANT_ACCEPT:
113                         sdb_log(prio, "%s: connection not set up", msg);
114                         break;
115                 case SSL_ERROR_WANT_X509_LOOKUP:
116                         sdb_log(prio, "%s: application error", msg);
117                         break;
118                 case SSL_ERROR_SYSCALL:
119                         if (ERR_peek_error())
120                                 return ssl_log(prio, msg);
121                         if (! status)
122                                 sdb_log(prio, "%s: unexpected end-of-file", msg);
123                         else if (! errno)
124                                 errno = EIO;
125                 case SSL_ERROR_SSL:
126                         return ssl_log(prio, msg);
127                 default:
128                         sdb_log(prio, "%s: unkown SSL error %d", msg, err);
129                         break;
130         }
132         if (errno) {
133                 char errbuf[1024];
134                 sdb_log(prio, "%s: %s", msg,
135                                 sdb_strerror(errno, errbuf, sizeof(errbuf)));
136         }
137 } /* ssl_log_err */
139 static int
140 copy_options(sdb_ssl_options_t *dst, const sdb_ssl_options_t *src)
142         sdb_ssl_options_t tmp;
143         sdb_ssl_options_t def = SDB_SSL_DEFAULT_OPTIONS;
145         if (src)
146                 tmp = *src;
147         else
148                 tmp = def;
150         if (! tmp.ca_file)
151                 tmp.ca_file = def.ca_file;
152         if (! tmp.key_file)
153                 tmp.key_file = def.key_file;
154         if (! tmp.cert_file)
155                 tmp.cert_file = def.cert_file;
157         dst->ca_file = strdup(tmp.ca_file);
158         dst->key_file = strdup(tmp.key_file);
159         dst->cert_file = strdup(tmp.cert_file);
160         if ((! dst->ca_file) || (! dst->key_file) || (! dst->cert_file))
161                 return -1;
162         if (tmp.crl_file) {
163                 dst->crl_file = strdup(tmp.crl_file);
164                 if (! dst->crl_file)
165                         return -1;
166         }
167         return 0;
168 } /* copy_options */
170 /*
171  * public API
172  */
174 void
175 sdb_ssl_init(void)
177         SSL_load_error_strings();
178         OpenSSL_add_ssl_algorithms();
179 } /* sdb_ssl_init */
181 void
182 sdb_ssl_shutdown(void)
184         ERR_free_strings();
185 } /* sdb_ssl_shutdown */
187 sdb_ssl_client_t *
188 sdb_ssl_client_create(const sdb_ssl_options_t *opts)
190         sdb_ssl_client_t *client;
192         client = calloc(1, sizeof(*client));
193         if (! client)
194                 return NULL;
196         if (copy_options(&client->opts, opts)) {
197                 sdb_ssl_client_destroy(client);
198                 return NULL;
199         }
201         client->ctx = SSL_CTX_new(SSLv23_client_method());
202         if (! client->ctx) {
203                 ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL context");
204                 sdb_ssl_client_destroy(client);
205                 return NULL;
206         }
208         if (! SSL_CTX_load_verify_locations(client->ctx,
209                                 client->opts.ca_file, NULL)) {
210                 ssl_log(SDB_LOG_ERR, "ssl: Failed to load CA file '%s'",
211                                 client->opts.ca_file);
212                 sdb_ssl_client_destroy(client);
213                 return NULL;
214         }
215         if (! SSL_CTX_use_certificate_file(client->ctx,
216                                 client->opts.cert_file, SSL_FILETYPE_PEM)) {
217                 ssl_log(SDB_LOG_ERR, "ssl: Failed to load cert file '%s'",
218                                 client->opts.cert_file);
219                 sdb_ssl_client_destroy(client);
220                 return NULL;
221         }
222         if (! SSL_CTX_use_PrivateKey_file(client->ctx,
223                                 client->opts.key_file, SSL_FILETYPE_PEM)) {
224                 ssl_log(SDB_LOG_ERR, "ssl: Failed to load key file '%s'",
225                                 client->opts.key_file);
226                 sdb_ssl_client_destroy(client);
227                 return NULL;
228         }
229         if (! SSL_CTX_check_private_key(client->ctx)) {
230                 ssl_log(SDB_LOG_ERR, "ssl: Failed to verify key (%s)",
231                                 client->opts.key_file);
232                 sdb_ssl_client_destroy(client);
233                 return NULL;
234         }
236         SSL_CTX_set_mode(client->ctx, SSL_MODE_AUTO_RETRY);
237         SSL_CTX_set_verify(client->ctx, SSL_VERIFY_PEER, NULL);
238         SSL_CTX_set_verify_depth(client->ctx, 1);
239         return client;
240 } /* sdb_ssl_client_create */
242 void
243 sdb_ssl_client_destroy(sdb_ssl_client_t *client)
245         if (! client)
246                 return;
248         if (client->ctx)
249                 SSL_CTX_free(client->ctx);
250         sdb_ssl_free_options(&client->opts);
251         free(client);
252 } /* sdb_ssl_client_destroy */
254 sdb_ssl_session_t *
255 sdb_ssl_client_connect(sdb_ssl_client_t *client, int fd)
257         sdb_ssl_session_t *session;
258         int status;
259         BIO *bio;
261         if ((! client) || (fd < 0))
262                 return NULL;
264         session = calloc(1, sizeof(*session));
265         if (! session)
266                 return NULL;
268         bio = BIO_new_socket(fd, BIO_NOCLOSE);
269         if (! bio) {
270                 ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL socket");
271                 sdb_ssl_session_destroy(session);
272                 return NULL;
273         }
274         session->ssl = SSL_new(client->ctx);
275         if (! session->ssl) {
276                 ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL object");
277                 sdb_ssl_session_destroy(session);
278                 return NULL;
279         }
280         SSL_set_bio(session->ssl, bio, bio);
282         if ((status = SSL_connect(session->ssl)) <= 0) {
283                 ssl_log_err(SDB_LOG_ERR, session->ssl, status,
284                                 "ssl: Failed to initialize SSL session");
285                 sdb_ssl_session_destroy(session);
286                 return NULL;
287         }
288         if (SSL_get_verify_result(session->ssl) != X509_V_OK) {
289                 sdb_log(SDB_LOG_ERR, "Failed to verify SSL connection");
290                 sdb_ssl_session_destroy(session);
291                 return NULL;
292         }
293         return session;
294 } /* sdb_ssl_client_connect */
296 sdb_ssl_server_t *
297 sdb_ssl_server_create(const sdb_ssl_options_t *opts)
299         sdb_ssl_server_t *server;
301         server = calloc(1, sizeof(*server));
302         if (! server)
303                 return NULL;
305         if (copy_options(&server->opts, opts)) {
306                 sdb_ssl_server_destroy(server);
307                 return NULL;
308         }
310         server->ctx = SSL_CTX_new(SSLv23_server_method());
311         if (! server->ctx) {
312                 ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL context");
313                 sdb_ssl_server_destroy(server);
314                 return NULL;
315         }
317         /* Recommendation documented at
318          * https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ */
319         if (! SSL_CTX_set_cipher_list(server->ctx,
320                                 "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:"
321                                 "DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:"
322                                 "!aNULL:!MD5:!DSS")) {
323                 sdb_log(SDB_LOG_ERR, "ssl: Invalid cipher list");
324                 sdb_ssl_server_destroy(server);
325                 return NULL;
326         }
328         if (! SSL_CTX_load_verify_locations(server->ctx,
329                                 server->opts.ca_file, NULL)) {
330                 ssl_log(SDB_LOG_ERR, "Failed to load CA file %s",
331                                 server->opts.ca_file);
332                 return NULL;
333         }
334         SSL_CTX_set_client_CA_list(server->ctx,
335                         SSL_load_client_CA_file(server->opts.ca_file));
337         if (! SSL_CTX_use_certificate_file(server->ctx,
338                                 server->opts.cert_file, SSL_FILETYPE_PEM)) {
339                 ssl_log(SDB_LOG_ERR, "Failed to load SSL cert file %s",
340                                 server->opts.cert_file);
341                 return NULL;
342         }
343         if (! SSL_CTX_use_PrivateKey_file(server->ctx,
344                                 server->opts.key_file, SSL_FILETYPE_PEM)) {
345                 ssl_log(SDB_LOG_ERR, "Failed to load SSL key file %s",
346                                 server->opts.key_file);
347                 return NULL;
348         }
349         if (! SSL_CTX_check_private_key(server->ctx)) {
350                 ssl_log(SDB_LOG_ERR, "Failed to verify SSL private key");
351                 return NULL;
352         }
354         SSL_CTX_set_mode(server->ctx, SSL_MODE_AUTO_RETRY);
355         SSL_CTX_set_verify(server->ctx,
356                         SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
357         SSL_CTX_set_verify_depth(server->ctx, 1);
359         /* TODO: handle server->opts.crl_file */
360         return server;
361 } /* sdb_ssl_server_create */
363 void
364 sdb_ssl_server_destroy(sdb_ssl_server_t *server)
366         if (! server)
367                 return;
369         if (server->ctx)
370                 SSL_CTX_free(server->ctx);
371         sdb_ssl_free_options(&server->opts);
372         free(server);
373 } /* sdb_ssl_server_destroy */
375 sdb_ssl_session_t *
376 sdb_ssl_server_accept(sdb_ssl_server_t *server, int fd)
378         sdb_ssl_session_t *session;
379         int status;
380         BIO *bio;
382         if ((! server) || (fd < 0))
383                 return NULL;
385         session = calloc(1, sizeof(*session));
386         if (! session)
387                 return NULL;
389         bio = BIO_new_socket(fd, BIO_NOCLOSE);
390         if (! bio) {
391                 ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL socket");
392                 sdb_ssl_session_destroy(session);
393                 return NULL;
394         }
395         session->ssl = SSL_new(server->ctx);
396         if (! session->ssl) {
397                 ssl_log(SDB_LOG_ERR, "ssl: Failed to create SSL object");
398                 sdb_ssl_session_destroy(session);
399                 return NULL;
400         }
401         SSL_set_bio(session->ssl, bio, bio);
403         while (42) {
404                 if ((status = SSL_accept(session->ssl)) <= 0) {
405                         if (SSL_get_error(session->ssl, status) == SSL_ERROR_WANT_READ)
406                                 continue;
408                         ssl_log_err(SDB_LOG_ERR, session->ssl, status,
409                                         "ssl: Failed to initialize SSL session");
410                         sdb_ssl_session_destroy(session);
411                         return NULL;
412                 }
413                 break;
414         }
415         if (SSL_get_verify_result(session->ssl) != X509_V_OK) {
416                 sdb_log(SDB_LOG_ERR, "Failed to verify SSL connection");
417                 sdb_ssl_session_destroy(session);
418                 return NULL;
419         }
420         return session;
421 } /* sdb_ssl_server_accept */
423 void
424 sdb_ssl_session_destroy(sdb_ssl_session_t *session)
426         if (! session)
427                 return;
429         if (session->ssl) {
430                 SSL_shutdown(session->ssl);
431                 SSL_clear(session->ssl);
432                 SSL_free(session->ssl);
433         }
434         free(session);
435 } /* sdb_ssl_session_destroy */
437 char *
438 sdb_ssl_session_peer(sdb_ssl_session_t *session)
440         X509 *x509;
441         X509_NAME *name;
443         char *peer = NULL;
444         char p[1024];
446         if (! session)
447                 return NULL;
449         x509 = SSL_get_peer_certificate(session->ssl);
450         if (! x509)
451                 return NULL;
452         name = X509_get_subject_name(x509);
453         if (! name) {
454                 X509_free(x509);
455                 return NULL;
456         }
458         if (X509_NAME_get_text_by_NID(name, NID_commonName, p, sizeof(p)) > 0)
459                 peer = strdup(p);
461         X509_free(x509);
462         return peer;
463 } /* sdb_ssl_session_peer */
465 ssize_t
466 sdb_ssl_session_write(sdb_ssl_session_t *session, const void *buf, size_t n)
468         int status;
470         if (! session)
471                 return -1;
473         status = SSL_write(session->ssl, buf, (int)n);
474         if (status) {
475                 if ((status < 0) && (errno != EAGAIN))
476                         ssl_log_err(SDB_LOG_ERR, session->ssl, status, "ssl: Write error");
477                 return (ssize_t)status;
478         }
480         status = SSL_get_error(session->ssl, status);
481         if (status == SSL_ERROR_ZERO_RETURN)
482                 return 0;
484         if ((status == SSL_ERROR_WANT_READ) || (status == SSL_ERROR_WANT_WRITE)) {
485                 errno = EWOULDBLOCK;
486                 return -1;
487         }
488         errno = ECONNRESET;
489         return -1;
490 } /* sdb_ssl_session_write */
492 ssize_t
493 sdb_ssl_session_read(sdb_ssl_session_t *session, void *buf, size_t n)
495         int status;
497         if (! session)
498                 return -1;
500         status = SSL_read(session->ssl, buf, (int)n);
501         if (status) {
502                 if ((status < 0) && (errno != EAGAIN))
503                         ssl_log_err(SDB_LOG_ERR, session->ssl, status, "ssl: Read error");
504                 return (ssize_t)status;
505         }
507         status = SSL_get_error(session->ssl, status);
508         if (status == SSL_ERROR_ZERO_RETURN)
509                 return 0;
511         if ((status == SSL_ERROR_WANT_READ) || (status == SSL_ERROR_WANT_WRITE)) {
512                 errno = EWOULDBLOCK;
513                 return -1;
514         }
515         errno = ECONNRESET;
516         return -1;
517 } /* sdb_ssl_session_read */
519 void
520 sdb_ssl_free_options(sdb_ssl_options_t *opts)
522         if (! opts)
523                 return;
525         if (opts->ca_file)
526                 free(opts->ca_file);
527         if (opts->key_file)
528                 free(opts->key_file);
529         if (opts->cert_file)
530                 free(opts->cert_file);
531         if (opts->crl_file)
532                 free(opts->crl_file);
534         opts->ca_file = opts->key_file = opts->cert_file = opts->crl_file = NULL;
535 } /* sdb_ssl_free_options */
537 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */