blob: b727538a01ab424d86929a23e7fa71051c2cfbdc [file] [log] [blame]
roman3f9b65c2023-06-05 14:26:58 +02001/**
2 * @file config_new.c
3 * @author Roman Janota <janota@cesnet.cz>
4 * @brief libnetconf2 server new configuration creation functions
5 *
6 * @copyright
7 * Copyright (c) 2023 CESNET, z.s.p.o.
8 *
9 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * https://opensource.org/licenses/BSD-3-Clause
14 */
15
16#define _GNU_SOURCE
17
roman2eab4742023-06-06 10:00:26 +020018#include <libyang/libyang.h>
roman3f9b65c2023-06-05 14:26:58 +020019#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22
roman2eab4742023-06-06 10:00:26 +020023#ifdef NC_ENABLED_SSH_TLS
roman3f9b65c2023-06-05 14:26:58 +020024#include <libssh/libssh.h>
roman3f9b65c2023-06-05 14:26:58 +020025#include <openssl/err.h>
26#include <openssl/evp.h>
27#include <openssl/pem.h>
roman2eab4742023-06-06 10:00:26 +020028#endif /* NC_ENABLED_SSH_TLS */
roman3f9b65c2023-06-05 14:26:58 +020029
30#include "compat.h"
31#include "config_new.h"
32#include "log_p.h"
33#include "session.h"
34#include "session_p.h"
35
36int
37nc_config_new_check_add_operation(const struct ly_ctx *ctx, struct lyd_node *top)
38{
39 if (lyd_find_meta(top->meta, NULL, "yang:operation")) {
40 /* it already has operation attribute */
41 return 0;
42 }
43
44 /* give the top level container create operation */
45 if (lyd_new_meta(ctx, top, NULL, "yang:operation", "create", 0, NULL)) {
46 return 1;
47 }
48
49 return 0;
50}
51
roman2eab4742023-06-06 10:00:26 +020052#ifdef NC_ENABLED_SSH_TLS
53
roman3f9b65c2023-06-05 14:26:58 +020054const char *
55nc_config_new_privkey_format_to_identityref(NC_PRIVKEY_FORMAT format)
56{
57 switch (format) {
58 case NC_PRIVKEY_FORMAT_RSA:
59 return "ietf-crypto-types:rsa-private-key-format";
60 case NC_PRIVKEY_FORMAT_EC:
61 return "ietf-crypto-types:ec-private-key-format";
62 case NC_PRIVKEY_FORMAT_X509:
63 return "libnetconf2-netconf-server:subject-private-key-info-format";
64 case NC_PRIVKEY_FORMAT_OPENSSH:
65 return "libnetconf2-netconf-server:openssh-private-key-format";
66 default:
67 ERR(NULL, "Private key type not supported.");
68 return NULL;
69 }
70}
71
72int
73nc_server_config_new_read_certificate(const char *cert_path, char **cert)
74{
75 int ret = 0, cert_len;
76 X509 *x509 = NULL;
77 FILE *f = NULL;
78 BIO *bio = NULL;
79 char *c = NULL;
80
81 *cert = NULL;
82
83 f = fopen(cert_path, "r");
84 if (!f) {
85 ERR(NULL, "Unable to open certificate file \"%s\".", cert_path);
86 ret = 1;
87 goto cleanup;
88 }
89
90 /* load the cert into memory */
91 x509 = PEM_read_X509(f, NULL, NULL, NULL);
92 if (!x509) {
93 ret = -1;
94 goto cleanup;
95 }
96
97 bio = BIO_new(BIO_s_mem());
98 if (!bio) {
99 ret = -1;
100 goto cleanup;
101 }
102
103 ret = PEM_write_bio_X509(bio, x509);
104 if (!ret) {
105 ret = -1;
106 goto cleanup;
107 }
108
109 cert_len = BIO_pending(bio);
110 if (cert_len <= 0) {
111 ret = -1;
112 goto cleanup;
113 }
114
115 c = malloc(cert_len + 1);
116 if (!c) {
117 ERRMEM;
118 ret = 1;
119 goto cleanup;
120 }
121
122 /* read the cert from bio */
123 ret = BIO_read(bio, c, cert_len);
124 if (ret <= 0) {
125 ret = -1;
126 goto cleanup;
127 }
128 c[cert_len] = '\0';
129
130 /* strip the cert of the header and footer */
131 *cert = strdup(c + strlen(NC_PEM_CERTIFICATE_HEADER));
132 if (!*cert) {
133 ERRMEM;
134 ret = 1;
135 goto cleanup;
136 }
137
138 (*cert)[strlen(*cert) - strlen(NC_PEM_CERTIFICATE_FOOTER)] = '\0';
139
140 ret = 0;
141
142cleanup:
143 if (ret == -1) {
144 ERR(NULL, "Error getting certificate from file \"%s\" (OpenSSL Error): \"%s\".", cert_path, ERR_reason_error_string(ERR_get_error()));
145 ret = 1;
146 }
147 if (f) {
148 fclose(f);
149 }
150
151 BIO_free(bio);
152 X509_free(x509);
153 free(c);
154 return ret;
155}
156
157static int
158nc_server_config_new_read_ssh2_pubkey(FILE *f, char **pubkey)
159{
160 char *buffer = NULL;
161 size_t size = 0, pubkey_len = 0;
162 void *tmp;
163 ssize_t read;
164 int ret = 0;
165
166 while ((read = getline(&buffer, &size, f)) > 0) {
167 if (!strncmp(buffer, "----", 4)) {
168 continue;
169 }
170
171 if (!strncmp(buffer, "Comment:", 8)) {
172 continue;
173 }
174
175 if (buffer[read - 1] == '\n') {
176 read--;
177 }
178
179 tmp = realloc(*pubkey, pubkey_len + read + 1);
180 if (!tmp) {
181 ERRMEM;
182 ret = 1;
183 goto cleanup;
184 }
185
186 *pubkey = tmp;
187 memcpy(*pubkey + pubkey_len, buffer, read);
188 pubkey_len += read;
189 }
190
191 if (!pubkey_len) {
192 ERR(NULL, "Unexpected public key format.");
193 ret = 1;
194 goto cleanup;
195 }
196
197 (*pubkey)[pubkey_len] = '\0';
198
199cleanup:
200 free(buffer);
201 return ret;
202}
203
204static int
205nc_server_config_new_read_pubkey_openssl(FILE *f, char **pubkey)
206{
207 int ret = 0;
208 EVP_PKEY *pkey = NULL;
209 BIO *bio = NULL;
210 char *key = NULL;
211 int pub_len;
212
213 /* read the pubkey from file */
214 pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL);
215 if (!pkey) {
216 ret = -1;
217 goto cleanup;
218 }
219
220 bio = BIO_new(BIO_s_mem());
221 if (!bio) {
222 ret = -1;
223 goto cleanup;
224 }
225
226 /* write the pubkey into bio */
227 ret = PEM_write_bio_PUBKEY(bio, pkey);
228 if (!ret) {
229 ret = -1;
230 goto cleanup;
231 }
232
233 pub_len = BIO_pending(bio);
234 if (pub_len <= 0) {
235 ret = -1;
236 goto cleanup;
237 }
238
239 /* get pubkey's length */
240 key = malloc(pub_len + 1);
241 if (!key) {
242 ERRMEM;
243 ret = 1;
244 goto cleanup;
245 }
246
247 /* read the public key from bio */
248 ret = BIO_read(bio, key, pub_len);
249 if (ret <= 0) {
250 ret = -1;
251 goto cleanup;
252 }
253 key[pub_len] = '\0';
254
255 /* strip the pubkey of the header and footer */
256 *pubkey = strdup(key + strlen(NC_SUBJECT_PUBKEY_INFO_HEADER));
257 if (!*pubkey) {
258 ERRMEM;
259 ret = 1;
260 goto cleanup;
261 }
262
263 (*pubkey)[strlen(*pubkey) - strlen(NC_SUBJECT_PUBKEY_INFO_FOOTER)] = '\0';
264
265 ret = 0;
266cleanup:
267 if (ret == -1) {
268 ERR(NULL, "Error getting public key from file (OpenSSL Error): \"%s\".", ERR_reason_error_string(ERR_get_error()));
269 ret = 1;
270 }
271
272 BIO_free(bio);
273 EVP_PKEY_free(pkey);
274 free(key);
275
276 return ret;
277}
278
roman3f9b65c2023-06-05 14:26:58 +0200279static int
280nc_server_config_new_read_pubkey_libssh(const char *pubkey_path, char **pubkey)
281{
282 int ret = 0;
283 ssh_key pub_sshkey = NULL;
284
285 ret = ssh_pki_import_pubkey_file(pubkey_path, &pub_sshkey);
286 if (ret) {
287 ERR(NULL, "Importing public key from file \"%s\" failed.", pubkey_path);
288 return ret;
289 }
290
291 ret = ssh_pki_export_pubkey_base64(pub_sshkey, pubkey);
292 if (ret) {
293 ERR(NULL, "Exporting public key to base64 failed.");
294 }
295
296 ssh_key_free(pub_sshkey);
297 return ret;
298}
299
roman3f9b65c2023-06-05 14:26:58 +0200300int
301nc_server_config_new_get_pubkey(const char *pubkey_path, char **pubkey, NC_PUBKEY_FORMAT *pubkey_type)
302{
303 int ret = 0;
304 FILE *f = NULL;
305 char *header = NULL;
306 size_t len = 0;
307
308 NC_CHECK_ARG_RET(NULL, pubkey, pubkey_type, 1);
309
310 *pubkey = NULL;
311
312 f = fopen(pubkey_path, "r");
313 if (!f) {
314 ERR(NULL, "Unable to open file \"%s\".", pubkey_path);
315 ret = 1;
316 goto cleanup;
317 }
318
319 if (getline(&header, &len, f) < 0) {
320 ERR(NULL, "Error reading header from file \"%s\".", pubkey_path);
321 ret = 1;
322 goto cleanup;
323 }
324 rewind(f);
325
326 if (!strncmp(header, NC_SUBJECT_PUBKEY_INFO_HEADER, strlen(NC_SUBJECT_PUBKEY_INFO_HEADER))) {
327 /* it's subject public key info public key */
328 ret = nc_server_config_new_read_pubkey_openssl(f, pubkey);
329 *pubkey_type = NC_PUBKEY_FORMAT_X509;
330 } else if (!strncmp(header, NC_SSH2_PUBKEY_HEADER, strlen(NC_SSH2_PUBKEY_HEADER))) {
331 /* it's ssh2 public key */
332 ret = nc_server_config_new_read_ssh2_pubkey(f, pubkey);
333 *pubkey_type = NC_PUBKEY_FORMAT_SSH2;
334 }
roman3f9b65c2023-06-05 14:26:58 +0200335 else {
336 /* it's probably OpenSSH public key */
337 ret = nc_server_config_new_read_pubkey_libssh(pubkey_path, pubkey);
338 *pubkey_type = NC_PUBKEY_FORMAT_SSH2;
339 }
roman3f9b65c2023-06-05 14:26:58 +0200340
341 if (ret) {
342 ERR(NULL, "Error getting public key from file \"%s\".", pubkey_path);
343 goto cleanup;
344 }
345
346cleanup:
347 if (f) {
348 fclose(f);
349 }
350
351 free(header);
352
353 return ret;
354}
355
356static int
357nc_server_config_new_get_privkey_openssl(FILE *f, char **privkey, EVP_PKEY **priv_pkey)
358{
359 int ret = 0, priv_len;
360 BIO *bio = NULL;
361
362 NC_CHECK_ARG_RET(NULL, privkey, priv_pkey, 1);
363
364 /* read private key from file */
365 *priv_pkey = PEM_read_PrivateKey(f, NULL, NULL, NULL);
366 if (!*priv_pkey) {
367 ret = -1;
368 goto cleanup;
369 }
370
371 bio = BIO_new(BIO_s_mem());
372 if (!bio) {
373 ret = -1;
374 goto cleanup;
375 }
376
377 /* write the private key in to bio */
378 ret = PEM_write_bio_PrivateKey(bio, *priv_pkey, NULL, NULL, 0, NULL, NULL);
379 if (!ret) {
380 ret = -1;
381 goto cleanup;
382 }
383
384 priv_len = BIO_pending(bio);
385 if (priv_len <= 0) {
386 ret = -1;
387 goto cleanup;
388 }
389
390 /* get private key's length */
391 *privkey = malloc(priv_len + 1);
392 if (!*privkey) {
393 ERRMEM;
394 ret = 1;
395 goto cleanup;
396 }
397
398 /* read the private key from bio */
399 ret = BIO_read(bio, *privkey, priv_len);
400 if (ret <= 0) {
401 ret = -1;
402 goto cleanup;
403 }
404 (*privkey)[priv_len] = '\0';
405
406 ret = 0;
407cleanup:
408 if (ret < 0) {
409 ERR(NULL, "Getting private key from file failed (%s).", ERR_reason_error_string(ERR_get_error()));
410 }
411 BIO_free(bio);
412 return ret;
413}
414
415static int
416nc_server_config_new_privkey_to_pubkey_openssl(EVP_PKEY *priv_pkey, char **pubkey)
417{
418 int ret = 0, pub_len;
419 BIO *bio = NULL;
420
421 bio = BIO_new(BIO_s_mem());
422 if (!bio) {
423 ret = -1;
424 goto cleanup;
425 }
426
427 /* write the pubkey into bio */
428 ret = PEM_write_bio_PUBKEY(bio, priv_pkey);
429 if (!ret) {
430 ret = -1;
431 goto cleanup;
432 }
433
434 /* get the length of the pubkey */
435 pub_len = BIO_pending(bio);
436 if (pub_len <= 0) {
437 ret = -1;
438 goto cleanup;
439 }
440
441 *pubkey = malloc(pub_len + 1);
442 if (!*pubkey) {
443 ERRMEM;
444 ret = 1;
445 goto cleanup;
446 }
447
448 /* read the pubkey from the bio */
449 ret = BIO_read(bio, *pubkey, pub_len);
450 if (ret <= 0) {
451 ret = -1;
452 goto cleanup;
453 }
454 (*pubkey)[pub_len] = '\0';
455
456 ret = 0;
457
458cleanup:
459 if (ret < 0) {
460 ERR(NULL, "Converting private to public key failed (%s).", ERR_reason_error_string(ERR_get_error()));
461 }
462 BIO_free(bio);
463 return ret;
464}
465
466static int
467nc_server_config_new_privkey_to_pubkey_libssh(const ssh_key priv_sshkey, char **pubkey)
468{
469 int ret;
470 ssh_key pub_sshkey = NULL;
471
472 ret = ssh_pki_export_privkey_to_pubkey(priv_sshkey, &pub_sshkey);
473 if (ret) {
474 ERR(NULL, "Exporting privkey to pubkey failed.");
475 return ret;
476 }
477
478 ret = ssh_pki_export_pubkey_base64(pub_sshkey, pubkey);
479 if (ret) {
480 ERR(NULL, "Exporting pubkey to base64 failed.");
481 }
482
483 ssh_key_free(pub_sshkey);
484 return ret;
485}
486
487static int
488nc_server_config_new_privkey_to_pubkey(EVP_PKEY *priv_pkey, const ssh_key priv_sshkey, NC_PRIVKEY_FORMAT privkey_type, char **pubkey, NC_PUBKEY_FORMAT *pubkey_type)
489{
490 switch (privkey_type) {
roman3f9b65c2023-06-05 14:26:58 +0200491 case NC_PRIVKEY_FORMAT_RSA:
492 case NC_PRIVKEY_FORMAT_EC:
493 case NC_PRIVKEY_FORMAT_OPENSSH:
494 *pubkey_type = NC_PUBKEY_FORMAT_SSH2;
495 return nc_server_config_new_privkey_to_pubkey_libssh(priv_sshkey, pubkey);
roman3f9b65c2023-06-05 14:26:58 +0200496 case NC_PRIVKEY_FORMAT_X509:
497 *pubkey_type = NC_PUBKEY_FORMAT_X509;
498 return nc_server_config_new_privkey_to_pubkey_openssl(priv_pkey, pubkey);
499 default:
500 break;
501 }
502
503 return 1;
504}
505
roman3f9b65c2023-06-05 14:26:58 +0200506static int
507nc_server_config_new_get_privkey_libssh(const char *privkey_path, char **privkey, ssh_key *priv_sshkey)
508{
509 int ret;
510
511 *priv_sshkey = NULL;
512
513 ret = ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, priv_sshkey);
514 if (ret) {
515 ERR(NULL, "Importing privkey from file \"%s\" failed.", privkey_path);
516 return ret;
517 }
518
519 ret = ssh_pki_export_privkey_base64(*priv_sshkey, NULL, NULL, NULL, privkey);
520 if (ret) {
521 ERR(NULL, "Exporting privkey from file \"%s\" to base64 failed.", privkey_path);
522 }
523
524 return ret;
525}
526
roman3f9b65c2023-06-05 14:26:58 +0200527int
528nc_server_config_new_get_keys(const char *privkey_path, const char *pubkey_path,
529 char **privkey, char **pubkey, NC_PRIVKEY_FORMAT *privkey_type, NC_PUBKEY_FORMAT *pubkey_type)
530{
531 int ret = 0;
532 EVP_PKEY *priv_pkey = NULL;
533 ssh_key priv_sshkey = NULL;
534 FILE *f_privkey = NULL;
535 char *header = NULL;
536 size_t len = 0;
537
538 NC_CHECK_ARG_RET(NULL, privkey_path, privkey, pubkey, privkey_type, 1);
539
540 *privkey = NULL;
541 *pubkey = NULL;
542
543 /* get private key first */
544 f_privkey = fopen(privkey_path, "r");
545 if (!f_privkey) {
546 ERR(NULL, "Unable to open file \"%s\".", privkey_path);
547 ret = 1;
548 goto cleanup;
549 }
550
551 if (getline(&header, &len, f_privkey) < 0) {
552 ERR(NULL, "Error reading header from file \"%s\".", privkey_path);
553 ret = 1;
554 goto cleanup;
555 }
556 rewind(f_privkey);
557
558 if (!strncmp(header, NC_PKCS8_PRIVKEY_HEADER, strlen(NC_PKCS8_PRIVKEY_HEADER))) {
559 /* it's PKCS8 (X.509) private key */
560 *privkey_type = NC_PRIVKEY_FORMAT_X509;
561 ret = nc_server_config_new_get_privkey_openssl(f_privkey, privkey, &priv_pkey);
roman2eab4742023-06-06 10:00:26 +0200562 } else if (!strncmp(header, NC_OPENSSH_PRIVKEY_HEADER, strlen(NC_OPENSSH_PRIVKEY_HEADER))) {
roman3f9b65c2023-06-05 14:26:58 +0200563 /* it's OpenSSH private key */
564 *privkey_type = NC_PRIVKEY_FORMAT_OPENSSH;
565 ret = nc_server_config_new_get_privkey_libssh(privkey_path, privkey, &priv_sshkey);
566 } else if (!strncmp(header, NC_PKCS1_RSA_PRIVKEY_HEADER, strlen(NC_PKCS1_RSA_PRIVKEY_HEADER))) {
567 /* it's RSA privkey in PKCS1 format */
568 *privkey_type = NC_PRIVKEY_FORMAT_RSA;
569 ret = nc_server_config_new_get_privkey_libssh(privkey_path, privkey, &priv_sshkey);
570 } else if (!strncmp(header, NC_SEC1_EC_PRIVKEY_HEADER, strlen(NC_SEC1_EC_PRIVKEY_HEADER))) {
571 /* it's EC privkey in SEC1 format */
572 *privkey_type = NC_PRIVKEY_FORMAT_EC;
573 ret = nc_server_config_new_get_privkey_libssh(privkey_path, privkey, &priv_sshkey);
roman2eab4742023-06-06 10:00:26 +0200574 } else {
roman3f9b65c2023-06-05 14:26:58 +0200575 ERR(NULL, "Private key format not supported.");
576 ret = 1;
577 goto cleanup;
578 }
579
580 if (ret) {
581 goto cleanup;
582 }
583
584 if (pubkey_path) {
585 ret = nc_server_config_new_get_pubkey(pubkey_path, pubkey, pubkey_type);
586 } else {
587 ret = nc_server_config_new_privkey_to_pubkey(priv_pkey, priv_sshkey, *privkey_type, pubkey, pubkey_type);
588 }
589
590 if (ret) {
591 ERR(NULL, "Getting public key failed.");
592 goto cleanup;
593 }
594
595cleanup:
596 if (f_privkey) {
597 fclose(f_privkey);
598 }
599
600 free(header);
601
602 ssh_key_free(priv_sshkey);
603 EVP_PKEY_free(priv_pkey);
604
605 return ret;
606}
607
608API int
609nc_server_config_new_address_port(const struct ly_ctx *ctx, const char *endpt_name, NC_TRANSPORT_IMPL transport,
610 const char *address, const char *port, struct lyd_node **config)
611{
612 int ret = 0;
613 char *tree_path = NULL;
614 struct lyd_node *new_tree, *port_node;
615
616 NC_CHECK_ARG_RET(NULL, address, port, ctx, endpt_name, config, 1);
617
618 /* prepare path for instertion of leaves later */
roman3f9b65c2023-06-05 14:26:58 +0200619 if (transport == NC_TI_LIBSSH) {
620 asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/ssh/tcp-server-parameters", endpt_name);
roman2eab4742023-06-06 10:00:26 +0200621 } else if (transport == NC_TI_OPENSSL) {
roman3f9b65c2023-06-05 14:26:58 +0200622 asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/tls/tcp-server-parameters", endpt_name);
roman2eab4742023-06-06 10:00:26 +0200623 } else {
624 ERR(NULL, "Transport not supported.");
625 ret = 1;
626 goto cleanup;
roman3f9b65c2023-06-05 14:26:58 +0200627 }
roman3f9b65c2023-06-05 14:26:58 +0200628 if (!tree_path) {
roman2eab4742023-06-06 10:00:26 +0200629 ERRMEM;
roman3f9b65c2023-06-05 14:26:58 +0200630 ret = 1;
631 goto cleanup;
632 }
633
634 /* create all the nodes in the path */
635 ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree);
636 if (ret) {
637 goto cleanup;
638 }
639 if (!*config) {
640 *config = new_tree;
641 }
642
643 if (!new_tree) {
644 /* no new nodes were created */
645 ret = lyd_find_path(*config, tree_path, 0, &new_tree);
646 } else {
647 /* config was NULL */
648 ret = lyd_find_path(new_tree, tree_path, 0, &new_tree);
649 }
650 if (ret) {
651 ERR(NULL, "Unable to find tcp-server-parameters container.");
652 goto cleanup;
653 }
654
655 ret = lyd_new_term(new_tree, NULL, "local-address", address, 0, NULL);
656 if (ret) {
657 goto cleanup;
658 }
659
660 ret = lyd_find_path(new_tree, "local-port", 0, &port_node);
661 if (!ret) {
662 ret = lyd_change_term(port_node, port);
663 } else if (ret == LY_ENOTFOUND) {
664 ret = lyd_new_term(new_tree, NULL, "local-port", port, 0, NULL);
665 }
666
667 if (ret && (ret != LY_EEXIST) && (ret != LY_ENOT)) {
668 /* only fail if there was actually an error */
669 goto cleanup;
670 }
671
672 /* Add all default nodes */
673 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
674 if (ret) {
675 goto cleanup;
676 }
677cleanup:
678 free(tree_path);
679 return ret;
680}
roman2eab4742023-06-06 10:00:26 +0200681
682#endif /* NC_ENABLED_SSH_TLS */