blob: 5627a1a99639e1ef30c2665d2b5afd0962a521b8 [file] [log] [blame]
Radek Krejci9f03b482015-10-22 16:02:10 +02001/**
Michal Vasko11d4cdb2015-10-29 11:42:52 +01002 * \file session_tls.c
Radek Krejci9f03b482015-10-22 16:02:10 +02003 * \author Radek Krejci <rkrejci@cesnet.cz>
Michal Vasko11d4cdb2015-10-29 11:42:52 +01004 * \author Michal Vasko <mvasko@cesnet.cz>
5 * \brief libnetconf2 - TLS specific session transport functions
Radek Krejci9f03b482015-10-22 16:02:10 +02006 *
Michal Vasko11d4cdb2015-10-29 11:42:52 +01007 * This source is compiled only with libssl.
Radek Krejci9f03b482015-10-22 16:02:10 +02008 *
9 * Copyright (c) 2015 CESNET, z.s.p.o.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 * 3. Neither the name of the Company nor the names of its contributors
21 * may be used to endorse or promote products derived from this
22 * software without specific prior written permission.
23 *
24 */
25
26#include <assert.h>
27#include <errno.h>
Radek Krejci9f03b482015-10-22 16:02:10 +020028#include <string.h>
29#include <unistd.h>
30
31#include <libyang/libyang.h>
32
33#include "libnetconf.h"
Michal Vasko11d4cdb2015-10-29 11:42:52 +010034#include "session.h"
Michal Vasko9e2d3a32015-11-10 13:09:18 +010035#include "session_p.h"
Michal Vasko11d4cdb2015-10-29 11:42:52 +010036
37/* TLS certificate verification error messages */
38static const char* verify_ret_msg[] = {
39 "ok",
40 "",
41 "unable to get issuer certificate",
42 "unable to get certificate CRL",
43 "unable to decrypt certificate's signature",
44 "unable to decrypt CRL's signature",
45 "unable to decode issuer public key",
46 "certificate signature failure",
47 "CRL signature failure",
48 "certificate is not yet valid",
49 "certificate has expired",
50 "CRL is not yet valid",
51 "CRL has expired",
52 "format error in certificate's notBefore field",
53 "format error in certificate's notAfter field",
54 "format error in CRL's lastUpdate field",
55 "format error in CRL's nextUpdate field",
56 "out of memory",
57 "self signed certificate",
58 "self signed certificate in certificate chain",
59 "unable to get local issuer certificate",
60 "unable to verify the first certificate",
61 "certificate chain too long",
62 "certificate revoked",
63 "invalid CA certificate",
64 "path length constraint exceeded",
65 "unsupported certificate purpose",
66 "certificate not trusted",
67 "certificate rejected",
68 "subject issuer mismatch",
69 "authority and subject key identifier mismatch",
70 "authority and issuer serial number mismatch",
71 "key usage does not include certificate signing"
72};
73
74static struct nc_tls_auth_opts tls_opts;
Radek Krejci9f03b482015-10-22 16:02:10 +020075
Radek Krejci9f03b482015-10-22 16:02:10 +020076static int
Michal Vasko11d4cdb2015-10-29 11:42:52 +010077tlsauth_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
Radek Krejci9f03b482015-10-22 16:02:10 +020078{
Michal Vasko11d4cdb2015-10-29 11:42:52 +010079 X509_STORE_CTX store_ctx;
80 X509_OBJECT obj;
81 X509_NAME *subject, *issuer;
82 X509 *cert;
83 X509_CRL *crl;
84 X509_REVOKED *revoked;
85 EVP_PKEY *pubkey;
86 int i, n, rc;
87 ASN1_TIME *next_update = NULL;
Radek Krejci9f03b482015-10-22 16:02:10 +020088
Michal Vasko11d4cdb2015-10-29 11:42:52 +010089 if (!preverify_ok) {
90 return 0;
91 }
Radek Krejci9f03b482015-10-22 16:02:10 +020092
Michal Vasko11d4cdb2015-10-29 11:42:52 +010093 cert = X509_STORE_CTX_get_current_cert(x509_ctx);
94 subject = X509_get_subject_name(cert);
95 issuer = X509_get_issuer_name(cert);
96
97 /* try to retrieve a CRL corresponding to the _subject_ of
98 * the current certificate in order to verify it's integrity */
99 memset((char *)&obj, 0, sizeof obj);
100 X509_STORE_CTX_init(&store_ctx, tls_opts.tls_store, NULL, NULL);
101 rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, subject, &obj);
102 X509_STORE_CTX_cleanup(&store_ctx);
103 crl = obj.data.crl;
104 if (rc > 0 && crl) {
105 next_update = X509_CRL_get_nextUpdate(crl);
106
107 /* verify the signature on this CRL */
108 pubkey = X509_get_pubkey(cert);
109 if (X509_CRL_verify(crl, pubkey) <= 0) {
110 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
111 X509_OBJECT_free_contents(&obj);
112 if (pubkey) {
113 EVP_PKEY_free(pubkey);
114 }
115 return 0; /* fail */
116 }
117 if (pubkey) {
118 EVP_PKEY_free(pubkey);
119 }
120
121 /* check date of CRL to make sure it's not expired */
122 if (!next_update) {
123 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
124 X509_OBJECT_free_contents(&obj);
125 return 0; /* fail */
126 }
127 if (X509_cmp_current_time(next_update) < 0) {
128 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_HAS_EXPIRED);
129 X509_OBJECT_free_contents(&obj);
130 return 0; /* fail */
131 }
132 X509_OBJECT_free_contents(&obj);
133 }
134
135 /* try to retrieve a CRL corresponding to the _issuer_ of
136 * the current certificate in order to check for revocation */
137 memset((char *)&obj, 0, sizeof obj);
138 X509_STORE_CTX_init(&store_ctx, tls_opts.tls_store, NULL, NULL);
139 rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, issuer, &obj);
140 X509_STORE_CTX_cleanup(&store_ctx);
141 crl = obj.data.crl;
142 if (rc > 0 && crl) {
143 /* check if the current certificate is revoked by this CRL */
144 n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
145 for (i = 0; i < n; i++) {
146 revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
147 if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(cert)) == 0) {
148 ERR("Certificate revoked!");
149 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REVOKED);
150 X509_OBJECT_free_contents(&obj);
151 return 0; /* fail */
152 }
153 }
154 X509_OBJECT_free_contents(&obj);
155 }
156
157 return 1; /* success */
158}
159
160API int
Michal Vaskoc111ac52015-12-08 14:36:36 +0100161nc_tls_client_init(const char *client_cert, const char *client_key, const char *ca_file, const char *ca_dir,
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100162 const char *crl_file, const char *crl_dir)
163{
164 const char *key_ = client_key;
165 X509_LOOKUP *lookup;
166
167 if (tls_opts.tls_ctx) {
Michal Vasko290fe2f2015-12-08 14:42:46 +0100168 VRB("TLS context reinitialization.");
169 SSL_CTX_free(tls_opts.tls_ctx);
170 tls_opts.tls_ctx = NULL;
171 } else {
172 /* init libssl */
173 SSL_load_error_strings();
174 ERR_load_BIO_strings();
175 SSL_library_init();
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100176 }
177
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100178 if (!client_cert) {
179 return EXIT_SUCCESS;
180 }
181
182 /* prepare global SSL context, allow only mandatory TLS 1.2 */
183 if (!(tls_opts.tls_ctx = SSL_CTX_new(TLSv1_2_client_method()))) {
184 ERR("Unable to create OpenSSL context (%s)", ERR_reason_error_string(ERR_get_error()));
185 return EXIT_FAILURE;
186 }
187
188 if (crl_file || crl_dir) {
189 /* set the revocation store with the correct paths for the callback */
190 tls_opts.tls_store = X509_STORE_new();
191 tls_opts.tls_store->cache = 0;
192
193 if (crl_file) {
194 if (!(lookup = X509_STORE_add_lookup(tls_opts.tls_store, X509_LOOKUP_file()))) {
195 ERR("Failed to add lookup method to CRL checking.");
196 return EXIT_FAILURE;
197 }
198 if (X509_LOOKUP_add_dir(lookup, crl_file, X509_FILETYPE_PEM) != 1) {
199 ERR("Failed to add the revocation lookup file \"%s\".", crl_file);
200 return EXIT_FAILURE;
201 }
202 }
203
204 if (crl_dir) {
205 if (!(lookup = X509_STORE_add_lookup(tls_opts.tls_store, X509_LOOKUP_hash_dir()))) {
206 ERR("Failed to add lookup method to CRL checking.");
207 return EXIT_FAILURE;
208 }
209 if (X509_LOOKUP_add_dir(lookup, crl_dir, X509_FILETYPE_PEM) != 1) {
210 ERR("Failed to add the revocation lookup directory \"%s\".", crl_dir);
211 return EXIT_FAILURE;
212 }
213 }
214
215 SSL_CTX_set_verify(tls_opts.tls_ctx, SSL_VERIFY_PEER, tlsauth_verify_callback);
216 } else {
217 /* CRL checking will be skipped */
218 SSL_CTX_set_verify(tls_opts.tls_ctx, SSL_VERIFY_PEER, NULL);
219 }
220
221 /* get peer certificate */
222 if (SSL_CTX_use_certificate_file(tls_opts.tls_ctx, client_cert, SSL_FILETYPE_PEM) != 1) {
223 ERR("Loading a peer certificate from \'%s\' failed (%s).", client_cert, ERR_reason_error_string(ERR_get_error()));
224 return EXIT_FAILURE;
225 }
226
227 if (!key_) {
228 /*
229 * if the file with private key not specified, expect that the private
230 * key is stored altogether with the certificate
231 */
232 key_ = client_cert;
233 }
234 if (SSL_CTX_use_PrivateKey_file(tls_opts.tls_ctx, key_, SSL_FILETYPE_PEM) != 1) {
235 ERR("Loading the client certificate from \'%s\' failed (%s).", key_, ERR_reason_error_string(ERR_get_error()));
236 return EXIT_FAILURE;
237 }
238
239 if (!SSL_CTX_load_verify_locations(tls_opts.tls_ctx, ca_file, ca_dir)) {
240 ERR("Failed to load the locations of trusted CA certificates (%s).", ERR_reason_error_string(ERR_get_error()));
241 return EXIT_FAILURE;
242 }
243
244 return EXIT_SUCCESS;
245}
246
247API void
Michal Vaskoc111ac52015-12-08 14:36:36 +0100248nc_tls_client_destroy()
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100249{
250 CRYPTO_THREADID crypto_tid;
251
252 SSL_CTX_free(tls_opts.tls_ctx);
253
254 EVP_cleanup();
255 CRYPTO_cleanup_all_ex_data();
256 ERR_free_strings();
257 sk_SSL_COMP_free(SSL_COMP_get_compression_methods());
258 CRYPTO_THREADID_current(&crypto_tid);
259 ERR_remove_thread_state(&crypto_tid);
Radek Krejci9f03b482015-10-22 16:02:10 +0200260}
261
262API struct nc_session *
Michal Vasko9bee18d2015-12-08 14:41:42 +0100263nc_connect_tls(const char *host, unsigned short port, struct ly_ctx *ctx)
Radek Krejci9f03b482015-10-22 16:02:10 +0200264{
Radek Krejci9f03b482015-10-22 16:02:10 +0200265 struct nc_session *session = NULL;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100266 int sock, verify;
267
268 /* was init called? */
269 if (!tls_opts.tls_ctx) {
270 ERR("TLS context was not initialized!");
271 return NULL;
272 }
Radek Krejci9f03b482015-10-22 16:02:10 +0200273
274 /* process parameters */
275 if (!host || strisempty(host)) {
276 host = "localhost";
277 }
278
279 if (!port) {
280 port = NC_PORT_TLS;
281 }
282
Radek Krejci9f03b482015-10-22 16:02:10 +0200283 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100284 session = calloc(1, sizeof *session);
Radek Krejci9f03b482015-10-22 16:02:10 +0200285 if (!session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100286 ERRMEM;
Radek Krejci9f03b482015-10-22 16:02:10 +0200287 return NULL;
288 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100289 session->status = NC_STATUS_STARTING;
290 session->side = NC_CLIENT;
Radek Krejci9f03b482015-10-22 16:02:10 +0200291
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100292 /* transport lock */
293 session->ti_lock = malloc(sizeof *session->ti_lock);
294 if (!session->ti_lock) {
295 ERRMEM;
296 goto fail;
297 }
298 pthread_mutex_init(session->ti_lock, NULL);
299
300 /* fill the session */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100301 session->ti_type = NC_TI_OPENSSL;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100302 if (!(session->ti.tls = SSL_new(tls_opts.tls_ctx))) {
303 ERR("Failed to create new TLS session structure (%s)", ERR_reason_error_string(ERR_get_error()));
304 goto fail;
305 }
Radek Krejci9f03b482015-10-22 16:02:10 +0200306
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100307 /* create and assign socket */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100308 sock = nc_connect_getsocket(host, port);
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100309 if (sock == -1) {
310 goto fail;
311 }
312 SSL_set_fd(session->ti.tls, sock);
313
314 /* set the SSL_MODE_AUTO_RETRY flag to allow OpenSSL perform re-handshake automatically */
315 SSL_set_mode(session->ti.tls, SSL_MODE_AUTO_RETRY);
316
317 /* connect and perform the handshake */
318 if (SSL_connect(session->ti.tls) != 1) {
319 ERR("Connecting over TLS failed (%s).", ERR_reason_error_string(ERR_get_error()));
320 goto fail;
321 }
322
323 /* check certificate verification result */
324 verify = SSL_get_verify_result(session->ti.tls);
325 switch (verify) {
326 case X509_V_OK:
327 VRB("Server certificate successfully verified.");
328 break;
329 default:
330 WRN("Server certificate verification problem (%s).", verify_ret_msg[verify]);
Radek Krejci9f03b482015-10-22 16:02:10 +0200331 }
332
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100333 /* assign context (dicionary needed for handshake) */
334 if (!ctx) {
335 ctx = ly_ctx_new(SCHEMAS_DIR);
336 } else {
337 session->flags |= NC_SESSION_SHAREDCTX;
338 }
339 session->ctx = ctx;
340
Radek Krejci9f03b482015-10-22 16:02:10 +0200341 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100342 if (nc_handshake(session)) {
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100343 goto fail;
Radek Krejci9f03b482015-10-22 16:02:10 +0200344 }
Michal Vaskoad611702015-12-03 13:41:51 +0100345 session->status = NC_STATUS_RUNNING;
Radek Krejci9f03b482015-10-22 16:02:10 +0200346
Michal Vasko57eb9402015-12-08 14:38:12 +0100347 if (nc_ctx_check_and_fill(session)) {
348 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100349 }
350
351 /* store information into session and the dictionary */
352 session->host = lydict_insert(ctx, host, 0);
353 session->port = port;
Michal Vasko9bee18d2015-12-08 14:41:42 +0100354 session->username = lydict_insert(ctx, "certificate-based", 0);
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100355
Radek Krejci9f03b482015-10-22 16:02:10 +0200356 return session;
357
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100358fail:
Radek Krejci9f03b482015-10-22 16:02:10 +0200359 nc_session_free(session);
360 return NULL;
361}
362
363API struct nc_session *
364nc_connect_libssl(SSL *tls, struct ly_ctx *ctx)
365{
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100366 struct nc_session *session;
Radek Krejci9f03b482015-10-22 16:02:10 +0200367
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100368 /* check TLS session status */
369 if (!tls || !SSL_is_init_finished(tls)) {
370 ERR("Supplied TLS session is not fully connected!");
371 return NULL;
372 }
373
374 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100375 session = calloc(1, sizeof *session);
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100376 if (!session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100377 ERRMEM;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100378 return NULL;
379 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100380 session->status = NC_STATUS_STARTING;
381 session->side = NC_CLIENT;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100382
383 /* transport lock */
384 session->ti_lock = malloc(sizeof *session->ti_lock);
385 if (!session->ti_lock) {
386 ERRMEM;
387 goto fail;
388 }
389 pthread_mutex_init(session->ti_lock, NULL);
390
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100391 session->ti_type = NC_TI_OPENSSL;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100392 session->ti.tls = tls;
393
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100394 /* assign context (dicionary needed for handshake) */
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100395 if (!ctx) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100396 ctx = ly_ctx_new(SCHEMAS_DIR);
397 } else {
398 session->flags |= NC_SESSION_SHAREDCTX;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100399 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100400 session->ctx = ctx;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100401
402 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100403 if (nc_handshake(session)) {
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100404 goto fail;
405 }
Michal Vaskoad611702015-12-03 13:41:51 +0100406 session->status = NC_STATUS_RUNNING;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100407
Michal Vasko57eb9402015-12-08 14:38:12 +0100408 if (nc_ctx_check_and_fill(session)) {
409 goto fail;
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100410 }
411
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100412 return session;
413
414fail:
415 nc_session_free(session);
Radek Krejci9f03b482015-10-22 16:02:10 +0200416 return NULL;
417}
418
Michal Vasko80cad7f2015-12-08 14:42:27 +0100419API struct nc_session *
420nc_callhome_accept_tls(uint16_t port, int32_t timeout, struct ly_ctx *ctx)
421{
422 int sock, verify;
423 char *server_host;
424 SSL *tls;
425 struct nc_session *session;
426
427 if (!port) {
428 port = NC_PORT_CH_TLS;
429 }
430
431 sock = nc_callhome_accept_connection(port, timeout, NULL, &server_host);
432 if (sock == -1) {
433 return NULL;
434 }
435
436 if (!(tls = SSL_new(tls_opts.tls_ctx))) {
437 ERR("Failed to create new TLS session structure (%s).", ERR_reason_error_string(ERR_get_error()));
438 close(sock);
439 return NULL;
440 }
441
442 SSL_set_fd(tls, sock);
443
444 /* set the SSL_MODE_AUTO_RETRY flag to allow OpenSSL perform re-handshake automatically */
445 SSL_set_mode(tls, SSL_MODE_AUTO_RETRY);
446
447 /* connect and perform the handshake */
448 if (SSL_connect(tls) != 1) {
449 ERR("Connecting over TLS failed (%s).", ERR_reason_error_string(ERR_get_error()));
450 SSL_free(tls);
451 return NULL;
452 }
453
454 /* check certificate verification result */
455 verify = SSL_get_verify_result(tls);
456 switch (verify) {
457 case X509_V_OK:
458 VRB("Server certificate successfully verified.");
459 break;
460 default:
461 WRN("Server certificate verification problem (%s).", verify_ret_msg[verify]);
462 }
463
464 session = nc_connect_libssl(tls, ctx);
465 if (session) {
466 /* store information into session and the dictionary */
467 session->host = lydict_insert_zc(session->ctx, server_host);
468 session->port = port;
469 session->username = lydict_insert(session->ctx, "certificate-based", 0);
470 }
471
472 return session;
473}