From ae80f14321d2571e7eaf3cb90c089ca2dae50258 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 17 Aug 2023 15:36:54 +0200 Subject: [PATCH] config UPDATE use correct public key types --- src/config_new.c | 804 +++++++++++++++++++++++++-------------- src/config_new.h | 6 +- src/config_new_ssh.c | 24 +- src/config_new_tls.c | 12 +- src/server_config.c | 83 +++- src/server_config.h | 5 +- src/server_config_ks.c | 2 +- src/server_config_ts.c | 2 +- src/session.c | 63 +++ src/session_p.h | 13 +- src/session_server_ssh.c | 64 +--- tests/test_ks_ts.c | 4 +- 12 files changed, 688 insertions(+), 394 deletions(-) diff --git a/src/config_new.c b/src/config_new.c index 4cefadf6..fa8dca20 100644 --- a/src/config_new.c +++ b/src/config_new.c @@ -210,7 +210,7 @@ nc_config_new_privkey_format_to_identityref(NC_PRIVKEY_FORMAT format) case NC_PRIVKEY_FORMAT_EC: return "ietf-crypto-types:ec-private-key-format"; case NC_PRIVKEY_FORMAT_X509: - return "libnetconf2-netconf-server:subject-private-key-info-format"; + return "libnetconf2-netconf-server:private-key-info-format"; case NC_PRIVKEY_FORMAT_OPENSSH: return "libnetconf2-netconf-server:openssh-private-key-format"; default: @@ -219,6 +219,306 @@ nc_config_new_privkey_format_to_identityref(NC_PRIVKEY_FORMAT format) } } +static int +nc_server_config_new_pubkey_bin_to_b64(const unsigned char *pub_bin, int bin_len, char **pubkey) +{ + int ret = 0, b64_len; + char *pub_b64 = NULL; + + /* get b64 buffer len, for ever 3 bytes of bin 4 bytes of b64 + NULL terminator */ + if (bin_len % 3 == 0) { + pub_b64 = malloc((bin_len / 3) * 4 + 1); + } else { + /* bin len not divisible by 3, need to add 4 bytes for some padding so that the len is divisible by 4 */ + pub_b64 = malloc((bin_len / 3) * 4 + 4 + 1); + } + if (!pub_b64) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* bin to b64 */ + b64_len = EVP_EncodeBlock((unsigned char *)pub_b64, pub_bin, bin_len); + *pubkey = strndup(pub_b64, b64_len); + if (!*pubkey) { + ERRMEM; + ret = 1; + goto cleanup; + } + +cleanup: + free(pub_b64); + return ret; +} + +static int +nc_server_config_new_bn_to_bin(const BIGNUM *bn, unsigned char **bin, int *bin_len) +{ + int ret = 0; + unsigned char *bin_tmp = NULL; + + NC_CHECK_ARG_RET(NULL, bn, bin, bin_len, 1); + + *bin = NULL; + + /* prepare buffer for converting BN to binary */ + bin_tmp = calloc(BN_num_bytes(bn), sizeof *bin_tmp); + if (!bin_tmp) { + ERRMEM; + return 1; + } + + /* convert to binary */ + *bin_len = BN_bn2bin(bn, bin_tmp); + + /* if the highest bit in the MSB is set a byte with the value 0 has to be prepended */ + if (bin_tmp[0] & 0x80) { + *bin = malloc(*bin_len + 1); + if (!*bin) { + ERRMEM; + ret = 1; + goto cleanup; + } + + (*bin)[0] = 0; + memcpy(*bin + 1, bin_tmp, *bin_len); + (*bin_len)++; + } else { + *bin = malloc(*bin_len); + if (!*bin) { + ERRMEM; + ret = 1; + goto cleanup; + } + + memcpy(*bin, bin_tmp, *bin_len); + } + +cleanup: + free(bin_tmp); + return ret; +} + +/* ssh pubkey defined in RFC 4253 section 6.6 */ +static int +nc_server_config_new_evp_pkey_to_ssh_pubkey(EVP_PKEY *pkey, char **pubkey) +{ + int ret = 0, e_len, n_len, p_len, bin_len; + BIGNUM *e = NULL, *n = NULL, *p = NULL; + unsigned char *e_bin = NULL, *n_bin = NULL, *p_bin = NULL, *bin = NULL, *bin_tmp; + const char *algorithm_name, *curve_name; + char *ec_group = NULL; + uint32_t alg_name_len, curve_name_len, alg_name_len_be, curve_name_len_be, p_len_be, e_len_be, n_len_be; + size_t ec_group_len; + + if (EVP_PKEY_is_a(pkey, "RSA")) { + /* RSA key */ + algorithm_name = "ssh-rsa"; + + /* get the public key params */ + if (!EVP_PKEY_get_bn_param(pkey, "e", &e) || !EVP_PKEY_get_bn_param(pkey, "n", &n)) { + ERR(NULL, "Getting public key parameters from RSA private key failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = 1; + goto cleanup; + } + + /* BIGNUM to bin */ + if (nc_server_config_new_bn_to_bin(e, &e_bin, &e_len) || nc_server_config_new_bn_to_bin(n, &n_bin, &n_len)) { + ret = 1; + goto cleanup; + } + + alg_name_len = strlen(algorithm_name); + /* buffer for public key in binary, which looks like this: + * alg_name len (4 bytes), alg_name, PK exponent len (4 bytes), PK exponent, modulus len (4 bytes), modulus + */ + bin_len = 4 + alg_name_len + 4 + e_len + 4 + n_len; + bin = malloc(bin_len); + if (!bin) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* to network byte order (big endian) */ + alg_name_len_be = htonl(alg_name_len); + e_len_be = htonl(e_len); + n_len_be = htonl(n_len); + + /* create the public key in binary */ + bin_tmp = bin; + memcpy(bin_tmp, &alg_name_len_be, 4); + bin_tmp += 4; + memcpy(bin_tmp, algorithm_name, alg_name_len); + bin_tmp += alg_name_len; + memcpy(bin_tmp, &e_len_be, 4); + bin_tmp += 4; + memcpy(bin_tmp, e_bin, e_len); + bin_tmp += e_len; + memcpy(bin_tmp, &n_len_be, 4); + bin_tmp += 4; + memcpy(bin_tmp, n_bin, n_len); + } else if (EVP_PKEY_is_a(pkey, "EC")) { + /* EC Private key, get it's group first */ + /* get group len */ + ret = EVP_PKEY_get_utf8_string_param(pkey, "group", NULL, 0, &ec_group_len); + if (!ret) { + ret = 1; + goto cleanup; + } + /* alloc mem for group + 1 for \0 */ + ec_group = malloc(ec_group_len + 1); + if (!ec_group) { + ERRMEM; + ret = 1; + goto cleanup; + } + /* get the group */ + ret = EVP_PKEY_get_utf8_string_param(pkey, "group", ec_group, ec_group_len + 1, NULL); + if (!ret) { + ERR(NULL, "Getting public key parameter from EC private key failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = 1; + goto cleanup; + } + + /* get alg and curve names */ + if (!strcmp(ec_group, "P-256") || !strcmp(ec_group, "secp256r1") || !strcmp(ec_group, "prime256v1")) { + algorithm_name = "ecdsa-sha2-nistp256"; + curve_name = "nistp256"; + } else if (!strcmp(ec_group, "P-384") || !strcmp(ec_group, "secp384r1")) { + algorithm_name = "ecdsa-sha2-nistp384"; + curve_name = "nistp384"; + } else if (!strcmp(ec_group, "P-521") || !strcmp(ec_group, "secp521r1")) { + algorithm_name = "ecdsa-sha2-nistp521"; + curve_name = "nistp521"; + } else { + ERR(NULL, "EC group \"%s\" not supported.", ec_group); + ret = 1; + goto cleanup; + } + + /* get the public key - p, which is a point on the elliptic curve */ + ret = EVP_PKEY_get_bn_param(pkey, "p", &p); + if (!ret) { + ERR(NULL, "Getting public key point from the EC private key failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = 1; + goto cleanup; + } + + /* prepare buffer for converting p to binary */ + p_bin = malloc(BN_num_bytes(p)); + if (!p_bin) { + ERRMEM; + ret = 1; + goto cleanup; + } + /* convert to binary */ + p_len = BN_bn2bin(p, p_bin); + + alg_name_len = strlen(algorithm_name); + curve_name_len = strlen(curve_name); + /* buffer for public key in binary, which looks like so: + * alg_name len (4 bytes), alg_name, curve_name len (4 bytes), curve_name, PK point p len (4 bytes), PK point p + */ + bin_len = 4 + alg_name_len + 4 + curve_name_len + 4 + p_len; + bin = malloc(bin_len); + if (!bin) { + ERRMEM; + ret = 1; + goto cleanup; + } + + /* to network byte order (big endian) */ + alg_name_len_be = htonl(alg_name_len); + curve_name_len_be = htonl(curve_name_len); + p_len_be = htonl(p_len); + + /* create the public key in binary */ + bin_tmp = bin; + memcpy(bin_tmp, &alg_name_len_be, 4); + bin_tmp += 4; + memcpy(bin_tmp, algorithm_name, alg_name_len); + bin_tmp += alg_name_len; + memcpy(bin_tmp, &curve_name_len_be, 4); + bin_tmp += 4; + memcpy(bin_tmp, curve_name, curve_name_len); + bin_tmp += curve_name_len; + memcpy(bin_tmp, &p_len_be, 4); + bin_tmp += 4; + memcpy(bin_tmp, p_bin, p_len); + } else if (EVP_PKEY_is_a(pkey, "ED25519")) { + ERR(NULL, "Generating PEM ED25519 key from OpenSSH is not supported by libssh yet."); + ret = 1; + goto cleanup; + } else { + ERR(NULL, "Unable to generate public key from private key (Private key type not supported)."); + ret = 1; + goto cleanup; + } + + ret = nc_server_config_new_pubkey_bin_to_b64(bin, bin_len, pubkey); + if (ret) { + ERR(NULL, "Converting public key from binary to base64 failed."); + goto cleanup; + } + +cleanup: + free(bin); + free(e_bin); + free(n_bin); + free(ec_group); + free(p_bin); + BN_free(e); + BN_free(n); + BN_free(p); + return ret; +} + +/* spki = subject public key info */ +static int +nc_server_config_new_evp_pkey_to_spki_pubkey(EVP_PKEY *pkey, char **pubkey) +{ + int ret = 0, len; + BIO *bio = NULL; + char *pub_b64 = NULL; + + bio = BIO_new(BIO_s_mem()); + if (!bio) { + ERR(NULL, "Creating new BIO failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = 1; + goto cleanup; + } + + /* write the evp_pkey contents to bio */ + if (!PEM_write_bio_PUBKEY(bio, pkey)) { + ERR(NULL, "Writing public key to BIO failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = 1; + goto cleanup; + } + + /* read the pubkey from bio */ + len = BIO_get_mem_data(bio, &pub_b64); + if (len <= 0) { + ERR(NULL, "Reading base64 private key from BIO failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = 1; + goto cleanup; + } + + /* copy the public key without the header and footer */ + *pubkey = strndup(pub_b64 + strlen(NC_SUBJECT_PUBKEY_INFO_HEADER), + len - strlen(NC_SUBJECT_PUBKEY_INFO_HEADER) - strlen(NC_SUBJECT_PUBKEY_INFO_FOOTER)); + if (!*pubkey) { + ERRMEM; + ret = 1; + goto cleanup; + } + +cleanup: + BIO_free(bio); + return ret; +} + int nc_server_config_new_read_certificate(const char *cert_path, char **cert) { @@ -305,7 +605,7 @@ nc_server_config_new_read_certificate(const char *cert_path, char **cert) } static int -nc_server_config_new_read_ssh2_pubkey(FILE *f, char **pubkey) +nc_server_config_new_read_pubkey_ssh2(FILE *f, char **pubkey) { char *buffer = NULL; size_t size = 0, pubkey_len = 0; @@ -355,74 +655,20 @@ static int nc_server_config_new_read_pubkey_openssl(FILE *f, char **pubkey) { int ret = 0; - EVP_PKEY *pkey = NULL; - BIO *bio = NULL; - char *key = NULL; - int pub_len; - - /* read the pubkey from file */ - pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL); - if (!pkey) { - ret = -1; - goto cleanup; - } - - bio = BIO_new(BIO_s_mem()); - if (!bio) { - ret = -1; - goto cleanup; - } - - /* write the pubkey into bio */ - ret = PEM_write_bio_PUBKEY(bio, pkey); - if (!ret) { - ret = -1; - goto cleanup; - } - - pub_len = BIO_pending(bio); - if (pub_len <= 0) { - ret = -1; - goto cleanup; - } - - /* get pubkey's length */ - key = malloc(pub_len + 1); - if (!key) { - ERRMEM; - ret = 1; - goto cleanup; - } - - /* read the public key from bio */ - ret = BIO_read(bio, key, pub_len); - if (ret <= 0) { - ret = -1; - goto cleanup; - } - key[pub_len] = '\0'; - - /* strip the pubkey of the header and footer */ - *pubkey = strdup(key + strlen(NC_SUBJECT_PUBKEY_INFO_HEADER)); - if (!*pubkey) { - ERRMEM; - ret = 1; - goto cleanup; - } + EVP_PKEY *pub_pkey = NULL; - (*pubkey)[strlen(*pubkey) - strlen(NC_SUBJECT_PUBKEY_INFO_FOOTER)] = '\0'; + NC_CHECK_ARG_RET(NULL, f, pubkey, 1); - ret = 0; -cleanup: - if (ret == -1) { - ERR(NULL, "Error getting public key from file (OpenSSL Error): \"%s\".", ERR_reason_error_string(ERR_get_error())); - ret = 1; + /* read the pubkey from file */ + pub_pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL); + if (!pub_pkey) { + ERR(NULL, "Reading public key from file failed (%s).", ERR_reason_error_string(ERR_get_error())); + return 1; } - BIO_free(bio); - EVP_PKEY_free(pkey); - free(key); + ret = nc_server_config_new_evp_pkey_to_ssh_pubkey(pub_pkey, pubkey); + EVP_PKEY_free(pub_pkey); return ret; } @@ -432,6 +678,8 @@ nc_server_config_new_read_pubkey_libssh(const char *pubkey_path, char **pubkey) int ret = 0; ssh_key pub_sshkey = NULL; + NC_CHECK_ARG_RET(NULL, pubkey_path, pubkey, 1); + ret = ssh_pki_import_pubkey_file(pubkey_path, &pub_sshkey); if (ret) { ERR(NULL, "Importing public key from file \"%s\" failed.", pubkey_path); @@ -440,22 +688,24 @@ nc_server_config_new_read_pubkey_libssh(const char *pubkey_path, char **pubkey) ret = ssh_pki_export_pubkey_base64(pub_sshkey, pubkey); if (ret) { - ERR(NULL, "Exporting public key to base64 failed."); + ERR(NULL, "Importing pubkey failed."); + goto cleanup; } +cleanup: ssh_key_free(pub_sshkey); - return ret; + return 0; } int -nc_server_config_new_get_pubkey(const char *pubkey_path, char **pubkey, NC_PUBKEY_FORMAT *pubkey_type) +nc_server_config_new_get_ssh_pubkey_file(const char *pubkey_path, char **pubkey) { int ret = 0; FILE *f = NULL; char *header = NULL; size_t len = 0; - NC_CHECK_ARG_RET(NULL, pubkey, pubkey_type, 1); + NC_CHECK_ARG_RET(NULL, pubkey_path, pubkey, 1); *pubkey = NULL; @@ -466,6 +716,7 @@ nc_server_config_new_get_pubkey(const char *pubkey_path, char **pubkey, NC_PUBKE goto cleanup; } + /* read the header */ if (getline(&header, &len, f) < 0) { ERR(NULL, "Error reading header from file \"%s\".", pubkey_path); ret = 1; @@ -476,17 +727,13 @@ nc_server_config_new_get_pubkey(const char *pubkey_path, char **pubkey, NC_PUBKE if (!strncmp(header, NC_SUBJECT_PUBKEY_INFO_HEADER, strlen(NC_SUBJECT_PUBKEY_INFO_HEADER))) { /* it's subject public key info public key */ ret = nc_server_config_new_read_pubkey_openssl(f, pubkey); - *pubkey_type = NC_PUBKEY_FORMAT_X509; } else if (!strncmp(header, NC_SSH2_PUBKEY_HEADER, strlen(NC_SSH2_PUBKEY_HEADER))) { /* it's ssh2 public key */ - ret = nc_server_config_new_read_ssh2_pubkey(f, pubkey); - *pubkey_type = NC_PUBKEY_FORMAT_SSH2; + ret = nc_server_config_new_read_pubkey_ssh2(f, pubkey); } else { /* it's probably OpenSSH public key */ ret = nc_server_config_new_read_pubkey_libssh(pubkey_path, pubkey); - *pubkey_type = NC_PUBKEY_FORMAT_SSH2; } - if (ret) { ERR(NULL, "Error getting public key from file \"%s\".", pubkey_path); goto cleanup; @@ -498,301 +745,289 @@ nc_server_config_new_get_pubkey(const char *pubkey_path, char **pubkey, NC_PUBKE } free(header); + return ret; +} + +int +nc_server_config_new_get_spki_pubkey_file(const char *pubkey_path, char **pubkey) +{ + int ret = 0; + FILE *f = NULL; + EVP_PKEY *pub_pkey = NULL; + + NC_CHECK_ARG_RET(NULL, pubkey_path, pubkey, 1); + *pubkey = NULL; + + f = fopen(pubkey_path, "r"); + if (!f) { + ERR(NULL, "Unable to open file \"%s\".", pubkey_path); + ret = 1; + goto cleanup; + } + + /* read the pubkey from file */ + pub_pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL); + if (!pub_pkey) { + ERR(NULL, "Reading public key from file failed (%s).", ERR_reason_error_string(ERR_get_error())); + return 1; + } + + ret = nc_server_config_new_evp_pkey_to_spki_pubkey(pub_pkey, pubkey); + if (ret) { + goto cleanup; + } + +cleanup: + if (f) { + fclose(f); + } + + EVP_PKEY_free(pub_pkey); return ret; } static int -nc_server_config_new_get_privkey_openssl(FILE *f, char **privkey, EVP_PKEY **priv_pkey) +nc_server_config_new_privkey_header_to_format(FILE *f_privkey, const char *privkey_path, NC_PRIVKEY_FORMAT *privkey_format) { - int ret = 0, priv_len; - BIO *bio = NULL; + char *privkey_header = NULL; + size_t len = 0; - NC_CHECK_ARG_RET(NULL, privkey, priv_pkey, 1); + /* read header */ + if (getline(&privkey_header, &len, f_privkey) < 0) { + ERR(NULL, "Error reading header from file \"%s\".", privkey_path); + return 1; + } - /* read private key from file */ - *priv_pkey = PEM_read_PrivateKey(f, NULL, NULL, NULL); - if (!*priv_pkey) { - ret = -1; - goto cleanup; + if (!strncmp(privkey_header, NC_PKCS8_PRIVKEY_HEADER, strlen(NC_PKCS8_PRIVKEY_HEADER))) { + /* it's PKCS8 (X.509) private key */ + *privkey_format = NC_PRIVKEY_FORMAT_X509; + } else if (!strncmp(privkey_header, NC_OPENSSH_PRIVKEY_HEADER, strlen(NC_OPENSSH_PRIVKEY_HEADER))) { + /* it's OpenSSH private key */ + *privkey_format = NC_PRIVKEY_FORMAT_OPENSSH; + } else if (!strncmp(privkey_header, NC_PKCS1_RSA_PRIVKEY_HEADER, strlen(NC_PKCS1_RSA_PRIVKEY_HEADER))) { + /* it's RSA privkey in PKCS1 format */ + *privkey_format = NC_PRIVKEY_FORMAT_RSA; + } else if (!strncmp(privkey_header, NC_SEC1_EC_PRIVKEY_HEADER, strlen(NC_SEC1_EC_PRIVKEY_HEADER))) { + /* it's EC privkey in SEC1 format */ + *privkey_format = NC_PRIVKEY_FORMAT_EC; + } else { + ERR(NULL, "Private key format (%s) not supported.", privkey_header); + free(privkey_header); + return 1; } + /* reset the reading head */ + rewind(f_privkey); + free(privkey_header); + return 0; +} + +static int +nc_server_config_new_get_privkey_openssl(const char *privkey_path, FILE *f_privkey, char **privkey, EVP_PKEY **pkey) +{ + int ret = 0, len; + BIO *bio = NULL; + char *priv_b64 = NULL; + bio = BIO_new(BIO_s_mem()); if (!bio) { - ret = -1; + ERR(NULL, "Creating new BIO failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = 1; goto cleanup; } - /* write the private key in to bio */ - ret = PEM_write_bio_PrivateKey(bio, *priv_pkey, NULL, NULL, 0, NULL, NULL); - if (!ret) { - ret = -1; + /* read the privkey file, create EVP_PKEY */ + *pkey = PEM_read_PrivateKey(f_privkey, NULL, NULL, NULL); + if (!*pkey) { + ERR(NULL, "Getting private key from file \"%s\" failed (%s).", privkey_path, ERR_reason_error_string(ERR_get_error())); + ret = 1; goto cleanup; } - priv_len = BIO_pending(bio); - if (priv_len <= 0) { - ret = -1; + /* write the privkey to bio */ + if (!PEM_write_bio_PrivateKey(bio, *pkey, NULL, NULL, 0, NULL, NULL)) { + ERR(NULL, "Writing private key to BIO failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = 1; goto cleanup; } - /* get private key's length */ - *privkey = malloc(priv_len + 1); - if (!*privkey) { - ERRMEM; + /* read the privkey from bio */ + len = BIO_get_mem_data(bio, &priv_b64); + if (len <= 0) { + ERR(NULL, "Reading base64 private key from BIO failed (%s).", ERR_reason_error_string(ERR_get_error())); ret = 1; goto cleanup; } - /* read the private key from bio */ - ret = BIO_read(bio, *privkey, priv_len); - if (ret <= 0) { - ret = -1; - goto cleanup; - } - (*privkey)[priv_len] = '\0'; + *privkey = strndup(priv_b64, len); - ret = 0; cleanup: - if (ret < 0) { - ERR(NULL, "Getting private key from file failed (%s).", ERR_reason_error_string(ERR_get_error())); - } BIO_free(bio); return ret; } static int -nc_server_config_new_privkey_to_pubkey_openssl(EVP_PKEY *priv_pkey, char **pubkey) +nc_server_config_new_get_privkey_libssh(const char *privkey_path, char **privkey, EVP_PKEY **pkey) { - int ret = 0, pub_len; + int ret = 0; BIO *bio = NULL; + char *priv_b64 = NULL; + ssh_key key = NULL; - bio = BIO_new(BIO_s_mem()); - if (!bio) { - ret = -1; + ret = ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, &key); + if (ret) { + ERR(NULL, "Importing privkey from file \"%s\" failed.", privkey_path); goto cleanup; } - /* write the pubkey into bio */ - ret = PEM_write_bio_PUBKEY(bio, priv_pkey); - if (!ret) { - ret = -1; + /* exports the key in a format in which OpenSSL can read it */ + ret = ssh_pki_export_privkey_base64(key, NULL, NULL, NULL, &priv_b64); + if (ret) { + ERR(NULL, "Exporting privkey to base64 failed."); goto cleanup; } - /* get the length of the pubkey */ - pub_len = BIO_pending(bio); - if (pub_len <= 0) { - ret = -1; + bio = BIO_new(BIO_s_mem()); + if (!bio) { + ERR(NULL, "Creating new BIO failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = 1; goto cleanup; } - *pubkey = malloc(pub_len + 1); - if (!*pubkey) { - ERRMEM; + ret = BIO_write(bio, priv_b64, strlen(priv_b64)); + if (ret <= 0) { + ERR(NULL, "Writing private key to BIO failed (%s).", ERR_reason_error_string(ERR_get_error())); ret = 1; goto cleanup; } - /* read the pubkey from the bio */ - ret = BIO_read(bio, *pubkey, pub_len); - if (ret <= 0) { - ret = -1; + /* create EVP_PKEY from the b64 */ + *pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + if (!*pkey) { + ERR(NULL, "Getting private key from file \"%s\" failed (%s).", privkey_path, ERR_reason_error_string(ERR_get_error())); + ret = 1; goto cleanup; } - (*pubkey)[pub_len] = '\0'; + *privkey = strndup(priv_b64, ret); + + /* ok */ ret = 0; cleanup: - if (ret < 0) { - ERR(NULL, "Converting private to public key failed (%s).", ERR_reason_error_string(ERR_get_error())); - } + free(priv_b64); BIO_free(bio); + ssh_key_free(key); return ret; } static int -nc_server_config_new_privkey_to_pubkey_libssh(const ssh_key priv_sshkey, char **pubkey) +nc_server_config_new_get_privkey(const char *privkey_path, NC_PRIVKEY_FORMAT *privkey_format, char **privkey, EVP_PKEY **pkey) { - int ret; - ssh_key pub_sshkey = NULL; + int ret = 0; + FILE *f_privkey = NULL; + char *priv = NULL; - ret = ssh_pki_export_privkey_to_pubkey(priv_sshkey, &pub_sshkey); - if (ret) { - ERR(NULL, "Exporting privkey to pubkey failed."); - return ret; + f_privkey = fopen(privkey_path, "r"); + if (!f_privkey) { + ERR(NULL, "Unable to open file \"%s\".", privkey_path); + ret = 1; + goto cleanup; } - ret = ssh_pki_export_pubkey_base64(pub_sshkey, pubkey); + /* read the first line from the privkey to determine it's type */ + ret = nc_server_config_new_privkey_header_to_format(f_privkey, privkey_path, privkey_format); if (ret) { - ERR(NULL, "Exporting pubkey to base64 failed."); + ERR(NULL, "Getting private key format from file \"%s\" failed.", privkey_path); + goto cleanup; } - ssh_key_free(pub_sshkey); - return ret; -} - -static int -nc_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) -{ - switch (privkey_type) { + switch (*privkey_format) { + /* fall-through */ case NC_PRIVKEY_FORMAT_RSA: case NC_PRIVKEY_FORMAT_EC: - case NC_PRIVKEY_FORMAT_OPENSSH: - *pubkey_type = NC_PUBKEY_FORMAT_SSH2; - return nc_server_config_new_privkey_to_pubkey_libssh(priv_sshkey, pubkey); case NC_PRIVKEY_FORMAT_X509: - *pubkey_type = NC_PUBKEY_FORMAT_X509; - return nc_server_config_new_privkey_to_pubkey_openssl(priv_pkey, pubkey); + /* OpenSSL solely can do this */ + ret = nc_server_config_new_get_privkey_openssl(privkey_path, f_privkey, &priv, pkey); + break; + case NC_PRIVKEY_FORMAT_OPENSSH: + /* need the help of libssh */ + ret = nc_server_config_new_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; + break; default: + ERR(NULL, "Private key format not recognized."); + ret = 1; break; } - - return 1; -} - -static int -nc_server_config_new_get_privkey_libssh(const char *privkey_path, char **privkey, ssh_key *priv_sshkey) -{ - int ret; - - *priv_sshkey = NULL; - - ret = ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, priv_sshkey); if (ret) { - ERR(NULL, "Importing privkey from file \"%s\" failed.", privkey_path); - return ret; + goto cleanup; } - ret = ssh_pki_export_privkey_base64(*priv_sshkey, NULL, NULL, NULL, privkey); - if (ret) { - ERR(NULL, "Exporting privkey from file \"%s\" to base64 failed.", privkey_path); + /* strip private key's header and footer */ + *privkey = strdup(priv + strlen(NC_PKCS8_PRIVKEY_HEADER)); + if (!*privkey) { + ERRMEM; + ret = 1; + goto cleanup; + } + (*privkey)[strlen(*privkey) - strlen(NC_PKCS8_PRIVKEY_FOOTER)] = '\0'; + +cleanup: + if (f_privkey) { + fclose(f_privkey); } + free(priv); return ret; } int -nc_server_config_new_get_keys(const char *privkey_path, const char *pubkey_path, - char **privkey, char **pubkey, NC_PRIVKEY_FORMAT *privkey_type, NC_PUBKEY_FORMAT *pubkey_type) +nc_server_config_new_get_asym_key_pair(const char *privkey_path, const char *pubkey_path, NC_PUBKEY_FORMAT wanted_pubkey_format, + char **privkey, NC_PRIVKEY_FORMAT *privkey_type, char **pubkey) { int ret = 0; EVP_PKEY *priv_pkey = NULL; - ssh_key priv_sshkey = NULL; - FILE *f_privkey = NULL; - char *header = NULL; - size_t len = 0; - char *priv = NULL, *pub = NULL; - NC_CHECK_ARG_RET(NULL, privkey_path, privkey, pubkey, privkey_type, 1); + NC_CHECK_ARG_RET(NULL, privkey_path, privkey, privkey_type, pubkey, 1); *privkey = NULL; *pubkey = NULL; - /* get private key first */ - f_privkey = fopen(privkey_path, "r"); - if (!f_privkey) { - ERR(NULL, "Unable to open file \"%s\".", privkey_path); - ret = 1; - goto cleanup; - } - - if (getline(&header, &len, f_privkey) < 0) { - ERR(NULL, "Error reading header from file \"%s\".", privkey_path); - ret = 1; - goto cleanup; - } - rewind(f_privkey); - - if (!strncmp(header, NC_PKCS8_PRIVKEY_HEADER, strlen(NC_PKCS8_PRIVKEY_HEADER))) { - /* it's PKCS8 (X.509) private key */ - *privkey_type = NC_PRIVKEY_FORMAT_X509; - ret = nc_server_config_new_get_privkey_openssl(f_privkey, &priv, &priv_pkey); - } else if (!strncmp(header, NC_OPENSSH_PRIVKEY_HEADER, strlen(NC_OPENSSH_PRIVKEY_HEADER))) { - /* it's OpenSSH private key */ - *privkey_type = NC_PRIVKEY_FORMAT_OPENSSH; - ret = nc_server_config_new_get_privkey_libssh(privkey_path, &priv, &priv_sshkey); - } else if (!strncmp(header, NC_PKCS1_RSA_PRIVKEY_HEADER, strlen(NC_PKCS1_RSA_PRIVKEY_HEADER))) { - /* it's RSA privkey in PKCS1 format */ - *privkey_type = NC_PRIVKEY_FORMAT_RSA; - ret = nc_server_config_new_get_privkey_libssh(privkey_path, &priv, &priv_sshkey); - } else if (!strncmp(header, NC_SEC1_EC_PRIVKEY_HEADER, strlen(NC_SEC1_EC_PRIVKEY_HEADER))) { - /* it's EC privkey in SEC1 format */ - *privkey_type = NC_PRIVKEY_FORMAT_EC; - ret = nc_server_config_new_get_privkey_libssh(privkey_path, &priv, &priv_sshkey); - } else { - ERR(NULL, "Private key format not supported."); - ret = 1; - goto cleanup; - } - if (ret) { - goto cleanup; - } - - if (pubkey_path) { - ret = nc_server_config_new_get_pubkey(pubkey_path, &pub, pubkey_type); - } else { - ret = nc_server_config_new_privkey_to_pubkey(priv_pkey, priv_sshkey, *privkey_type, &pub, pubkey_type); - } + /* get private key base64 and EVP_PKEY */ + ret = nc_server_config_new_get_privkey(privkey_path, privkey_type, privkey, &priv_pkey); if (ret) { - ERR(NULL, "Getting public key failed."); + ERR(NULL, "Getting private key from file \"%s\" failed.", privkey_path); goto cleanup; } - /* strip pubkey's header and footer only if it's generated from pkcs8 key (using OpenSSL), - * otherwise it's already stripped - */ - if (!pubkey_path && (*privkey_type == NC_PRIVKEY_FORMAT_X509)) { - *pubkey = strdup(pub + strlen(NC_SUBJECT_PUBKEY_INFO_HEADER)); - if (!*pubkey) { - ERRMEM; - ret = 1; - goto cleanup; + /* get public key, either from file or generate it from the EVP_PKEY */ + if (!pubkey_path) { + if (wanted_pubkey_format == NC_PUBKEY_FORMAT_SSH) { + ret = nc_server_config_new_evp_pkey_to_ssh_pubkey(priv_pkey, pubkey); + } else { + ret = nc_server_config_new_evp_pkey_to_spki_pubkey(priv_pkey, pubkey); } - (*pubkey)[strlen(*pubkey) - strlen(NC_SUBJECT_PUBKEY_INFO_FOOTER)] = '\0'; } else { - *pubkey = strdup(pub); - if (!*pubkey) { - ERRMEM; - ret = 1; - goto cleanup; + if (wanted_pubkey_format == NC_PUBKEY_FORMAT_SSH) { + ret = nc_server_config_new_get_ssh_pubkey_file(pubkey_path, pubkey); + } else { + ret = nc_server_config_new_get_spki_pubkey_file(pubkey_path, pubkey); } } - - /* strip private key's header and footer */ - if (*privkey_type == NC_PRIVKEY_FORMAT_OPENSSH) { - /* only OpenSSH private keys have different header and footer after processing */ - *privkey = strdup(priv + strlen(NC_OPENSSH_PRIVKEY_HEADER)); - if (!*privkey) { - ERRMEM; - ret = 1; - goto cleanup; - } - (*privkey)[strlen(*privkey) - strlen(NC_OPENSSH_PRIVKEY_FOOTER)] = '\0'; - } else { - /* the rest share the same header and footer */ - *privkey = strdup(priv + strlen(NC_PKCS8_PRIVKEY_HEADER)); - if (!*privkey) { - ERRMEM; - ret = 1; - goto cleanup; + if (ret) { + if (pubkey_path) { + ERR(NULL, "Getting public key from file \"%s\" failed.", pubkey_path); + } else { + ERR(NULL, "Generating public key from private key failed."); } - (*privkey)[strlen(*privkey) - strlen(NC_PKCS8_PRIVKEY_FOOTER)] = '\0'; + goto cleanup; } cleanup: - if (f_privkey) { - fclose(f_privkey); - } - - free(header); - free(pub); - free(priv); - - ssh_key_free(priv_sshkey); EVP_PKEY_free(priv_pkey); - return ret; } @@ -911,29 +1146,36 @@ nc_server_config_new_ch_del_endpt(const char *client_name, const char *endpt_nam } API int -nc_server_config_new_keystore_asym_key(const struct ly_ctx *ctx, const char *asym_key_name, const char *privkey_path, - const char *pubkey_path, struct lyd_node **config) +nc_server_config_new_keystore_asym_key(const struct ly_ctx *ctx, NC_TRANSPORT_IMPL ti, const char *asym_key_name, + const char *privkey_path, const char *pubkey_path, struct lyd_node **config) { int ret = 0; char *privkey = NULL, *pubkey = NULL; NC_PRIVKEY_FORMAT privkey_type; - NC_PUBKEY_FORMAT pubkey_type; const char *privkey_format, *pubkey_format; NC_CHECK_ARG_RET(NULL, ctx, asym_key_name, privkey_path, config, 1); /* get the keys as a string from the given files */ - ret = nc_server_config_new_get_keys(privkey_path, pubkey_path, &privkey, &pubkey, &privkey_type, &pubkey_type); + if (ti == NC_TI_LIBSSH) { + ret = nc_server_config_new_get_asym_key_pair(privkey_path, pubkey_path, NC_PUBKEY_FORMAT_SSH, &privkey, &privkey_type, &pubkey); + } else if (ti == NC_TI_OPENSSL) { + ret = nc_server_config_new_get_asym_key_pair(privkey_path, pubkey_path, NC_PUBKEY_FORMAT_X509, &privkey, &privkey_type, &pubkey); + } else { + ERR(NULL, "Only SSH and TLS transports can be used to create an asymmetric key pair in the keystore."); + ret = 1; + goto cleanup; + } if (ret) { ERR(NULL, "Getting keys from file(s) failed."); goto cleanup; } /* get pubkey format str */ - if (pubkey_type == NC_PUBKEY_FORMAT_X509) { - pubkey_format = "ietf-crypto-types:public-key-info-format"; - } else { + if (ti == NC_TI_LIBSSH) { pubkey_format = "ietf-crypto-types:ssh-public-key-format"; + } else { + pubkey_format = "ietf-crypto-types:subject-public-key-info-format"; } /* get privkey identityref value */ @@ -1028,24 +1270,16 @@ nc_server_config_new_truststore_pubkey(const struct ly_ctx *ctx, const char *pub { int ret = 0; char *pubkey = NULL; - NC_PUBKEY_FORMAT pubkey_format; - const char *format; + const char *pubkey_format = "ietf-crypto-types:ssh-public-key-format"; NC_CHECK_ARG_RET(NULL, ctx, pub_bag_name, pubkey_name, pubkey_path, config, 1); - ret = nc_server_config_new_get_pubkey(pubkey_path, &pubkey, &pubkey_format); + ret = nc_server_config_new_get_ssh_pubkey_file(pubkey_path, &pubkey); if (ret) { goto cleanup; } - /* pubkey format to str */ - if (pubkey_format == NC_PUBKEY_FORMAT_SSH2) { - format = "ietf-crypto-types:ssh-public-key-format"; - } else { - format = "ietf-crypto-types:subject-public-key-info-format"; - } - - ret = nc_config_new_create(ctx, config, format, "/ietf-truststore:truststore/public-key-bags/" + ret = nc_config_new_create(ctx, config, pubkey_format, "/ietf-truststore:truststore/public-key-bags/" "public-key-bag[name='%s']/public-key[name='%s']/public-key-format", pub_bag_name, pubkey_name); if (ret) { goto cleanup; diff --git a/src/config_new.h b/src/config_new.h index 76ae4815..3b7401b0 100644 --- a/src/config_new.h +++ b/src/config_new.h @@ -73,10 +73,10 @@ typedef enum { NC_ALG_MAC } NC_ALG_TYPE; -int nc_server_config_new_get_keys(const char *privkey_path, const char *pubkey_path, - char **privkey, char **pubkey, NC_PRIVKEY_FORMAT *privkey_type, NC_PUBKEY_FORMAT *pubkey_type); +int nc_server_config_new_get_asym_key_pair(const char *privkey_path, const char *pubkey_path, NC_PUBKEY_FORMAT wanted_pubkey_type, + char **privkey, NC_PRIVKEY_FORMAT *privkey_type, char **pubkey); -int nc_server_config_new_get_pubkey(const char *pubkey_path, char **pubkey, NC_PUBKEY_FORMAT *pubkey_type); +int nc_server_config_new_get_ssh_pubkey_file(const char *pubkey_path, char **pubkey); int nc_server_config_new_read_certificate(const char *cert_path, char **cert); diff --git a/src/config_new_ssh.c b/src/config_new_ssh.c index 6c10e2f1..db9670e6 100644 --- a/src/config_new_ssh.c +++ b/src/config_new_ssh.c @@ -41,23 +41,15 @@ _nc_server_config_new_ssh_hostkey(const struct ly_ctx *ctx, const char *tree_pat int ret = 0; char *pubkey = NULL, *privkey = NULL; NC_PRIVKEY_FORMAT privkey_type; - NC_PUBKEY_FORMAT pubkey_type; - const char *privkey_format, *pubkey_format; + const char *privkey_format, *pubkey_format = "ietf-crypto-types:ssh-public-key-format"; /* get the keys as a string from the given files */ - ret = nc_server_config_new_get_keys(privkey_path, pubkey_path, &privkey, &pubkey, &privkey_type, &pubkey_type); + ret = nc_server_config_new_get_asym_key_pair(privkey_path, pubkey_path, NC_PUBKEY_FORMAT_SSH, &privkey, &privkey_type, &pubkey); if (ret) { ERR(NULL, "Getting keys from file(s) failed."); goto cleanup; } - /* pubkey format to str */ - if (pubkey_type == NC_PUBKEY_FORMAT_SSH2) { - pubkey_format = "ietf-crypto-types:ssh-public-key-format"; - } else { - pubkey_format = "ietf-crypto-types:subject-public-key-info-format"; - } - /* get privkey identityref value */ privkey_format = nc_config_new_privkey_format_to_identityref(privkey_type); if (!privkey_format) { @@ -372,22 +364,14 @@ _nc_server_config_new_ssh_user_pubkey(const struct ly_ctx *ctx, const char *tree { int ret = 0; char *pubkey = NULL; - NC_PUBKEY_FORMAT pubkey_type; - const char *pubkey_format; + const char *pubkey_format = "ietf-crypto-types:ssh-public-key-format"; /* get pubkey data */ - ret = nc_server_config_new_get_pubkey(pubkey_path, &pubkey, &pubkey_type); + ret = nc_server_config_new_get_ssh_pubkey_file(pubkey_path, &pubkey); if (ret) { goto cleanup; } - /* get pubkey format */ - if (pubkey_type == NC_PUBKEY_FORMAT_SSH2) { - pubkey_format = "ietf-crypto-types:ssh-public-key-format"; - } else { - pubkey_format = "ietf-crypto-types:subject-public-key-info-format"; - } - ret = nc_config_new_create_append(ctx, tree_path, "public-key-format", pubkey_format, config); if (ret) { goto cleanup; diff --git a/src/config_new_tls.c b/src/config_new_tls.c index 985f1764..a094ec6f 100644 --- a/src/config_new_tls.c +++ b/src/config_new_tls.c @@ -38,11 +38,10 @@ _nc_server_config_new_tls_server_certificate(const struct ly_ctx *ctx, const cha int ret = 0; char *privkey = NULL, *pubkey = NULL, *cert = NULL; NC_PRIVKEY_FORMAT privkey_type; - NC_PUBKEY_FORMAT pubkey_type; - const char *privkey_format, *pubkey_format; + const char *privkey_format, *pubkey_format = "ietf-crypto-types:subject-public-key-info-format"; /* get the keys as a string from the given files */ - ret = nc_server_config_new_get_keys(privkey_path, pubkey_path, &privkey, &pubkey, &privkey_type, &pubkey_type); + ret = nc_server_config_new_get_asym_key_pair(privkey_path, pubkey_path, NC_PUBKEY_FORMAT_X509, &privkey, &privkey_type, &pubkey); if (ret) { ERR(NULL, "Getting keys from file(s) failed."); goto cleanup; @@ -55,13 +54,6 @@ _nc_server_config_new_tls_server_certificate(const struct ly_ctx *ctx, const cha goto cleanup; } - /* get pubkey format str */ - if (pubkey_type == NC_PUBKEY_FORMAT_X509) { - pubkey_format = "ietf-crypto-types:public-key-info-format"; - } else { - pubkey_format = "ietf-crypto-types:ssh-public-key-format"; - } - /* get privkey identityref value */ privkey_format = nc_config_new_privkey_format_to_identityref(privkey_type); if (!privkey_format) { diff --git a/src/server_config.c b/src/server_config.c index 7ffaad87..b6f693e0 100644 --- a/src/server_config.c +++ b/src/server_config.c @@ -26,6 +26,9 @@ #include #ifdef NC_ENABLED_SSH_TLS +#include +#include // EVP_PKEY_free +#include // d2i_PUBKEY #include // X509_STORE_free #endif @@ -525,7 +528,7 @@ nc_server_config_get_private_key_type(const char *format) return NC_PRIVKEY_FORMAT_RSA; } else if (!strcmp(format, "ec-private-key-format")) { return NC_PRIVKEY_FORMAT_EC; - } else if (!strcmp(format, "subject-private-key-info-format")) { + } else if (!strcmp(format, "private-key-info-format")) { return NC_PRIVKEY_FORMAT_X509; } else if (!strcmp(format, "openssh-private-key-format")) { return NC_PRIVKEY_FORMAT_OPENSSH; @@ -2006,7 +2009,7 @@ nc_server_config_public_key_format(const struct lyd_node *node, NC_OPERATION op) format = ((struct lyd_node_term *)node)->value.ident->name; if (!strcmp(format, "ssh-public-key-format")) { - pubkey_type = NC_PUBKEY_FORMAT_SSH2; + pubkey_type = NC_PUBKEY_FORMAT_SSH; } else if (!strcmp(format, "subject-public-key-info-format")) { pubkey_type = NC_PUBKEY_FORMAT_X509; } else { @@ -2113,6 +2116,41 @@ nc_server_config_tls_replace_server_public_key(const struct lyd_node *node, stru return 0; } +static int +nc_server_config_is_pk_subject_public_key_info(const char *b64) +{ + int ret = 0; + long len; + char *bin = NULL, *tmp; + EVP_PKEY *pkey = NULL; + + /* base64 2 binary */ + len = nc_base64_to_bin(b64, &bin); + if (len == -1) { + ERR(NULL, "Decoding base64 public key to binary failed."); + ret = -1; + goto cleanup; + } + + /* for deallocation later */ + tmp = bin; + + /* try to create EVP_PKEY from the supposed SubjectPublicKeyInfo binary data */ + pkey = d2i_PUBKEY(NULL, (const unsigned char **)&tmp, len); + if (pkey) { + /* success, it's most likely SubjectPublicKeyInfo pubkey */ + ret = 1; + } else { + /* fail, it's most likely not SubjectPublicKeyInfo pubkey */ + ret = 0; + } + +cleanup: + EVP_PKEY_free(pkey); + free(bin); + return ret; +} + static int nc_server_config_public_key(const struct lyd_node *node, NC_OPERATION op) { @@ -2137,6 +2175,13 @@ nc_server_config_public_key(const struct lyd_node *node, NC_OPERATION op) goto cleanup; } + /* the public key must not be SubjectPublicKeyInfoFormat, as per the ietf-netconf-server model */ + if (nc_server_config_is_pk_subject_public_key_info(lyd_get_value(node))) { + ERR(NULL, "Using Public Key in the SubjectPublicKeyInfo format as an SSH hostkey is forbidden!"); + ret = 1; + goto cleanup; + } + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { /* set to local */ hostkey->store = NC_STORE_LOCAL; @@ -2176,6 +2221,13 @@ nc_server_config_public_key(const struct lyd_node *node, NC_OPERATION op) goto cleanup; } + /* the public key must not be SubjectPublicKeyInfoFormat, as per the ietf-netconf-server model */ + if (nc_server_config_is_pk_subject_public_key_info(lyd_get_value(node))) { + ERR(NULL, "Using Public Key in the SubjectPublicKeyInfo format as an SSH user's key is forbidden!"); + ret = 1; + goto cleanup; + } + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { ret = nc_server_config_replace_auth_key_public_key_leaf(node, pubkey); if (ret) { @@ -2191,6 +2243,13 @@ nc_server_config_public_key(const struct lyd_node *node, NC_OPERATION op) goto cleanup; } + /* the public key must be SubjectPublicKeyInfoFormat, as per the ietf-netconf-server model */ + if (!nc_server_config_is_pk_subject_public_key_info(lyd_get_value(node))) { + ERR(NULL, "TLS server certificate's Public Key must be in the SubjectPublicKeyInfo format!"); + ret = 1; + goto cleanup; + } + if ((op == NC_OP_CREATE) || (op == NC_OP_REPLACE)) { /* set to local */ opts->store = NC_STORE_LOCAL; @@ -2364,12 +2423,18 @@ nc_server_config_create_keystore_reference(const struct lyd_node *node, struct n } if (i == ks->asym_key_count) { - ERR(NULL, "Keystore \"%s\" not found.", lyd_get_value(node)); + ERR(NULL, "Keystore entry \"%s\" not found.", lyd_get_value(node)); return 1; } hostkey->ks_ref = &ks->asym_keys[i]; + /* check if the referenced public key is SubjectPublicKeyInfo */ + if (nc_server_config_is_pk_subject_public_key_info(hostkey->ks_ref->pubkey_data)) { + ERR(NULL, "Using Public Key in the SubjectPublicKeyInfo format as an SSH hostkey is forbidden!"); + return 1; + } + return 0; } @@ -2530,7 +2595,7 @@ nc_server_config_auth_timeout(const struct lyd_node *node, NC_OPERATION op) } static int -nc_server_config_replace_truststore_reference(const struct lyd_node *node, struct nc_client_auth *client_auth) +nc_server_config_ssh_replace_truststore_reference(const struct lyd_node *node, struct nc_client_auth *client_auth) { uint16_t i; struct nc_truststore *ts = &server_opts.truststore; @@ -2549,6 +2614,14 @@ nc_server_config_replace_truststore_reference(const struct lyd_node *node, struc client_auth->ts_ref = &ts->pub_bags[i]; + /* check if any of the referenced public keys is SubjectPublicKeyInfo */ + for (i = 0; i < client_auth->ts_ref->pubkey_count; i++) { + if (nc_server_config_is_pk_subject_public_key_info(client_auth->ts_ref->pubkeys[i].data)) { + ERR(NULL, "Using Public Key in the SubjectPublicKeyInfo format as an SSH user's key is forbidden!"); + return 1; + } + } + return 0; } @@ -2601,7 +2674,7 @@ nc_server_config_truststore_reference(const struct lyd_node *node, NC_OPERATION /* set to truststore */ auth_client->store = NC_STORE_TRUSTSTORE; - ret = nc_server_config_replace_truststore_reference(node, auth_client); + ret = nc_server_config_ssh_replace_truststore_reference(node, auth_client); if (ret) { goto cleanup; } diff --git a/src/server_config.h b/src/server_config.h index ec48fb21..bf127570 100644 --- a/src/server_config.h +++ b/src/server_config.h @@ -138,6 +138,7 @@ int nc_server_config_new_del_endpt(const char *endpt_name, struct lyd_node **con * @brief Creates new YANG data nodes for an asymmetric key in the keystore. * * @param[in] ctx libyang context. + * @param[in] ti Transport in which the key pair will be used. Either SSH or TLS. * @param[in] asym_key_name Identifier of the asymmetric key pair. * This identifier is used to reference the key pair. * @param[in] privkey_path Path to a private key file. @@ -147,8 +148,8 @@ int nc_server_config_new_del_endpt(const char *endpt_name, struct lyd_node **con * Otherwise the new YANG data will be added to the previous data and may override it. * @return 0 on success, non-zero otherwise. */ -int nc_server_config_new_keystore_asym_key(const struct ly_ctx *ctx, const char *asym_key_name, const char *privkey_path, - const char *pubkey_path, struct lyd_node **config); +int nc_server_config_new_keystore_asym_key(const struct ly_ctx *ctx, NC_TRANSPORT_IMPL ti, const char *asym_key_name, + const char *privkey_path, const char *pubkey_path, struct lyd_node **config); /** * @brief Deletes a keystore's asymmetric key from the YANG data. diff --git a/src/server_config_ks.c b/src/server_config_ks.c index 90243343..b39f8d8d 100644 --- a/src/server_config_ks.c +++ b/src/server_config_ks.c @@ -261,7 +261,7 @@ nc_server_config_ks_public_key_format(const struct lyd_node *node, NC_OPERATION format = ((struct lyd_node_term *)node)->value.ident->name; if (!strcmp(format, "ssh-public-key-format")) { - key->pubkey_type = NC_PUBKEY_FORMAT_SSH2; + key->pubkey_type = NC_PUBKEY_FORMAT_SSH; } else if (!strcmp(format, "subject-public-key-info-format")) { key->pubkey_type = NC_PUBKEY_FORMAT_X509; } else { diff --git a/src/server_config_ts.c b/src/server_config_ts.c index e32ef99b..11fec832 100644 --- a/src/server_config_ts.c +++ b/src/server_config_ts.c @@ -544,7 +544,7 @@ nc_server_config_ts_public_key_format(const struct lyd_node *node, NC_OPERATION format = ((struct lyd_node_term *)node)->value.ident->name; if (!strcmp(format, "ssh-public-key-format")) { - pkey->type = NC_PUBKEY_FORMAT_SSH2; + pkey->type = NC_PUBKEY_FORMAT_SSH; } else if (!strcmp(format, "subject-public-key-info-format")) { pkey->type = NC_PUBKEY_FORMAT_X509; } else { diff --git a/src/session.c b/src/session.c index 3851a1d1..ad96fc56 100644 --- a/src/session.c +++ b/src/session.c @@ -36,6 +36,7 @@ #ifdef NC_ENABLED_SSH_TLS #include +#include #include #include @@ -120,6 +121,68 @@ nc_privkey_format_to_str(NC_PRIVKEY_FORMAT format) } } +int +nc_base64_to_bin(const char *base64, char **bin) +{ + BIO *bio, *bio64; + size_t used = 0, size = 0, r = 0; + void *tmp = NULL; + int nl_count, i, remainder; + char *b64; + + /* insert new lines into the base64 string, so BIO_read works correctly */ + nl_count = strlen(base64) / 64; + remainder = strlen(base64) - 64 * nl_count; + b64 = calloc(strlen(base64) + nl_count + 1, 1); + if (!b64) { + ERRMEM; + return -1; + } + + for (i = 0; i < nl_count; i++) { + /* copy 64 bytes and add a NL */ + strncpy(b64 + i * 65, base64 + i * 64, 64); + b64[i * 65 + 64] = '\n'; + } + + /* copy the rest */ + strncpy(b64 + i * 65, base64 + i * 64, remainder); + + bio64 = BIO_new(BIO_f_base64()); + if (!bio64) { + ERR(NULL, "Error creating a bio (%s).", ERR_reason_error_string(ERR_get_error())); + return -1; + } + + bio = BIO_new_mem_buf(b64, strlen(b64)); + if (!bio) { + ERR(NULL, "Error creating a bio (%s).", ERR_reason_error_string(ERR_get_error())); + return -1; + } + + BIO_push(bio64, bio); + + /* store the decoded base64 in bin */ + *bin = NULL; + do { + size += 64; + + tmp = realloc(*bin, size); + if (!tmp) { + free(*bin); + return -1; + } + *bin = tmp; + + r = BIO_read(bio64, *bin + used, 64); + used += r; + } while (r == 64); + + free(b64); + BIO_free_all(bio64); + return size; +} + #endif /* NC_ENABLED_SSH_TLS */ int diff --git a/src/session_p.h b/src/session_p.h index e6816fe4..84d4b840 100644 --- a/src/session_p.h +++ b/src/session_p.h @@ -63,8 +63,8 @@ typedef enum { * Enumeration of SSH public key formats. */ typedef enum { - NC_PUBKEY_FORMAT_SSH2, /**< begins with BEGIN SSH2 PUBLICKEY, see RFC 4716 */ - NC_PUBKEY_FORMAT_X509 /**< begins with BEGIN PUBLICKEY, see RFC 5280 sec. 4.1.2.7 */ + NC_PUBKEY_FORMAT_SSH, /**< see RFC 4253, section 6.6 */ + NC_PUBKEY_FORMAT_X509 /**< see RFC 5280 sec. 4.1.2.7 */ } NC_PUBKEY_FORMAT; /** @@ -745,6 +745,15 @@ struct nc_pam_thread_arg { */ const char *nc_privkey_format_to_str(NC_PRIVKEY_FORMAT format); +/** + * @brief Decodes base64 to binary. + * + * @param[in] base64 Base64 string. + * @param[out] bin Binary result, memory managed by the caller. + * @return Length of the binary data on success, -1 on error. + */ +int nc_base64_to_bin(const char *base64, char **bin); + #endif /* NC_ENABLED_SSH_TLS */ void *nc_realloc(void *ptr, size_t size); diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c index 06af48c5..fde062c5 100644 --- a/src/session_server_ssh.c +++ b/src/session_server_ssh.c @@ -498,68 +498,6 @@ nc_sshcb_auth_kbdint(struct nc_session *session, struct nc_server_ssh_opts *opts return auth_ret; } -static int -nc_server_ssh_decode_base64(const char *base64, char **buffer) -{ - BIO *bio, *bio64; - size_t used = 0, size = 0, r = 0; - void *tmp = NULL; - int nl_count, i, remainder; - char *b64; - - /* insert new lines into the base64 string, so BIO_read works correctly */ - nl_count = strlen(base64) / 64; - remainder = strlen(base64) - 64 * nl_count; - b64 = calloc(strlen(base64) + nl_count + 1, 1); - if (!b64) { - ERRMEM; - return 1; - } - - for (i = 0; i < nl_count; i++) { - /* copy 64 bytes and add a NL */ - strncpy(b64 + i * 65, base64 + i * 64, 64); - b64[i * 65 + 64] = '\n'; - } - - /* copy the rest */ - strncpy(b64 + i * 65, base64 + i * 64, remainder); - - bio64 = BIO_new(BIO_f_base64()); - if (!bio64) { - ERR(NULL, "Error creating a bio (%s).", ERR_reason_error_string(ERR_get_error())); - return 1; - } - - bio = BIO_new_mem_buf(b64, strlen(b64)); - if (!bio) { - ERR(NULL, "Error creating a bio (%s).", ERR_reason_error_string(ERR_get_error())); - return 1; - } - - BIO_push(bio64, bio); - - /* store the decoded base64 in buffer */ - *buffer = NULL; - do { - size += 64; - - tmp = realloc(*buffer, size); - if (!tmp) { - free(*buffer); - return 1; - } - *buffer = tmp; - - r = BIO_read(bio64, *buffer + used, 64); - used += r; - } while (r == 64); - - free(b64); - BIO_free_all(bio64); - return 0; -} - /* * Get the public key type from binary data stored in buffer. * The data is in the form of: 4 bytes = data length, then data of data length @@ -839,7 +777,7 @@ nc_server_ssh_create_ssh_pubkey(const char *base64, ssh_key *key) *key = NULL; /* convert base64 to binary */ - if (nc_server_ssh_decode_base64(base64, &bin)) { + if (nc_base64_to_bin(base64, &bin) == -1) { ERR(NULL, "Unable to decode base64."); ret = 1; goto cleanup; diff --git a/tests/test_ks_ts.c b/tests/test_ks_ts.c index c76997f4..cd8cd96a 100644 --- a/tests/test_ks_ts.c +++ b/tests/test_ks_ts.c @@ -147,7 +147,7 @@ setup_ssh(void **state) ret = nc_server_config_new_ssh_truststore_ref(ctx, "endpt", "client", "test_truststore", &tree); assert_int_equal(ret, 0); - ret = nc_server_config_new_keystore_asym_key(ctx, "test_keystore", TESTS_DIR "/data/key_rsa", NULL, &tree); + ret = nc_server_config_new_keystore_asym_key(ctx, NC_TI_LIBSSH, "test_keystore", TESTS_DIR "/data/key_rsa", NULL, &tree); assert_int_equal(ret, 0); ret = nc_server_config_new_truststore_pubkey(ctx, "test_truststore", "pubkey", TESTS_DIR "/data/id_ed25519.pub", &tree); @@ -244,7 +244,7 @@ setup_tls(void **state) assert_int_equal(ret, 0); /* new keystore asym key pair */ - ret = nc_server_config_new_keystore_asym_key(ctx, "server_key", TESTS_DIR "/data/server.key", NULL, &tree); + ret = nc_server_config_new_keystore_asym_key(ctx, NC_TI_OPENSSL, "server_key", TESTS_DIR "/data/server.key", NULL, &tree); assert_int_equal(ret, 0); /* new keystore cert belonging to the key pair */