From ad242b88e552541e44f47e23f1c2c5c3e9dce5a0 Mon Sep 17 00:00:00 2001 From: Roytak Date: Tue, 16 Dec 2025 20:58:33 +0900 Subject: [PATCH 1/2] server config util UPDATE keep privkey format Before, whenever a privkey was loaded from a file, its format was always changed to the SubjectPrivateKeyInfo format. With this change, the privkeys retain their format from the input file in YANG. Doesn't work for X509 privkeys when using MbedTLS. --- src/server_config_util.c | 94 +++++++++++++++++------------------ src/session_mbedtls.c | 81 ++++++++++++++++++++++++------ src/session_openssl.c | 104 ++++++++++++++++++++++++++++++++++++--- src/session_wrapper.h | 11 +++-- 4 files changed, 218 insertions(+), 72 deletions(-) diff --git a/src/server_config_util.c b/src/server_config_util.c index 86d25e50..7ef6d89e 100644 --- a/src/server_config_util.c +++ b/src/server_config_util.c @@ -794,42 +794,37 @@ nc_server_config_util_get_privkey_format(const char *privkey, enum nc_privkey_fo return 0; } +/** + * @brief Get private key using the TLS backend's functions. + * + * @param[in] privkey_path Path to the private key file. + * @param[out] pkey TLS backend's underlying private key structure. + * @return 0 on success, 1 on failure. + */ static int -nc_server_config_util_get_privkey_libtls(const char *privkey_path, char **privkey, void **pkey) +nc_server_config_util_get_privkey_libtls(const char *privkey_path, void **pkey) { - void *pkey_tmp; - char *privkey_tmp; - - NC_CHECK_ARG_RET(NULL, privkey_path, privkey, pkey, 1); - - *privkey = *pkey = NULL; - - pkey_tmp = nc_tls_import_privkey_file_wrap(privkey_path); - if (!pkey_tmp) { - return 1; - } - - privkey_tmp = nc_tls_export_privkey_pem_wrap(pkey_tmp); - if (!privkey_tmp) { - nc_tls_privkey_destroy_wrap(pkey_tmp); - return 1; - } + *pkey = NULL; - *privkey = privkey_tmp; - *pkey = pkey_tmp; - return 0; + *pkey = nc_tls_import_privkey_file_wrap(privkey_path); + return *pkey ? 0 : 1; } +/** + * @brief Get private key using libssh functions. + * + * @param[in] privkey_path Path to the private key file. + * @param[out] pkey TLS backend's underlying private key structure. + * @return 0 on success, 1 on failure. + */ static int -nc_server_config_util_get_privkey_libssh(const char *privkey_path, char **privkey, void **pkey) +nc_server_config_util_get_privkey_libssh(const char *privkey_path, void **pkey) { int ret = 0; ssh_key key = NULL; - void *pkey_tmp = NULL; - char *privkey_tmp = NULL; - - NC_CHECK_ARG_RET(NULL, privkey_path, privkey, pkey, 1); + char *privkey_buf = NULL; + /* import the OpenSSH private key using libssh */ ret = ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, &key); if (ret) { ERR(NULL, "Importing privkey from file \"%s\" failed.", privkey_path); @@ -838,45 +833,43 @@ nc_server_config_util_get_privkey_libssh(const char *privkey_path, char **privke } /* export the key in PEM */ - ret = ssh_pki_export_privkey_base64(key, NULL, NULL, NULL, &privkey_tmp); + ret = ssh_pki_export_privkey_base64(key, NULL, NULL, NULL, &privkey_buf); if (ret) { ERR(NULL, "Exporting privkey to base64 failed."); goto cleanup; } - pkey_tmp = nc_tls_pem_to_privkey_wrap(privkey_tmp); - if (!pkey_tmp) { - free(privkey_tmp); + /* convert the base64 PEM to libtls private key representation */ + *pkey = nc_tls_pem_to_privkey_wrap(privkey_buf); + if (!*pkey) { ret = 1; goto cleanup; } - *privkey = privkey_tmp; - *pkey = pkey_tmp; - cleanup: + free(privkey_buf); ssh_key_free(key); return ret; } static int -nc_server_config_util_pem_strip_header_footer(const char *pem, char **privkey) +nc_server_config_util_privkey_strip_header_footer(const char *orig_privkey, char **privkey) { const char *header, *footer; - if (!strncmp(pem, NC_PKCS8_PRIVKEY_HEADER, strlen(NC_PKCS8_PRIVKEY_HEADER))) { + if (!strncmp(orig_privkey, NC_PKCS8_PRIVKEY_HEADER, strlen(NC_PKCS8_PRIVKEY_HEADER))) { /* it's PKCS8 (X.509) private key */ header = NC_PKCS8_PRIVKEY_HEADER; footer = NC_PKCS8_PRIVKEY_FOOTER; - } else if (!strncmp(pem, NC_OPENSSH_PRIVKEY_HEADER, strlen(NC_OPENSSH_PRIVKEY_HEADER))) { + } else if (!strncmp(orig_privkey, NC_OPENSSH_PRIVKEY_HEADER, strlen(NC_OPENSSH_PRIVKEY_HEADER))) { /* it's OpenSSH private key */ header = NC_OPENSSH_PRIVKEY_HEADER; footer = NC_OPENSSH_PRIVKEY_FOOTER; - } else if (!strncmp(pem, NC_PKCS1_RSA_PRIVKEY_HEADER, strlen(NC_PKCS1_RSA_PRIVKEY_HEADER))) { + } else if (!strncmp(orig_privkey, NC_PKCS1_RSA_PRIVKEY_HEADER, strlen(NC_PKCS1_RSA_PRIVKEY_HEADER))) { /* it's RSA privkey in PKCS1 format */ header = NC_PKCS1_RSA_PRIVKEY_HEADER; footer = NC_PKCS1_RSA_PRIVKEY_FOOTER; - } else if (!strncmp(pem, NC_SEC1_EC_PRIVKEY_HEADER, strlen(NC_SEC1_EC_PRIVKEY_HEADER))) { + } else if (!strncmp(orig_privkey, NC_SEC1_EC_PRIVKEY_HEADER, strlen(NC_SEC1_EC_PRIVKEY_HEADER))) { /* it's EC privkey in SEC1 format */ header = NC_SEC1_EC_PRIVKEY_HEADER; footer = NC_SEC1_EC_PRIVKEY_FOOTER; @@ -885,7 +878,7 @@ nc_server_config_util_pem_strip_header_footer(const char *pem, char **privkey) } /* make a copy without the header and footer */ - *privkey = strndup(pem + strlen(header), strlen(pem) - strlen(header) - strlen(footer)); + *privkey = strndup(orig_privkey + strlen(header), strlen(orig_privkey) - strlen(header) - strlen(footer)); NC_CHECK_ERRMEM_RET(!*privkey, 1); return 0; @@ -930,13 +923,11 @@ nc_server_config_util_get_privkey(const char *privkey_path, enum nc_privkey_form case NC_PRIVKEY_FORMAT_EC: case NC_PRIVKEY_FORMAT_X509: /* the TLS lib can do this */ - ret = nc_server_config_util_get_privkey_libtls(privkey_path, &priv, pkey); + ret = nc_server_config_util_get_privkey_libtls(privkey_path, pkey); break; case NC_PRIVKEY_FORMAT_OPENSSH: /* need the help of libssh */ - ret = nc_server_config_util_get_privkey_libssh(privkey_path, &priv, pkey); - /* if the function returned successfully, the key is no longer OpenSSH, it was converted to x509 */ - *privkey_format = NC_PRIVKEY_FORMAT_X509; + ret = nc_server_config_util_get_privkey_libssh(privkey_path, pkey); break; default: ERR(NULL, "Private key format not recognized."); @@ -947,17 +938,26 @@ nc_server_config_util_get_privkey(const char *privkey_path, enum nc_privkey_form goto cleanup; } - /* parsing may have changed its type, get it again */ + /* export the private key to its original format type, + * all of this was done to avoid having to parse the private key ourselves + * and since we have a "pkey" we can be sure, that the private key is valid */ + ret = nc_tls_privkey_export_wrap(*pkey, *privkey_format, &priv); + if (ret) { + goto cleanup; + } + + /* get the privkey format again from the exported private key, + * it should match the previous one, but in case it doesn't, + * we can still at least store the 'current' one in YANG and use it */ ret = nc_server_config_util_get_privkey_format(priv, privkey_format); if (ret) { - ERR(NULL, "Getting private key format from file \"%s\" failed.", privkey_path); + ERR(NULL, "Private key format \"%s\" not supported.", priv); goto cleanup; } /* strip private key's header and footer */ - ret = nc_server_config_util_pem_strip_header_footer(priv, privkey); + ret = nc_server_config_util_privkey_strip_header_footer(priv, privkey); if (ret) { - ERR(NULL, "Stripping header and footer from private key \"%s\" failed.", privkey_path); goto cleanup; } diff --git a/src/session_mbedtls.c b/src/session_mbedtls.c index b7375eef..8f0276b0 100644 --- a/src/session_mbedtls.c +++ b/src/session_mbedtls.c @@ -38,6 +38,8 @@ #include "session_p.h" #include "session_wrapper.h" +#include + #include #include #include @@ -1411,28 +1413,79 @@ nc_tls_import_cert_file_wrap(const char *cert_path) return c; } -char * -nc_tls_export_privkey_pem_wrap(void *pkey) +/** + * @brief Convert a PKCS#1/SEC1 private key to OpenSSH format. + * + * @param[in] pk Private key in PKCS#1/SEC1 PEM format. + * @param[out] privkey Private key in OpenSSH format. + * @return 0 on success, 1 on error. + */ +static int +nc_tls_privkey_export_openssh(const char *pk, char **privkey) { - int rc; - char *pem; + int rc = 0; + ssh_key sshkey = NULL; + + /* load the SEC1/PKCS#1 using libssh */ + if (ssh_pki_import_privkey_base64(pk, NULL, NULL, NULL, &sshkey)) { + ERR(NULL, "Importing the private key to libssh failed (%s).", ssh_get_error(NULL)); + rc = 1; + goto cleanup; + } + + /* export to OpenSSH format */ + if (ssh_pki_export_privkey_base64_format(sshkey, NULL, NULL, NULL, privkey, SSH_FILE_FORMAT_OPENSSH)) { + ERR(NULL, "Exporting the private key to OpenSSH format failed (%s).", ssh_get_error(NULL)); + rc = 1; + goto cleanup; + } + +cleanup: + ssh_key_free(sshkey); + return rc; +} + +int +nc_tls_privkey_export_wrap(void *pkey, enum nc_privkey_format format, char **privkey) +{ + int r, rc = 0; size_t size = 128; + char *pk; - pem = malloc(size); - NC_CHECK_ERRMEM_RET(!pem, NULL); + if (format == NC_PRIVKEY_FORMAT_UNKNOWN) { + ERRINT; + return 1; + } + + /* use mbedtls_pk_write_key_pem to write either PKCS#1 or SEC1 format */ + pk = malloc(size); + NC_CHECK_ERRMEM_RET(!pk, 1); - while ((rc = mbedtls_pk_write_key_pem(pkey, (unsigned char *)pem, size)) == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { + /* try to write the key, reallocating if the buffer is too small */ + while ((r = mbedtls_pk_write_key_pem(pkey, + (unsigned char *)pk, size)) == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { size <<= 1; - pem = nc_realloc(pem, size); - NC_CHECK_ERRMEM_RET(!pem, NULL); + pk = nc_realloc(pk, size); + NC_CHECK_ERRMEM_RET(!pk, 1); } - if (rc < 0) { - nc_mbedtls_strerr(NULL, rc, "Exporting private key to PEM format failed"); - free(pem); - return NULL; + if (r < 0) { + nc_mbedtls_strerr(NULL, r, "Exporting private key to PEM format failed"); + rc = 1; + goto cleanup; } - return pem; + if (format == NC_PRIVKEY_FORMAT_OPENSSH) { + /* convert it to OpenSSH format */ + rc = nc_tls_privkey_export_openssh(pk, privkey); + } else { + /* return the PEM as is (PKCS#1 or SEC1), mbedtls can not do NC_PRIVKEY_FORMAT_X509 */ + *privkey = pk; + pk = NULL; + } + +cleanup: + free(pk); + return rc; } char * diff --git a/src/session_openssl.c b/src/session_openssl.c index 472d8c3d..a2adb209 100644 --- a/src/session_openssl.c +++ b/src/session_openssl.c @@ -37,7 +37,10 @@ #include "session_p.h" #include "session_wrapper.h" +#include + #include +#include #include #include #include @@ -1198,32 +1201,119 @@ nc_tls_import_cert_file_wrap(const char *cert_path) return cert; } -char * -nc_tls_export_privkey_pem_wrap(void *pkey) +/** + * @brief Export OpenSSL's EVP_PKEY to OpenSSH private key format. + * + * @param[in] pkey OpenSSL EVP_PKEY. + * @param[out] privkey Exported private key in OpenSSH format. + * @return 0 on success, 1 on error. + */ +static int +nc_tls_privkey_export_openssh(EVP_PKEY *pkey, char **privkey) { + int rc = 0; BIO *bio = NULL; char *pem = NULL; + ssh_key sshkey = NULL; bio = BIO_new(BIO_s_mem()); if (!bio) { ERR(NULL, "Creating new bio failed (%s).", ERR_reason_error_string(ERR_get_error())); - goto cleanup; + return 1; } + /* export the privkey in PEM format first */ if (!PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL)) { - ERR(NULL, "Exporting the private key failed (%s).", ERR_reason_error_string(ERR_get_error())); + ERR(NULL, "Exporting the private key to PEM format failed (%s).", ERR_reason_error_string(ERR_get_error())); + rc = 1; goto cleanup; } pem = malloc(BIO_number_written(bio) + 1); - NC_CHECK_ERRMEM_GOTO(!pem, , cleanup); - + NC_CHECK_ERRMEM_GOTO(!pem, rc = 1, cleanup); BIO_read(bio, pem, BIO_number_written(bio)); pem[BIO_number_written(bio)] = '\0'; + /* load the PEM using libssh */ + if (ssh_pki_import_privkey_base64(pem, NULL, NULL, NULL, &sshkey)) { + ERR(NULL, "Importing the private key to libssh failed (%s).", ssh_get_error(NULL)); + rc = 1; + goto cleanup; + } + + /* export to OpenSSH format */ + if (ssh_pki_export_privkey_base64_format(sshkey, NULL, NULL, NULL, privkey, SSH_FILE_FORMAT_OPENSSH)) { + ERR(NULL, "Exporting the private key to OpenSSH format failed (%s).", ssh_get_error(NULL)); + rc = 1; + goto cleanup; + } + cleanup: BIO_free(bio); - return pem; + free(pem); + ssh_key_free(sshkey); + return rc; +} + +int +nc_tls_privkey_export_wrap(void *pkey, enum nc_privkey_format format, char **privkey) +{ + int rc = 0; + BIO *bio = NULL; + OSSL_ENCODER_CTX *ctx = NULL; + const char *output_structure; + + bio = BIO_new(BIO_s_mem()); + if (!bio) { + ERR(NULL, "Creating new bio failed (%s).", ERR_reason_error_string(ERR_get_error())); + return 1; + } + + switch (format) { + case NC_PRIVKEY_FORMAT_RSA: + output_structure = "type-specific"; + break; + case NC_PRIVKEY_FORMAT_EC: + output_structure = "type-specific"; + break; + case NC_PRIVKEY_FORMAT_X509: + output_structure = "PrivateKeyInfo"; + break; + case NC_PRIVKEY_FORMAT_OPENSSH: + /* we need to use libssh for this */ + rc = nc_tls_privkey_export_openssh(pkey, privkey); + goto cleanup; + default: + ERRINT; + rc = 1; + goto cleanup; + } + + ctx = OSSL_ENCODER_CTX_new_for_pkey(pkey, + OSSL_KEYMGMT_SELECT_PRIVATE_KEY | OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + "PEM", output_structure, NULL); + + if (!ctx) { + ERR(NULL, "Creating encoder context failed (%s).", ERR_reason_error_string(ERR_get_error())); + rc = 1; + goto cleanup; + } + + if (!OSSL_ENCODER_to_bio(ctx, bio)) { + ERR(NULL, "Exporting the private key failed (%s).", ERR_reason_error_string(ERR_get_error())); + rc = 1; + goto cleanup; + } + + *privkey = malloc(BIO_number_written(bio) + 1); + NC_CHECK_ERRMEM_GOTO(!*privkey, rc = 1, cleanup); + BIO_read(bio, *privkey, BIO_number_written(bio)); + (*privkey)[BIO_number_written(bio)] = '\0'; + +cleanup: + OSSL_ENCODER_CTX_free(ctx); + BIO_free(bio); + return rc; } char * diff --git a/src/session_wrapper.h b/src/session_wrapper.h index 229bf5a2..fc955420 100644 --- a/src/session_wrapper.h +++ b/src/session_wrapper.h @@ -76,8 +76,9 @@ struct nc_tls_verify_cb_data { void *chain; /**< Certificate chain used to verify the client cert. */ }; -/* forward declaration */ +/* forward declarations */ enum nc_tls_version; +enum nc_privkey_format; /** * @brief Initializes the TLS backend. @@ -592,12 +593,14 @@ void * nc_tls_import_privkey_file_wrap(const char *privkey_path); void * nc_tls_import_cert_file_wrap(const char *cert_path); /** - * @brief Export a private key to a PEM string. + * @brief Export a private key to a specific format. * * @param[in] pkey Private key. - * @return PEM string on success, NULL on fail. + * @param[in] format Desired private key format. + * @param[out] privkey Exported private key string (PEM). + * @return 0 on success, non-zero on fail. */ -char * nc_tls_export_privkey_pem_wrap(void *pkey); +int nc_tls_privkey_export_wrap(void *pkey, enum nc_privkey_format format, char **privkey); /** * @brief Export a certificate to a PEM string. From 80ae943bbfb902b35486e0b4481b8006cabd4a5d Mon Sep 17 00:00:00 2001 From: roman Date: Mon, 5 Jan 2026 11:31:37 +0100 Subject: [PATCH 2/2] session mbedtls UPDATE add libssh version check --- src/session_mbedtls.c | 16 +++++++++++++++- src/session_openssl.c | 22 +++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/session_mbedtls.c b/src/session_mbedtls.c index 8f0276b0..493bd1f6 100644 --- a/src/session_mbedtls.c +++ b/src/session_mbedtls.c @@ -1426,6 +1426,11 @@ nc_tls_privkey_export_openssh(const char *pk, char **privkey) int rc = 0; ssh_key sshkey = NULL; + *privkey = NULL; + + /* older versions of libssh (< v0.11.0) do not support exporting to OpenSSH format, + * signal this to the caller by returning success with NULL privkey */ +#if (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 11) /* load the SEC1/PKCS#1 using libssh */ if (ssh_pki_import_privkey_base64(pk, NULL, NULL, NULL, &sshkey)) { ERR(NULL, "Importing the private key to libssh failed (%s).", ssh_get_error(NULL)); @@ -1439,6 +1444,7 @@ nc_tls_privkey_export_openssh(const char *pk, char **privkey) rc = 1; goto cleanup; } +#endif // (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 11) cleanup: ssh_key_free(sshkey); @@ -1475,8 +1481,16 @@ nc_tls_privkey_export_wrap(void *pkey, enum nc_privkey_format format, char **pri } if (format == NC_PRIVKEY_FORMAT_OPENSSH) { - /* convert it to OpenSSH format */ rc = nc_tls_privkey_export_openssh(pk, privkey); + if (rc) { + goto cleanup; + } + + if (!*privkey) { + /* privkey not converted, just use the PEM as is (PKCS#1 or SEC1) */ + *privkey = pk; + pk = NULL; + } } else { /* return the PEM as is (PKCS#1 or SEC1), mbedtls can not do NC_PRIVKEY_FORMAT_X509 */ *privkey = pk; diff --git a/src/session_openssl.c b/src/session_openssl.c index a2adb209..9be30625 100644 --- a/src/session_openssl.c +++ b/src/session_openssl.c @@ -1216,6 +1216,11 @@ nc_tls_privkey_export_openssh(EVP_PKEY *pkey, char **privkey) char *pem = NULL; ssh_key sshkey = NULL; + *privkey = NULL; + + /* older versions of libssh (< v0.11.0) do not support exporting to OpenSSH format, + * signal this to the caller by returning success with NULL privkey */ +#if (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 11) bio = BIO_new(BIO_s_mem()); if (!bio) { ERR(NULL, "Creating new bio failed (%s).", ERR_reason_error_string(ERR_get_error())); @@ -1247,6 +1252,7 @@ nc_tls_privkey_export_openssh(EVP_PKEY *pkey, char **privkey) rc = 1; goto cleanup; } +#endif cleanup: BIO_free(bio); @@ -1261,7 +1267,7 @@ nc_tls_privkey_export_wrap(void *pkey, enum nc_privkey_format format, char **pri int rc = 0; BIO *bio = NULL; OSSL_ENCODER_CTX *ctx = NULL; - const char *output_structure; + const char *output_structure = NULL; bio = BIO_new(BIO_s_mem()); if (!bio) { @@ -1280,12 +1286,22 @@ nc_tls_privkey_export_wrap(void *pkey, enum nc_privkey_format format, char **pri output_structure = "PrivateKeyInfo"; break; case NC_PRIVKEY_FORMAT_OPENSSH: - /* we need to use libssh for this */ rc = nc_tls_privkey_export_openssh(pkey, privkey); - goto cleanup; + if (rc) { + goto cleanup; + } + + if (!*privkey) { + /* privkey not converted, just convert it to PrivateKeyInfo format */ + output_structure = "PrivateKeyInfo"; + } + break; default: ERRINT; rc = 1; + break; + } + if (!output_structure) { goto cleanup; }