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..493bd1f6 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,93 @@ 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; + + *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)); + 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; + } +#endif // (LIBSSH_VERSION_MAJOR > 0) || (LIBSSH_VERSION_MAJOR == 0 && LIBSSH_VERSION_MINOR >= 11) + +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) { + 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; + pk = NULL; + } + +cleanup: + free(pk); + return rc; } char * diff --git a/src/session_openssl.c b/src/session_openssl.c index 472d8c3d..9be30625 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,135 @@ 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; + + *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())); - 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; + } +#endif + 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 = NULL; + + 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: + rc = nc_tls_privkey_export_openssh(pkey, privkey); + 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; + } + + 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.