blob: dd18b96f5e4bb042e02b4b3049aeeb9b75b571b5 [file] [log] [blame]
roman27215242023-03-10 14:55:00 +01001/**
roman466719d2023-05-05 16:14:37 +02002 * @file config_new_ssh.c
roman27215242023-03-10 14:55:00 +01003 * @author Roman Janota <janota@cesnet.cz>
4 * @brief libnetconf2 server new configuration creation functions
5 *
6 * @copyright
roman466719d2023-05-05 16:14:37 +02007 * Copyright (c) 2023 CESNET, z.s.p.o.
roman27215242023-03-10 14:55:00 +01008 *
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
18#include <assert.h>
roman31820092023-03-24 15:26:21 +010019#include <crypt.h>
roman27215242023-03-10 14:55:00 +010020#include <ctype.h>
21#include <stdarg.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
roman31820092023-03-24 15:26:21 +010025#include <unistd.h>
roman27215242023-03-10 14:55:00 +010026
roman4f9bb442023-03-24 09:05:37 +010027#include <openssl/buffer.h>
roman27215242023-03-10 14:55:00 +010028#include <openssl/err.h>
roman4f9bb442023-03-24 09:05:37 +010029#include <openssl/evp.h>
roman27215242023-03-10 14:55:00 +010030#include <openssl/pem.h>
31
32#include "compat.h"
roman466719d2023-05-05 16:14:37 +020033#include "config_new_ssh.h"
roman27215242023-03-10 14:55:00 +010034#include "libnetconf.h"
35#include "server_config.h"
roman4f9bb442023-03-24 09:05:37 +010036#include "session_server.h"
roman27215242023-03-10 14:55:00 +010037
roman31820092023-03-24 15:26:21 +010038#if !defined (HAVE_CRYPT_R)
39extern pthread_mutex_t crypt_lock;
40#endif
41
roman27215242023-03-10 14:55:00 +010042static int
roman466719d2023-05-05 16:14:37 +020043nc_server_config_new_ssh_read_ssh2_pubkey(FILE *f, char **pubkey)
roman27215242023-03-10 14:55:00 +010044{
roman466719d2023-05-05 16:14:37 +020045 char *buffer = NULL;
46 size_t size = 0, pubkey_len = 0;
47 void *tmp;
48 ssize_t read;
roman27215242023-03-10 14:55:00 +010049 int ret = 0;
roman27215242023-03-10 14:55:00 +010050
roman466719d2023-05-05 16:14:37 +020051 while ((read = getline(&buffer, &size, f)) > 0) {
52 if (!strncmp(buffer, "----", 4)) {
53 continue;
54 }
roman27215242023-03-10 14:55:00 +010055
roman466719d2023-05-05 16:14:37 +020056 if (!strncmp(buffer, "Comment:", 8)) {
57 continue;
58 }
59
60 if (buffer[read - 1] == '\n') {
61 read--;
62 }
63
64 tmp = realloc(*pubkey, pubkey_len + read + 1);
65 if (!tmp) {
66 ERRMEM;
67 ret = 1;
68 goto cleanup;
69 }
70
71 *pubkey = tmp;
72 memcpy(*pubkey + pubkey_len, buffer, read);
73 pubkey_len += read;
74 }
75
76 if (!pubkey_len) {
77 ERR(NULL, "Unexpected public key format.");
roman27215242023-03-10 14:55:00 +010078 ret = 1;
79 goto cleanup;
80 }
81
roman466719d2023-05-05 16:14:37 +020082 (*pubkey)[pubkey_len] = '\0';
roman27215242023-03-10 14:55:00 +010083
roman466719d2023-05-05 16:14:37 +020084cleanup:
85 free(buffer);
86 return ret;
87}
88
89static int
90nc_server_config_new_ssh_read_pubkey_openssl(FILE *f, char **pubkey)
91{
92 int ret = 0;
93 EVP_PKEY *pkey;
94 BIO *bio;
95 char *key = NULL;
96 int pub_len;
97
98 /* read the pubkey from file */
99 pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL);
100 if (!pkey) {
roman27215242023-03-10 14:55:00 +0100101 ret = -1;
102 goto cleanup;
103 }
104
roman466719d2023-05-05 16:14:37 +0200105 bio = BIO_new(BIO_s_mem());
106 if (!bio) {
107 ret = -1;
108 goto cleanup;
109 }
110
111 /* write the pubkey into bio */
112 ret = PEM_write_bio_PUBKEY(bio, pkey);
roman27215242023-03-10 14:55:00 +0100113 if (!ret) {
114 ret = -1;
115 goto cleanup;
116 }
117
roman466719d2023-05-05 16:14:37 +0200118 pub_len = BIO_pending(bio);
119 if (pub_len <= 0) {
120 ret = -1;
121 goto cleanup;
122 }
123
124 /* get pubkey's length */
125 key = malloc(pub_len + 1);
126 if (!key) {
127 ERRMEM;
128 ret = 1;
129 goto cleanup;
130 }
131
132 /* read the public key from bio */
133 ret = BIO_read(bio, key, pub_len);
134 if (ret <= 0) {
135 ret = -1;
136 goto cleanup;
137 }
138 key[pub_len] = '\0';
139
140 /* strip the pubkey of the header and footer */
141 *pubkey = strdup(key + strlen(NC_SUBJECT_PUBKEY_INFO_HEADER));
142 if (!*pubkey) {
143 ERRMEM;
144 ret = 1;
145 goto cleanup;
146 }
147
148 (*pubkey)[strlen(*pubkey) - strlen(NC_SUBJECT_PUBKEY_INFO_FOOTER)] = '\0';
149
150 ret = 0;
151cleanup:
152 if (ret == -1) {
153 ERR(NULL, "Error getting public key from file (OpenSSL Error): \"%s\".", ERR_reason_error_string(ERR_get_error()));
154 ret = 1;
155 }
156
157 BIO_free(bio);
158 EVP_PKEY_free(pkey);
159 free(key);
160
161 return ret;
162}
163
164static int
165nc_server_config_new_ssh_read_pubkey_libssh(const char *pubkey_path, char **pubkey)
166{
167 int ret = 0;
168 ssh_key pub_sshkey = NULL;
169
170 ret = ssh_pki_import_pubkey_file(pubkey_path, &pub_sshkey);
171 if (ret) {
172 ERR(NULL, "Importing public key from file \"%s\" failed.", pubkey_path);
173 return ret;
174 }
175
176 ret = ssh_pki_export_pubkey_base64(pub_sshkey, pubkey);
177 if (ret) {
178 ERR(NULL, "Exporting public key to base64 failed.");
179 }
180
181 ssh_key_free(pub_sshkey);
182 return ret;
183}
184
185static int
186nc_server_config_new_ssh_get_pubkey(const char *pubkey_path, char **pubkey, NC_SSH_PUBKEY_TYPE *pubkey_type)
187{
188 int ret = 0;
189 FILE *f = NULL;
190 char *header = NULL;
191 size_t len = 0;
192
193 NC_CHECK_ARG_RET(NULL, pubkey, pubkey_type, 1);
194
195 *pubkey = NULL;
196
197 f = fopen(pubkey_path, "r");
198 if (!f) {
199 ERR(NULL, "Unable to open file \"%s\".", pubkey_path);
200 ret = 1;
201 goto cleanup;
202 }
203
204 if (getline(&header, &len, f) < 0) {
205 ERR(NULL, "Error reading header from file \"%s\".", pubkey_path);
206 ret = 1;
207 goto cleanup;
208 }
209 rewind(f);
210
211 if (!strncmp(header, NC_SUBJECT_PUBKEY_INFO_HEADER, strlen(NC_SUBJECT_PUBKEY_INFO_HEADER))) {
212 /* it's subject public key info public key */
213 ret = nc_server_config_new_ssh_read_pubkey_openssl(f, pubkey);
214 *pubkey_type = NC_SSH_PUBKEY_X509;
215 } else if (!strncmp(header, NC_SSH2_PUBKEY_HEADER, strlen(NC_SSH2_PUBKEY_HEADER))) {
216 /* it's ssh2 public key */
217 ret = nc_server_config_new_ssh_read_ssh2_pubkey(f, pubkey);
218 *pubkey_type = NC_SSH_PUBKEY_SSH2;
219 } else {
220 /* it's probably OpenSSH public key */
221 ret = nc_server_config_new_ssh_read_pubkey_libssh(pubkey_path, pubkey);
222 *pubkey_type = NC_SSH_PUBKEY_SSH2;
223 }
224
225 if (ret) {
226 ERR(NULL, "Error getting public key from file \"%s\".", pubkey_path);
227 goto cleanup;
228 }
229
230cleanup:
231 if (f) {
232 fclose(f);
233 }
234
235 free(header);
236
237 return ret;
238}
239
240static int
241nc_server_config_new_ssh_get_privkey_openssl(FILE *f, char **privkey, EVP_PKEY **priv_pkey)
242{
243 int ret = 0, priv_len;
244 BIO *bio = NULL;
245
246 NC_CHECK_ARG_RET(NULL, privkey, priv_pkey, 1);
247
248 /* read private key from file */
249 *priv_pkey = PEM_read_PrivateKey(f, NULL, NULL, NULL);
250 if (!*priv_pkey) {
251 ret = -1;
252 goto cleanup;
253 }
254
255 bio = BIO_new(BIO_s_mem());
256 if (!bio) {
257 ret = -1;
258 goto cleanup;
259 }
260
261 /* write the private key in to bio */
262 ret = PEM_write_bio_PrivateKey(bio, *priv_pkey, NULL, NULL, 0, NULL, NULL);
263 if (!ret) {
264 ret = -1;
265 goto cleanup;
266 }
267
268 priv_len = BIO_pending(bio);
roman27215242023-03-10 14:55:00 +0100269 if (priv_len <= 0) {
270 ret = -1;
271 goto cleanup;
272 }
273
roman466719d2023-05-05 16:14:37 +0200274 /* get private key's length */
roman27215242023-03-10 14:55:00 +0100275 *privkey = malloc(priv_len + 1);
276 if (!*privkey) {
277 ERRMEM;
278 ret = 1;
279 goto cleanup;
280 }
281
roman466719d2023-05-05 16:14:37 +0200282 /* read the private key from bio */
283 ret = BIO_read(bio, *privkey, priv_len);
roman27215242023-03-10 14:55:00 +0100284 if (ret <= 0) {
285 ret = -1;
286 goto cleanup;
287 }
288 (*privkey)[priv_len] = '\0';
289
roman466719d2023-05-05 16:14:37 +0200290 ret = 0;
291cleanup:
292 if (ret < 0) {
293 ERR(NULL, "Getting private key from file failed (%s).", ERR_reason_error_string(ERR_get_error()));
roman27215242023-03-10 14:55:00 +0100294 }
roman466719d2023-05-05 16:14:37 +0200295 BIO_free(bio);
296 return ret;
297}
roman27215242023-03-10 14:55:00 +0100298
roman466719d2023-05-05 16:14:37 +0200299static int
300nc_server_config_new_ssh_privkey_to_pubkey_openssl(EVP_PKEY *priv_pkey, char **pubkey)
301{
302 int ret = 0, pub_len;
303 BIO *bio = NULL;
304
305 bio = BIO_new(BIO_s_mem());
306 if (!bio) {
roman27215242023-03-10 14:55:00 +0100307 ret = -1;
308 goto cleanup;
309 }
310
roman466719d2023-05-05 16:14:37 +0200311 /* write the pubkey into bio */
312 ret = PEM_write_bio_PUBKEY(bio, priv_pkey);
roman27215242023-03-10 14:55:00 +0100313 if (!ret) {
314 ret = -1;
315 goto cleanup;
316 }
317
roman466719d2023-05-05 16:14:37 +0200318 /* get the length of the pubkey */
319 pub_len = BIO_pending(bio);
roman27215242023-03-10 14:55:00 +0100320 if (pub_len <= 0) {
321 ret = -1;
322 goto cleanup;
323 }
324
325 *pubkey = malloc(pub_len + 1);
326 if (!*pubkey) {
327 ERRMEM;
328 ret = 1;
329 goto cleanup;
330 }
331
roman466719d2023-05-05 16:14:37 +0200332 /* read the pubkey from the bio */
333 ret = BIO_read(bio, *pubkey, pub_len);
roman27215242023-03-10 14:55:00 +0100334 if (ret <= 0) {
335 ret = -1;
336 goto cleanup;
337 }
338 (*pubkey)[pub_len] = '\0';
339
340 ret = 0;
roman466719d2023-05-05 16:14:37 +0200341
roman27215242023-03-10 14:55:00 +0100342cleanup:
343 if (ret < 0) {
roman466719d2023-05-05 16:14:37 +0200344 ERR(NULL, "Converting private to public key failed (%s).", ERR_reason_error_string(ERR_get_error()));
roman27215242023-03-10 14:55:00 +0100345 }
roman466719d2023-05-05 16:14:37 +0200346 BIO_free(bio);
347 return ret;
348}
349
350static int
351nc_server_config_new_ssh_privkey_to_pubkey_libssh(const ssh_key priv_sshkey, char **pubkey)
352{
353 int ret;
354 ssh_key pub_sshkey = NULL;
355
356 ret = ssh_pki_export_privkey_to_pubkey(priv_sshkey, &pub_sshkey);
357 if (ret) {
358 ERR(NULL, "Exporting privkey to pubkey failed.");
359 return ret;
360 }
361
362 ret = ssh_pki_export_pubkey_base64(pub_sshkey, pubkey);
363 if (ret) {
364 ERR(NULL, "Exporting pubkey to base64 failed.");
365 }
366
367 ssh_key_free(pub_sshkey);
368 return ret;
369}
370
371static int
372nc_server_config_new_ssh_privkey_to_pubkey(EVP_PKEY *priv_pkey, const ssh_key priv_sshkey, NC_PRIVKEY_FORMAT privkey_type, char **pubkey, NC_SSH_PUBKEY_TYPE *pubkey_type)
373{
374 switch (privkey_type) {
375 case NC_PRIVKEY_FORMAT_RSA:
376 case NC_PRIVKEY_FORMAT_EC:
377 case NC_PRIVKEY_FORMAT_OPENSSH:
378 *pubkey_type = NC_SSH_PUBKEY_SSH2;
379 return nc_server_config_new_ssh_privkey_to_pubkey_libssh(priv_sshkey, pubkey);
380 case NC_PRIVKEY_FORMAT_PKCS8:
381 *pubkey_type = NC_SSH_PUBKEY_X509;
382 return nc_server_config_new_ssh_privkey_to_pubkey_openssl(priv_pkey, pubkey);
383 }
384
385 return 1;
386}
387
388static int
389nc_server_config_new_ssh_get_privkey_libssh(const char *privkey_path, char **privkey, ssh_key *priv_sshkey)
390{
391 int ret;
392
393 *priv_sshkey = NULL;
394
395 ret = ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, priv_sshkey);
396 if (ret) {
397 ERR(NULL, "Importing privkey from file \"%s\" failed.", privkey_path);
398 return ret;
399 }
400
401 ret = ssh_pki_export_privkey_base64(*priv_sshkey, NULL, NULL, NULL, privkey);
402 if (ret) {
403 ERR(NULL, "Exporting privkey from file \"%s\" to base64 failed.", privkey_path);
404 }
405
406 return ret;
407}
408
409static int
410nc_server_config_new_ssh_get_keys(const char *privkey_path, const char *pubkey_path,
411 char **privkey, char **pubkey, NC_PRIVKEY_FORMAT *privkey_type, NC_SSH_PUBKEY_TYPE *pubkey_type)
412{
413 int ret = 0;
414 EVP_PKEY *priv_pkey = NULL;
415 ssh_key priv_sshkey = NULL;
416 FILE *f_privkey = NULL;
417 char *header = NULL;
418 size_t len = 0;
419
420 NC_CHECK_ARG_RET(NULL, privkey_path, privkey, pubkey, privkey_type, 1);
421
422 *privkey = NULL;
423 *pubkey = NULL;
424
425 /* get private key first */
426 f_privkey = fopen(privkey_path, "r");
427 if (!f_privkey) {
428 ERR(NULL, "Unable to open file \"%s\".", privkey_path);
429 ret = 1;
430 goto cleanup;
431 }
432
433 if (getline(&header, &len, f_privkey) < 0) {
434 ERR(NULL, "Error reading header from file \"%s\".", privkey_path);
435 ret = 1;
436 goto cleanup;
437 }
438 rewind(f_privkey);
439
440 if (!strncmp(header, NC_PKCS8_PRIVKEY_HEADER, strlen(NC_PKCS8_PRIVKEY_HEADER))) {
441 /* it's PKCS8 (X.509) private key */
442 *privkey_type = NC_PRIVKEY_FORMAT_PKCS8;
443 ret = nc_server_config_new_ssh_get_privkey_openssl(f_privkey, privkey, &priv_pkey);
444 } else if (!strncmp(header, NC_OPENSSH_PRIVKEY_HEADER, strlen(NC_OPENSSH_PRIVKEY_HEADER))) {
445 /* it's OpenSSH private key */
446 *privkey_type = NC_PRIVKEY_FORMAT_OPENSSH;
447 ret = nc_server_config_new_ssh_get_privkey_libssh(privkey_path, privkey, &priv_sshkey);
448 } else if (!strncmp(header, NC_PKCS1_RSA_PRIVKEY_HEADER, strlen(NC_PKCS1_RSA_PRIVKEY_HEADER))) {
449 /* it's RSA privkey in PKCS1 format */
450 *privkey_type = NC_PRIVKEY_FORMAT_RSA;
451 ret = nc_server_config_new_ssh_get_privkey_libssh(privkey_path, privkey, &priv_sshkey);
452 } else if (!strncmp(header, NC_SEC1_EC_PRIVKEY_HEADER, strlen(NC_SEC1_EC_PRIVKEY_HEADER))) {
453 /* it's EC privkey in SEC1 format */
454 *privkey_type = NC_PRIVKEY_FORMAT_EC;
455 ret = nc_server_config_new_ssh_get_privkey_libssh(privkey_path, privkey, &priv_sshkey);
456 } else {
457 ERR(NULL, "Private key format not supported.");
458 ret = 1;
459 goto cleanup;
460 }
461
462 if (ret) {
463 goto cleanup;
464 }
465
466 if (pubkey_path) {
467 ret = nc_server_config_new_ssh_get_pubkey(pubkey_path, pubkey, pubkey_type);
468 } else {
469 ret = nc_server_config_new_ssh_privkey_to_pubkey(priv_pkey, priv_sshkey, *privkey_type, pubkey, pubkey_type);
470 }
471
472 if (ret) {
473 ERR(NULL, "Getting public key failed.");
474 goto cleanup;
475 }
476
477cleanup:
roman27215242023-03-10 14:55:00 +0100478 if (f_privkey) {
479 fclose(f_privkey);
480 }
roman466719d2023-05-05 16:14:37 +0200481
482 free(header);
483
484 ssh_key_free(priv_sshkey);
485 EVP_PKEY_free(priv_pkey);
486
roman27215242023-03-10 14:55:00 +0100487 return ret;
488}
489
490API int
roman466719d2023-05-05 16:14:37 +0200491nc_server_config_new_ssh_hostkey(const char *privkey_path, const char *pubkey_path, const struct ly_ctx *ctx,
roman27215242023-03-10 14:55:00 +0100492 const char *endpt_name, const char *hostkey_name, struct lyd_node **config)
493{
494 int ret = 0;
roman466719d2023-05-05 16:14:37 +0200495 char *pubkey = NULL, *privkey = NULL, *pubkey_stripped, *privkey_stripped;
roman27215242023-03-10 14:55:00 +0100496 struct lyd_node *new_tree;
497 char *tree_path = NULL;
roman466719d2023-05-05 16:14:37 +0200498 NC_PRIVKEY_FORMAT privkey_type;
499 NC_SSH_PUBKEY_TYPE pubkey_type;
roman27215242023-03-10 14:55:00 +0100500
roman40672412023-05-04 11:10:22 +0200501 NC_CHECK_ARG_RET(NULL, privkey_path, config, ctx, endpt_name, hostkey_name, 1);
roman27215242023-03-10 14:55:00 +0100502
503 /* get the keys as a string from the given files */
roman466719d2023-05-05 16:14:37 +0200504 ret = nc_server_config_new_ssh_get_keys(privkey_path, pubkey_path, &privkey, &pubkey, &privkey_type, &pubkey_type);
roman27215242023-03-10 14:55:00 +0100505 if (ret) {
506 ERR(NULL, "Getting keys from file(s) failed.");
507 goto cleanup;
508 }
509
510 /* prepare path where leaves will get inserted */
511 asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/ssh/ssh-server-parameters/"
512 "server-identity/host-key[name='%s']/public-key/local-definition", endpt_name, hostkey_name);
513 if (!tree_path) {
514 ERRMEM;
515 ret = 1;
516 goto cleanup;
517 }
518
519 /* create all the nodes in the path if they weren't there */
520 ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree);
521 if (ret) {
522 goto cleanup;
523 }
524 if (!*config) {
525 *config = new_tree;
526 }
527
528 /* give the top level container create operation */
529 ret = lyd_new_meta(ctx, *config, NULL, "yang:operation", "create", 0, NULL);
530 if (ret) {
531 goto cleanup;
532 }
533
534 /* find the node where leaves will get inserted */
535 ret = lyd_find_path(*config, tree_path, 0, &new_tree);
536 if (ret) {
537 goto cleanup;
538 }
539
540 /* insert pubkey format */
roman466719d2023-05-05 16:14:37 +0200541 if (pubkey_type == NC_SSH_PUBKEY_SSH2) {
roman27215242023-03-10 14:55:00 +0100542 ret = lyd_new_term(new_tree, NULL, "public-key-format", "ietf-crypto-types:ssh-public-key-format", 0, NULL);
543 } else {
544 ret = lyd_new_term(new_tree, NULL, "public-key-format", "ietf-crypto-types:subject-public-key-info-format", 0, NULL);
545 }
546 if (ret) {
547 goto cleanup;
548 }
549
roman466719d2023-05-05 16:14:37 +0200550 /* strip pubkey's header and footer only if it's generated from pkcs8 key (using OpenSSL),
551 * otherwise it's already stripped
552 */
553 if (!pubkey_path && (privkey_type == NC_PRIVKEY_FORMAT_PKCS8)) {
554 pubkey_stripped = pubkey + strlen("-----BEGIN PUBLIC KEY-----") + 1;
555 pubkey_stripped[strlen(pubkey_stripped) - strlen("-----END PUBLIC KEY-----") - 2] = '\0';
556 } else {
557 pubkey_stripped = pubkey;
558 }
roman27215242023-03-10 14:55:00 +0100559
560 /* insert pubkey b64 */
roman466719d2023-05-05 16:14:37 +0200561 ret = lyd_new_term(new_tree, NULL, "public-key", pubkey_stripped, 0, NULL);
roman27215242023-03-10 14:55:00 +0100562 if (ret) {
563 goto cleanup;
564 }
565
roman466719d2023-05-05 16:14:37 +0200566 /* insert private key format */
567 if (privkey_type == NC_PRIVKEY_FORMAT_RSA) {
roman27215242023-03-10 14:55:00 +0100568 ret = lyd_new_term(new_tree, NULL, "private-key-format", "ietf-crypto-types:rsa-private-key-format", 0, NULL);
roman466719d2023-05-05 16:14:37 +0200569 } else if (privkey_type == NC_PRIVKEY_FORMAT_EC) {
roman27215242023-03-10 14:55:00 +0100570 ret = lyd_new_term(new_tree, NULL, "private-key-format", "ietf-crypto-types:ec-private-key-format", 0, NULL);
roman466719d2023-05-05 16:14:37 +0200571 } else if (privkey_type == NC_PRIVKEY_FORMAT_PKCS8) {
572 ret = lyd_new_term(new_tree, NULL, "private-key-format", "libnetconf2-netconf-server:subject-private-key-info-format", 0, NULL);
573 } else if (privkey_type == NC_PRIVKEY_FORMAT_OPENSSH) {
574 ret = lyd_new_term(new_tree, NULL, "private-key-format", "libnetconf2-netconf-server:openssh-private-key-format", 0, NULL);
roman27215242023-03-10 14:55:00 +0100575 } else {
576 ERR(NULL, "Private key type not supported.");
577 ret = 1;
578 }
roman466719d2023-05-05 16:14:37 +0200579
roman27215242023-03-10 14:55:00 +0100580 if (ret) {
581 goto cleanup;
582 }
583
roman466719d2023-05-05 16:14:37 +0200584 if (privkey_type == NC_PRIVKEY_FORMAT_OPENSSH) {
585 /* only OpenSSH private keys have different header and footer after processing */
586 privkey_stripped = privkey + strlen(NC_OPENSSH_PRIVKEY_HEADER);
587 privkey_stripped[strlen(privkey_stripped) - strlen(NC_OPENSSH_PRIVKEY_FOOTER)] = '\0';
588 } else {
589 /* the rest share the same header and footer */
590 privkey_stripped = privkey + strlen(NC_PKCS8_PRIVKEY_HEADER);
591 privkey_stripped[strlen(privkey_stripped) - strlen(NC_PKCS8_PRIVKEY_FOOTER)] = '\0';
592 }
roman27215242023-03-10 14:55:00 +0100593
roman466719d2023-05-05 16:14:37 +0200594 ret = lyd_new_term(new_tree, NULL, "cleartext-private-key", privkey_stripped, 0, NULL);
roman27215242023-03-10 14:55:00 +0100595 if (ret) {
596 goto cleanup;
597 }
598
roman4f9bb442023-03-24 09:05:37 +0100599 /* Add all default nodes */
600 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
601 if (ret) {
602 goto cleanup;
603 }
604
roman27215242023-03-10 14:55:00 +0100605cleanup:
roman466719d2023-05-05 16:14:37 +0200606 free(privkey);
607 free(pubkey);
roman27215242023-03-10 14:55:00 +0100608 free(tree_path);
609 return ret;
610}
611
612API int
roman466719d2023-05-05 16:14:37 +0200613nc_server_config_new_ssh_address_port(const char *address, const char *port, const struct ly_ctx *ctx,
roman27215242023-03-10 14:55:00 +0100614 const char *endpt_name, struct lyd_node **config)
615{
616 int ret = 0;
617 char *tree_path = NULL;
roman4f9bb442023-03-24 09:05:37 +0100618 struct lyd_node *new_tree, *port_node;
roman27215242023-03-10 14:55:00 +0100619
roman40672412023-05-04 11:10:22 +0200620 NC_CHECK_ARG_RET(NULL, address, port, ctx, endpt_name, config, 1);
roman27215242023-03-10 14:55:00 +0100621
622 /* prepare path for instertion of leaves later */
623 asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/ssh/tcp-server-parameters", endpt_name);
624 if (!tree_path) {
625 ERRMEM;
626 ret = 1;
627 goto cleanup;
628 }
629
630 /* create all the nodes in the path */
631 ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree);
632 if (ret) {
633 goto cleanup;
634 }
635 if (!*config) {
636 *config = new_tree;
637 }
638
roman4f9bb442023-03-24 09:05:37 +0100639 if (!new_tree) {
640 /* no new nodes were created */
641 ret = lyd_find_path(*config, tree_path, 0, &new_tree);
642 } else {
643 /* config was NULL */
644 ret = lyd_find_path(new_tree, tree_path, 0, &new_tree);
645 }
roman27215242023-03-10 14:55:00 +0100646 if (ret) {
647 ERR(NULL, "Unable to find tcp-server-parameters container.");
648 goto cleanup;
649 }
650
651 ret = lyd_new_term(new_tree, NULL, "local-address", address, 0, NULL);
652 if (ret) {
653 goto cleanup;
654 }
655
roman4f9bb442023-03-24 09:05:37 +0100656 ret = lyd_find_path(new_tree, "local-port", 0, &port_node);
657 if (!ret) {
658 ret = lyd_change_term(port_node, port);
659 } else if (ret == LY_ENOTFOUND) {
660 ret = lyd_new_term(new_tree, NULL, "local-port", port, 0, NULL);
661 }
662
romana08a05f2023-04-05 14:46:29 +0200663 if (ret && (ret != LY_EEXIST) && (ret != LY_ENOT)) {
664 /* only fail if there was actually an error */
roman27215242023-03-10 14:55:00 +0100665 goto cleanup;
666 }
667
roman4f9bb442023-03-24 09:05:37 +0100668 /* Add all default nodes */
669 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
670 if (ret) {
671 goto cleanup;
672 }
roman27215242023-03-10 14:55:00 +0100673cleanup:
674 free(tree_path);
675 return ret;
676}
677
678static int
roman466719d2023-05-05 16:14:37 +0200679nc_server_config_new_ssh_transport_params_prep(const struct ly_ctx *ctx, const char *endpt_name,
roman27215242023-03-10 14:55:00 +0100680 struct lyd_node *config, struct lyd_node **new_tree, struct lyd_node **alg_tree)
681{
682 int ret = 0;
683 char *tree_path = NULL;
684
685 /* prepare path */
686 asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/"
687 "ssh/ssh-server-parameters/transport-params", endpt_name);
688 if (!tree_path) {
689 ERRMEM;
690 ret = 1;
691 goto cleanup;
692 }
693
694 /* create all the nodes in the path */
695 ret = lyd_new_path2(config, ctx, tree_path, NULL, 0, 0, LYD_NEW_PATH_UPDATE, new_tree, alg_tree);
696 if (ret) {
697 ERR(NULL, "Creating new path to transport-params failed.");
698 goto cleanup;
699 }
700
roman4f9bb442023-03-24 09:05:37 +0100701 if (!*alg_tree) {
702 /* no new nodes added */
703 ret = lyd_find_path(config, tree_path, 0, alg_tree);
704 if (ret) {
705 goto cleanup;
706 }
707 }
708
roman27215242023-03-10 14:55:00 +0100709cleanup:
710 free(tree_path);
711 return ret;
712}
713
714static int
roman466719d2023-05-05 16:14:37 +0200715nc_server_config_new_ssh_transport_params(const struct ly_ctx *ctx, NC_ALG_TYPE alg_type, int alg_count, va_list ap,
roman27215242023-03-10 14:55:00 +0100716 struct lyd_node *tree)
717{
718 int i, ret = 0;
719 char *alg, *alg_ident;
720 const char *module, *alg_path, *old_path;
721 struct lyd_node *old = NULL;
722
723 /* get the correct module with the indentity base and the path in the ietf-netconf-server module */
724 switch (alg_type) {
725 case NC_ALG_HOSTKEY:
726 module = "iana-ssh-public-key-algs";
727 alg_path = "host-key/host-key-alg";
728 old_path = "host-key";
729 break;
730 case NC_ALG_KEY_EXCHANGE:
731 module = "iana-ssh-key-exchange-algs";
732 alg_path = "key-exchange/key-exchange-alg";
733 old_path = "key-exchange";
734 break;
735 case NC_ALG_ENCRYPTION:
736 module = "iana-ssh-encryption-algs";
737 alg_path = "encryption/encryption-alg";
738 old_path = "encryption";
739 break;
740 case NC_ALG_MAC:
741 module = "iana-ssh-mac-algs";
742 alg_path = "mac/mac-alg";
743 old_path = "mac";
744 break;
745 default:
746 ret = 1;
747 ERR(NULL, "Unknown algorithm type.");
748 goto cleanup;
749 }
750
751 /* delete all older algorithms (if any) se they can be replaced by the new ones */
752 lyd_find_path(tree, old_path, 0, &old);
753 if (old) {
754 lyd_free_tree(old);
755 }
756
757 for (i = 0; i < alg_count; i++) {
758 alg = va_arg(ap, char *);
759
760 asprintf(&alg_ident, "%s:%s", module, alg);
761 if (!alg_ident) {
762 ERRMEM;
763 ret = 1;
764 goto cleanup;
765 }
766
767 /* create the leaf list */
768 ret = lyd_new_path(tree, ctx, alg_path, alg_ident, 0, NULL);
769 if (ret) {
770 ERR(NULL, "Creating new algorithm leaf-list failed.");
771 goto cleanup;
772 }
773
774 free(alg_ident);
775 }
776
777cleanup:
778 va_end(ap);
779 return ret;
780}
781
782API int
roman466719d2023-05-05 16:14:37 +0200783nc_server_config_new_ssh_host_key_algs(const struct ly_ctx *ctx, const char *endpt_name, struct lyd_node **config,
roman27215242023-03-10 14:55:00 +0100784 int alg_count, ...)
785{
786 int ret = 0;
787 struct lyd_node *new_tree, *alg_tree;
788 va_list ap;
789
roman466719d2023-05-05 16:14:37 +0200790 ret = nc_server_config_new_ssh_transport_params_prep(ctx, endpt_name, *config, &new_tree, &alg_tree);
roman27215242023-03-10 14:55:00 +0100791 if (ret) {
792 goto cleanup;
793 }
794
795 if (!*config) {
796 *config = new_tree;
797 }
798
799 va_start(ap, alg_count);
800
roman466719d2023-05-05 16:14:37 +0200801 ret = nc_server_config_new_ssh_transport_params(ctx, NC_ALG_HOSTKEY, alg_count, ap, alg_tree);
roman27215242023-03-10 14:55:00 +0100802 if (ret) {
803 goto cleanup;
804 }
805
roman4f9bb442023-03-24 09:05:37 +0100806 /* Add all default nodes */
807 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
808 if (ret) {
809 goto cleanup;
810 }
roman27215242023-03-10 14:55:00 +0100811cleanup:
812 return ret;
813}
814
815API int
816nc_server_config_ssh_new_key_exchange_algs(const struct ly_ctx *ctx, const char *endpt_name, struct lyd_node **config,
817 int alg_count, ...)
818{
819 int ret = 0;
820 struct lyd_node *new_tree, *alg_tree;
821 va_list ap;
822
roman466719d2023-05-05 16:14:37 +0200823 ret = nc_server_config_new_ssh_transport_params_prep(ctx, endpt_name, *config, &new_tree, &alg_tree);
roman27215242023-03-10 14:55:00 +0100824 if (ret) {
825 goto cleanup;
826 }
827
828 if (!*config) {
829 *config = new_tree;
830 }
831
832 va_start(ap, alg_count);
833
roman466719d2023-05-05 16:14:37 +0200834 ret = nc_server_config_new_ssh_transport_params(ctx, NC_ALG_KEY_EXCHANGE, alg_count, ap, alg_tree);
roman27215242023-03-10 14:55:00 +0100835 if (ret) {
836 goto cleanup;
837 }
838
roman4f9bb442023-03-24 09:05:37 +0100839 /* Add all default nodes */
840 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
841 if (ret) {
842 goto cleanup;
843 }
roman27215242023-03-10 14:55:00 +0100844cleanup:
845 return ret;
846}
847
848API int
roman466719d2023-05-05 16:14:37 +0200849nc_server_config_new_ssh_encryption_algs(const struct ly_ctx *ctx, const char *endpt_name, struct lyd_node **config,
roman27215242023-03-10 14:55:00 +0100850 int alg_count, ...)
851{
852 int ret = 0;
853 struct lyd_node *new_tree, *alg_tree;
854 va_list ap;
855
roman466719d2023-05-05 16:14:37 +0200856 ret = nc_server_config_new_ssh_transport_params_prep(ctx, endpt_name, *config, &new_tree, &alg_tree);
roman27215242023-03-10 14:55:00 +0100857 if (ret) {
858 goto cleanup;
859 }
860
861 if (!*config) {
862 *config = new_tree;
863 }
864
865 va_start(ap, alg_count);
866
roman466719d2023-05-05 16:14:37 +0200867 ret = nc_server_config_new_ssh_transport_params(ctx, NC_ALG_ENCRYPTION, alg_count, ap, alg_tree);
roman27215242023-03-10 14:55:00 +0100868 if (ret) {
869 goto cleanup;
870 }
871
roman4f9bb442023-03-24 09:05:37 +0100872 /* Add all default nodes */
873 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
874 if (ret) {
875 goto cleanup;
876 }
roman27215242023-03-10 14:55:00 +0100877cleanup:
878 return ret;
879}
880
881API int
882nc_server_config_ssh_new_mac_algs(const struct ly_ctx *ctx, const char *endpt_name, struct lyd_node **config,
883 int alg_count, ...)
884{
885 int ret = 0;
886 struct lyd_node *new_tree, *alg_tree;
887 va_list ap;
888
roman466719d2023-05-05 16:14:37 +0200889 ret = nc_server_config_new_ssh_transport_params_prep(ctx, endpt_name, *config, &new_tree, &alg_tree);
roman27215242023-03-10 14:55:00 +0100890 if (ret) {
891 goto cleanup;
892 }
893
894 if (!*config) {
895 *config = new_tree;
896 }
897
898 va_start(ap, alg_count);
899
roman466719d2023-05-05 16:14:37 +0200900 ret = nc_server_config_new_ssh_transport_params(ctx, NC_ALG_MAC, alg_count, ap, alg_tree);
roman27215242023-03-10 14:55:00 +0100901 if (ret) {
902 goto cleanup;
903 }
904
roman4f9bb442023-03-24 09:05:37 +0100905 /* Add all default nodes */
906 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
907 if (ret) {
908 goto cleanup;
909 }
roman27215242023-03-10 14:55:00 +0100910cleanup:
911 return ret;
912}
roman4f9bb442023-03-24 09:05:37 +0100913
roman4f9bb442023-03-24 09:05:37 +0100914API int
roman466719d2023-05-05 16:14:37 +0200915nc_server_config_new_ssh_client_auth_pubkey(const char *pubkey_path, const struct ly_ctx *ctx, const char *endpt_name,
roman4f9bb442023-03-24 09:05:37 +0100916 const char *user_name, const char *pubkey_name, struct lyd_node **config)
917{
918 int ret = 0;
919 char *pubkey = NULL, *tree_path = NULL;
920 struct lyd_node *new_tree;
921 NC_SSH_PUBKEY_TYPE pubkey_type;
922
roman466719d2023-05-05 16:14:37 +0200923 ret = nc_server_config_new_ssh_get_pubkey(pubkey_path, &pubkey, &pubkey_type);
roman4f9bb442023-03-24 09:05:37 +0100924 if (ret) {
925 goto cleanup;
926 }
927
928 /* prepare path where leaves will get inserted */
929 asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
930 "users/user[name='%s']/public-keys/local-definition/public-key[name='%s']", endpt_name, user_name, pubkey_name);
931 if (!tree_path) {
932 ERRMEM;
933 ret = 1;
934 goto cleanup;
935 }
936
937 /* create all the nodes in the path if they weren't there */
938 ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree);
939 if (ret) {
940 goto cleanup;
941 }
942 if (!*config) {
943 *config = new_tree;
944 }
945
946 /* find the node where leaves will get inserted */
947 ret = lyd_find_path(*config, tree_path, 0, &new_tree);
948 if (ret) {
949 goto cleanup;
950 }
951
952 /* insert pubkey format */
953 if (pubkey_type == NC_SSH_PUBKEY_SSH2) {
954 ret = lyd_new_term(new_tree, NULL, "public-key-format", "ietf-crypto-types:ssh-public-key-format", 0, NULL);
955 } else {
956 ret = lyd_new_term(new_tree, NULL, "public-key-format", "ietf-crypto-types:subject-public-key-info-format", 0, NULL);
957 }
958 if (ret) {
959 goto cleanup;
960 }
961
962 /* insert pubkey b64 */
963 ret = lyd_new_term(new_tree, NULL, "public-key", pubkey, 0, NULL);
964 if (ret) {
965 goto cleanup;
966 }
967
968 /* Add all default nodes */
969 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
970 if (ret) {
971 goto cleanup;
972 }
973
974cleanup:
975 free(tree_path);
976 free(pubkey);
977 return ret;
978}
roman31820092023-03-24 15:26:21 +0100979
980API int
roman466719d2023-05-05 16:14:37 +0200981nc_server_config_new_ssh_client_auth_password(const char *password, const struct ly_ctx *ctx, const char *endpt_name,
roman31820092023-03-24 15:26:21 +0100982 const char *user_name, struct lyd_node **config)
983{
984 int ret = 0;
985 char *tree_path = NULL, *hashed_pw = NULL;
986 struct lyd_node *new_tree;
987 const char *salt = "$6$idsizuippipk$";
988
989#ifdef HAVE_CRYPT_R
990 struct crypt_data cdata;
991#endif
992
993 /* prepare path where the leaf will get inserted */
994 asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
995 "users/user[name='%s']", endpt_name, user_name);
996 if (!tree_path) {
997 ERRMEM;
998 ret = 1;
999 goto cleanup;
1000 }
1001
1002 /* create all the nodes in the path if they weren't there */
1003 ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree);
1004 if (ret) {
1005 goto cleanup;
1006 }
1007 if (!*config) {
1008 *config = new_tree;
1009 }
1010
1011 /* find the node where the leaf will get inserted */
1012 ret = lyd_find_path(*config, tree_path, 0, &new_tree);
1013 if (ret) {
1014 goto cleanup;
1015 }
1016
1017#ifdef HAVE_CRYPT_R
1018 cdata.initialized = 0;
1019 hashed_pw = crypt_r(password, salt, &data);
1020#else
1021 pthread_mutex_lock(&crypt_lock);
1022 hashed_pw = crypt(password, salt);
1023 pthread_mutex_unlock(&crypt_lock);
1024#endif
1025
1026 if (!hashed_pw) {
1027 ERR(NULL, "Hashing password failed.");
1028 ret = 1;
1029 goto cleanup;
1030 }
1031
1032 /* insert SHA-512 hashed password */
1033 ret = lyd_new_term(new_tree, NULL, "password", hashed_pw, 0, NULL);
1034 if (ret) {
1035 goto cleanup;
1036 }
1037
1038 /* Add all default nodes */
1039 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
1040 if (ret) {
1041 goto cleanup;
1042 }
1043
1044cleanup:
1045 free(tree_path);
1046 return ret;
1047}
1048
1049API int
roman466719d2023-05-05 16:14:37 +02001050nc_server_config_new_ssh_client_auth_none(const struct ly_ctx *ctx, const char *endpt_name,
roman31820092023-03-24 15:26:21 +01001051 const char *user_name, struct lyd_node **config)
1052{
1053 int ret = 0;
1054 char *tree_path = NULL;
1055 struct lyd_node *new_tree;
1056
1057 /* prepare path where the leaf will get inserted */
1058 asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
1059 "users/user[name='%s']", endpt_name, user_name);
1060 if (!tree_path) {
1061 ERRMEM;
1062 ret = 1;
1063 goto cleanup;
1064 }
1065
1066 /* create all the nodes in the path if they weren't there */
1067 ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree);
1068 if (ret) {
1069 goto cleanup;
1070 }
1071 if (!*config) {
1072 *config = new_tree;
1073 }
1074
1075 /* find the node where the leaf will get inserted */
1076 ret = lyd_find_path(*config, tree_path, 0, &new_tree);
1077 if (ret) {
1078 goto cleanup;
1079 }
1080
1081 /* insert none leaf */
1082 ret = lyd_new_term(new_tree, NULL, "none", NULL, 0, NULL);
1083 if (ret) {
1084 goto cleanup;
1085 }
1086
1087 /* Add all default nodes */
1088 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
1089 if (ret) {
1090 goto cleanup;
1091 }
1092
1093cleanup:
1094 free(tree_path);
1095 return ret;
1096}
1097
1098API int
roman466719d2023-05-05 16:14:37 +02001099nc_server_config_new_ssh_client_auth_interactive(const char *pam_config_name, const char *pam_config_dir,
roman31820092023-03-24 15:26:21 +01001100 const struct ly_ctx *ctx, const char *endpt_name,
1101 const char *user_name, struct lyd_node **config)
1102{
1103 int ret = 0;
1104 char *tree_path = NULL;
1105 struct lyd_node *new_tree;
1106
1107 /* prepare path where the leaf will get inserted */
1108 asprintf(&tree_path, "/ietf-netconf-server:netconf-server/listen/endpoint[name='%s']/ssh/ssh-server-parameters/client-authentication/"
1109 "users/user[name='%s']/libnetconf2-netconf-server:keyboard-interactive", endpt_name, user_name);
1110 if (!tree_path) {
1111 ERRMEM;
1112 ret = 1;
1113 goto cleanup;
1114 }
1115
1116 /* create all the nodes in the path if they weren't there */
1117 ret = lyd_new_path(*config, ctx, tree_path, NULL, LYD_NEW_PATH_UPDATE, &new_tree);
1118 if (ret) {
1119 goto cleanup;
1120 }
1121 if (!*config) {
1122 *config = new_tree;
1123 }
1124
1125 /* find the node where the leaf will get inserted */
1126 ret = lyd_find_path(*config, tree_path, 0, &new_tree);
1127 if (ret) {
1128 goto cleanup;
1129 }
1130
1131 /* insert file-name leaf */
1132 ret = lyd_new_term(new_tree, NULL, "pam-config-file-name", pam_config_name, 0, NULL);
1133 if (ret) {
1134 goto cleanup;
1135 }
1136
1137 if (pam_config_dir) {
1138 /* insert file-path leaf */
1139 ret = lyd_new_term(new_tree, NULL, "pam-config-file-dir", pam_config_dir, 0, NULL);
1140 if (ret) {
1141 goto cleanup;
1142 }
1143 }
1144
1145 /* Add all default nodes */
1146 ret = lyd_new_implicit_tree(*config, LYD_IMPLICIT_NO_STATE, NULL);
1147 if (ret) {
1148 goto cleanup;
1149 }
1150
1151cleanup:
1152 free(tree_path);
1153 return ret;
1154}