blob: b2c8534d5bad835accef4a7df2a691dfb7be852a [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>
28#include <pwd.h>
29#include <sys/types.h>
30#include <string.h>
31#include <unistd.h>
32
33#include <libyang/libyang.h>
34
35#include "libnetconf.h"
Michal Vasko11d4cdb2015-10-29 11:42:52 +010036#include "session.h"
Michal Vasko9e2d3a32015-11-10 13:09:18 +010037#include "session_p.h"
Michal Vasko11d4cdb2015-10-29 11:42:52 +010038
39/* TLS certificate verification error messages */
40static const char* verify_ret_msg[] = {
41 "ok",
42 "",
43 "unable to get issuer certificate",
44 "unable to get certificate CRL",
45 "unable to decrypt certificate's signature",
46 "unable to decrypt CRL's signature",
47 "unable to decode issuer public key",
48 "certificate signature failure",
49 "CRL signature failure",
50 "certificate is not yet valid",
51 "certificate has expired",
52 "CRL is not yet valid",
53 "CRL has expired",
54 "format error in certificate's notBefore field",
55 "format error in certificate's notAfter field",
56 "format error in CRL's lastUpdate field",
57 "format error in CRL's nextUpdate field",
58 "out of memory",
59 "self signed certificate",
60 "self signed certificate in certificate chain",
61 "unable to get local issuer certificate",
62 "unable to verify the first certificate",
63 "certificate chain too long",
64 "certificate revoked",
65 "invalid CA certificate",
66 "path length constraint exceeded",
67 "unsupported certificate purpose",
68 "certificate not trusted",
69 "certificate rejected",
70 "subject issuer mismatch",
71 "authority and subject key identifier mismatch",
72 "authority and issuer serial number mismatch",
73 "key usage does not include certificate signing"
74};
75
76static struct nc_tls_auth_opts tls_opts;
Radek Krejci9f03b482015-10-22 16:02:10 +020077
Radek Krejci9f03b482015-10-22 16:02:10 +020078static int
Michal Vasko11d4cdb2015-10-29 11:42:52 +010079tlsauth_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
Radek Krejci9f03b482015-10-22 16:02:10 +020080{
Michal Vasko11d4cdb2015-10-29 11:42:52 +010081 X509_STORE_CTX store_ctx;
82 X509_OBJECT obj;
83 X509_NAME *subject, *issuer;
84 X509 *cert;
85 X509_CRL *crl;
86 X509_REVOKED *revoked;
87 EVP_PKEY *pubkey;
88 int i, n, rc;
89 ASN1_TIME *next_update = NULL;
Radek Krejci9f03b482015-10-22 16:02:10 +020090
Michal Vasko11d4cdb2015-10-29 11:42:52 +010091 if (!preverify_ok) {
92 return 0;
93 }
Radek Krejci9f03b482015-10-22 16:02:10 +020094
Michal Vasko11d4cdb2015-10-29 11:42:52 +010095 cert = X509_STORE_CTX_get_current_cert(x509_ctx);
96 subject = X509_get_subject_name(cert);
97 issuer = X509_get_issuer_name(cert);
98
99 /* try to retrieve a CRL corresponding to the _subject_ of
100 * the current certificate in order to verify it's integrity */
101 memset((char *)&obj, 0, sizeof obj);
102 X509_STORE_CTX_init(&store_ctx, tls_opts.tls_store, NULL, NULL);
103 rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, subject, &obj);
104 X509_STORE_CTX_cleanup(&store_ctx);
105 crl = obj.data.crl;
106 if (rc > 0 && crl) {
107 next_update = X509_CRL_get_nextUpdate(crl);
108
109 /* verify the signature on this CRL */
110 pubkey = X509_get_pubkey(cert);
111 if (X509_CRL_verify(crl, pubkey) <= 0) {
112 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
113 X509_OBJECT_free_contents(&obj);
114 if (pubkey) {
115 EVP_PKEY_free(pubkey);
116 }
117 return 0; /* fail */
118 }
119 if (pubkey) {
120 EVP_PKEY_free(pubkey);
121 }
122
123 /* check date of CRL to make sure it's not expired */
124 if (!next_update) {
125 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
126 X509_OBJECT_free_contents(&obj);
127 return 0; /* fail */
128 }
129 if (X509_cmp_current_time(next_update) < 0) {
130 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CRL_HAS_EXPIRED);
131 X509_OBJECT_free_contents(&obj);
132 return 0; /* fail */
133 }
134 X509_OBJECT_free_contents(&obj);
135 }
136
137 /* try to retrieve a CRL corresponding to the _issuer_ of
138 * the current certificate in order to check for revocation */
139 memset((char *)&obj, 0, sizeof obj);
140 X509_STORE_CTX_init(&store_ctx, tls_opts.tls_store, NULL, NULL);
141 rc = X509_STORE_get_by_subject(&store_ctx, X509_LU_CRL, issuer, &obj);
142 X509_STORE_CTX_cleanup(&store_ctx);
143 crl = obj.data.crl;
144 if (rc > 0 && crl) {
145 /* check if the current certificate is revoked by this CRL */
146 n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
147 for (i = 0; i < n; i++) {
148 revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
149 if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(cert)) == 0) {
150 ERR("Certificate revoked!");
151 X509_STORE_CTX_set_error(x509_ctx, X509_V_ERR_CERT_REVOKED);
152 X509_OBJECT_free_contents(&obj);
153 return 0; /* fail */
154 }
155 }
156 X509_OBJECT_free_contents(&obj);
157 }
158
159 return 1; /* success */
160}
161
162API int
163nc_client_init_tls(const char *client_cert, const char *client_key, const char *ca_file, const char *ca_dir,
164 const char *crl_file, const char *crl_dir)
165{
166 const char *key_ = client_key;
167 X509_LOOKUP *lookup;
168
169 if (tls_opts.tls_ctx) {
170 ERR("TLS context already initialized!");
171 return EXIT_FAILURE;
172 }
173
174 /* init libssl */
175 SSL_load_error_strings();
176 ERR_load_BIO_strings();
177 SSL_library_init();
178
179 if (!client_cert) {
180 return EXIT_SUCCESS;
181 }
182
183 /* prepare global SSL context, allow only mandatory TLS 1.2 */
184 if (!(tls_opts.tls_ctx = SSL_CTX_new(TLSv1_2_client_method()))) {
185 ERR("Unable to create OpenSSL context (%s)", ERR_reason_error_string(ERR_get_error()));
186 return EXIT_FAILURE;
187 }
188
189 if (crl_file || crl_dir) {
190 /* set the revocation store with the correct paths for the callback */
191 tls_opts.tls_store = X509_STORE_new();
192 tls_opts.tls_store->cache = 0;
193
194 if (crl_file) {
195 if (!(lookup = X509_STORE_add_lookup(tls_opts.tls_store, X509_LOOKUP_file()))) {
196 ERR("Failed to add lookup method to CRL checking.");
197 return EXIT_FAILURE;
198 }
199 if (X509_LOOKUP_add_dir(lookup, crl_file, X509_FILETYPE_PEM) != 1) {
200 ERR("Failed to add the revocation lookup file \"%s\".", crl_file);
201 return EXIT_FAILURE;
202 }
203 }
204
205 if (crl_dir) {
206 if (!(lookup = X509_STORE_add_lookup(tls_opts.tls_store, X509_LOOKUP_hash_dir()))) {
207 ERR("Failed to add lookup method to CRL checking.");
208 return EXIT_FAILURE;
209 }
210 if (X509_LOOKUP_add_dir(lookup, crl_dir, X509_FILETYPE_PEM) != 1) {
211 ERR("Failed to add the revocation lookup directory \"%s\".", crl_dir);
212 return EXIT_FAILURE;
213 }
214 }
215
216 SSL_CTX_set_verify(tls_opts.tls_ctx, SSL_VERIFY_PEER, tlsauth_verify_callback);
217 } else {
218 /* CRL checking will be skipped */
219 SSL_CTX_set_verify(tls_opts.tls_ctx, SSL_VERIFY_PEER, NULL);
220 }
221
222 /* get peer certificate */
223 if (SSL_CTX_use_certificate_file(tls_opts.tls_ctx, client_cert, SSL_FILETYPE_PEM) != 1) {
224 ERR("Loading a peer certificate from \'%s\' failed (%s).", client_cert, ERR_reason_error_string(ERR_get_error()));
225 return EXIT_FAILURE;
226 }
227
228 if (!key_) {
229 /*
230 * if the file with private key not specified, expect that the private
231 * key is stored altogether with the certificate
232 */
233 key_ = client_cert;
234 }
235 if (SSL_CTX_use_PrivateKey_file(tls_opts.tls_ctx, key_, SSL_FILETYPE_PEM) != 1) {
236 ERR("Loading the client certificate from \'%s\' failed (%s).", key_, ERR_reason_error_string(ERR_get_error()));
237 return EXIT_FAILURE;
238 }
239
240 if (!SSL_CTX_load_verify_locations(tls_opts.tls_ctx, ca_file, ca_dir)) {
241 ERR("Failed to load the locations of trusted CA certificates (%s).", ERR_reason_error_string(ERR_get_error()));
242 return EXIT_FAILURE;
243 }
244
245 return EXIT_SUCCESS;
246}
247
248API void
249nc_client_destroy_tls()
250{
251 CRYPTO_THREADID crypto_tid;
252
253 SSL_CTX_free(tls_opts.tls_ctx);
254
255 EVP_cleanup();
256 CRYPTO_cleanup_all_ex_data();
257 ERR_free_strings();
258 sk_SSL_COMP_free(SSL_COMP_get_compression_methods());
259 CRYPTO_THREADID_current(&crypto_tid);
260 ERR_remove_thread_state(&crypto_tid);
Radek Krejci9f03b482015-10-22 16:02:10 +0200261}
262
263API struct nc_session *
264nc_connect_tls(const char *host, unsigned short port, const char *username, struct ly_ctx *ctx)
265{
266 struct passwd *pw;
267 struct nc_session *session = NULL;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100268 int sock, verify;
269
270 /* was init called? */
271 if (!tls_opts.tls_ctx) {
272 ERR("TLS context was not initialized!");
273 return NULL;
274 }
Radek Krejci9f03b482015-10-22 16:02:10 +0200275
276 /* process parameters */
277 if (!host || strisempty(host)) {
278 host = "localhost";
279 }
280
281 if (!port) {
282 port = NC_PORT_TLS;
283 }
284
285 if (!username) {
286 pw = getpwuid(getuid());
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100287 if (pw) {
Radek Krejci9f03b482015-10-22 16:02:10 +0200288 username = pw->pw_name;
289 }
290 }
291
292 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100293 session = calloc(1, sizeof *session);
Radek Krejci9f03b482015-10-22 16:02:10 +0200294 if (!session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100295 ERRMEM;
Radek Krejci9f03b482015-10-22 16:02:10 +0200296 return NULL;
297 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100298 session->status = NC_STATUS_STARTING;
299 session->side = NC_CLIENT;
Radek Krejci9f03b482015-10-22 16:02:10 +0200300
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100301 /* transport lock */
302 session->ti_lock = malloc(sizeof *session->ti_lock);
303 if (!session->ti_lock) {
304 ERRMEM;
305 goto fail;
306 }
307 pthread_mutex_init(session->ti_lock, NULL);
308
309 /* fill the session */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100310 session->ti_type = NC_TI_OPENSSL;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100311 if (!(session->ti.tls = SSL_new(tls_opts.tls_ctx))) {
312 ERR("Failed to create new TLS session structure (%s)", ERR_reason_error_string(ERR_get_error()));
313 goto fail;
314 }
Radek Krejci9f03b482015-10-22 16:02:10 +0200315
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100316 /* create and assign socket */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100317 sock = nc_connect_getsocket(host, port);
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100318 if (sock == -1) {
319 goto fail;
320 }
321 SSL_set_fd(session->ti.tls, sock);
322
323 /* set the SSL_MODE_AUTO_RETRY flag to allow OpenSSL perform re-handshake automatically */
324 SSL_set_mode(session->ti.tls, SSL_MODE_AUTO_RETRY);
325
326 /* connect and perform the handshake */
327 if (SSL_connect(session->ti.tls) != 1) {
328 ERR("Connecting over TLS failed (%s).", ERR_reason_error_string(ERR_get_error()));
329 goto fail;
330 }
331
332 /* check certificate verification result */
333 verify = SSL_get_verify_result(session->ti.tls);
334 switch (verify) {
335 case X509_V_OK:
336 VRB("Server certificate successfully verified.");
337 break;
338 default:
339 WRN("Server certificate verification problem (%s).", verify_ret_msg[verify]);
Radek Krejci9f03b482015-10-22 16:02:10 +0200340 }
341
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100342 /* assign context (dicionary needed for handshake) */
343 if (!ctx) {
344 ctx = ly_ctx_new(SCHEMAS_DIR);
345 } else {
346 session->flags |= NC_SESSION_SHAREDCTX;
347 }
348 session->ctx = ctx;
349
Radek Krejci9f03b482015-10-22 16:02:10 +0200350 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100351 if (nc_handshake(session)) {
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100352 goto fail;
Radek Krejci9f03b482015-10-22 16:02:10 +0200353 }
Michal Vaskoad611702015-12-03 13:41:51 +0100354 session->status = NC_STATUS_RUNNING;
Radek Krejci9f03b482015-10-22 16:02:10 +0200355
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100356 /* check/fill libyang context */
357 if (session->flags & NC_SESSION_SHAREDCTX) {
358 if (nc_ctx_check(session)) {
359 goto fail;
360 }
361 } else {
362 if (nc_ctx_fill(session)) {
363 goto fail;
364 }
365 }
366
367 /* store information into session and the dictionary */
368 session->host = lydict_insert(ctx, host, 0);
369 session->port = port;
370 session->username = lydict_insert(ctx, username, 0);
371
Radek Krejci9f03b482015-10-22 16:02:10 +0200372 return session;
373
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100374fail:
Radek Krejci9f03b482015-10-22 16:02:10 +0200375 nc_session_free(session);
376 return NULL;
377}
378
379API struct nc_session *
380nc_connect_libssl(SSL *tls, struct ly_ctx *ctx)
381{
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100382 struct nc_session *session;
Radek Krejci9f03b482015-10-22 16:02:10 +0200383
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100384 /* check TLS session status */
385 if (!tls || !SSL_is_init_finished(tls)) {
386 ERR("Supplied TLS session is not fully connected!");
387 return NULL;
388 }
389
390 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100391 session = calloc(1, sizeof *session);
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100392 if (!session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100393 ERRMEM;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100394 return NULL;
395 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100396 session->status = NC_STATUS_STARTING;
397 session->side = NC_CLIENT;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100398
399 /* transport lock */
400 session->ti_lock = malloc(sizeof *session->ti_lock);
401 if (!session->ti_lock) {
402 ERRMEM;
403 goto fail;
404 }
405 pthread_mutex_init(session->ti_lock, NULL);
406
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100407 session->ti_type = NC_TI_OPENSSL;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100408 session->ti.tls = tls;
409
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100410 /* assign context (dicionary needed for handshake) */
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100411 if (!ctx) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100412 ctx = ly_ctx_new(SCHEMAS_DIR);
413 } else {
414 session->flags |= NC_SESSION_SHAREDCTX;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100415 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100416 session->ctx = ctx;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100417
418 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100419 if (nc_handshake(session)) {
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100420 goto fail;
421 }
Michal Vaskoad611702015-12-03 13:41:51 +0100422 session->status = NC_STATUS_RUNNING;
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100423
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100424 /* check/fill libyang context */
425 if (session->flags & NC_SESSION_SHAREDCTX) {
426 if (nc_ctx_check(session)) {
427 goto fail;
428 }
429 } else {
430 if (nc_ctx_fill(session)) {
431 goto fail;
432 }
433 }
434
Michal Vasko11d4cdb2015-10-29 11:42:52 +0100435 return session;
436
437fail:
438 nc_session_free(session);
Radek Krejci9f03b482015-10-22 16:02:10 +0200439 return NULL;
440}
441