diff --git a/src/session_mbedtls.c b/src/session_mbedtls.c index ed55a080..b8287b8c 100644 --- a/src/session_mbedtls.c +++ b/src/session_mbedtls.c @@ -464,6 +464,38 @@ nc_tls_cert_dup(const mbedtls_x509_crt *cert) return new_cert; } +/** + * @brief Duplicate a certificate and append it to a chain. + * + * @param[in] cert Certificate to duplicate and append. + * @param[in,out] chain Chain to append the certificate to. + * @return 0 on success, -1 on error. + */ +static int +nc_server_tls_append_cert_to_chain(mbedtls_x509_crt *cert, mbedtls_x509_crt **chain) +{ + mbedtls_x509_crt *iter, *copy; + + copy = nc_tls_cert_dup(cert); + if (!copy) { + return -1; + } + + if (!*chain) { + /* first in the list */ + *chain = copy; + } else { + /* find the last cert */ + iter = *chain; + while (iter->next) { + iter = iter->next; + } + iter->next = copy; + } + + return 0; +} + /** * @brief Verify a certificate. * @@ -480,6 +512,13 @@ nc_server_tls_verify_cb(void *cb_data, mbedtls_x509_crt *cert, int depth, uint32 struct nc_tls_verify_cb_data *data = cb_data; char *err; + /* append to the chain we're building */ + ret = nc_server_tls_append_cert_to_chain(cert, (mbedtls_x509_crt **)&data->chain); + if (ret) { + nc_tls_cert_destroy_wrap(data->chain); + return MBEDTLS_ERR_X509_ALLOC_FAILED; + } + if (!*flags) { /* in-built verification was successful */ ret = nc_server_tls_verify_cert(cert, depth, 1, data); @@ -509,6 +548,11 @@ nc_server_tls_verify_cb(void *cb_data, mbedtls_x509_crt *cert, int depth, uint32 } } + if ((ret == -1) || (depth == 0)) { + /* free the chain */ + nc_tls_cert_destroy_wrap(data->chain); + } + if (ret == -1) { /* fatal error */ return MBEDTLS_ERR_X509_ALLOC_FAILED; @@ -653,6 +697,36 @@ nc_tls_get_san_value_type_wrap(void *sans, int idx, char **san_value, NC_TLS_CTN return ret; } +int +nc_tls_get_num_certs_wrap(void *chain) +{ + mbedtls_x509_crt *iter; + int n = 0; + + /* chain is a linked list */ + iter = chain; + while (iter) { + ++n; + iter = iter->next; + } + + return n; +} + +void +nc_tls_get_cert_wrap(void *chain, int idx, void **cert) +{ + int i; + mbedtls_x509_crt *iter; + + iter = chain; + for (i = 0; i < idx; i++) { + iter = iter->next; + } + + *cert = iter; +} + int nc_server_tls_certs_match_wrap(void *cert1, void *cert2) { diff --git a/src/session_openssl.c b/src/session_openssl.c index d49b5349..d67bca9e 100644 --- a/src/session_openssl.c +++ b/src/session_openssl.c @@ -309,6 +309,19 @@ nc_server_tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) return 0; } data = SSL_CTX_get_ex_data(ctx, 0); + if (!data) { + ERRINT; + return 0; + } + + /* get the cert chain once */ + if (!data->chain) { + data->chain = X509_STORE_CTX_get0_chain(x509_ctx); + if (!data->chain) { + ERRINT; + return 0; + } + } /* get current cert and its depth */ cert = X509_STORE_CTX_get_current_cert(x509_ctx); @@ -473,6 +486,18 @@ nc_tls_get_san_value_type_wrap(void *sans, int idx, char **san_value, NC_TLS_CTN return ret; } +int +nc_tls_get_num_certs_wrap(void *chain) +{ + return sk_X509_num(chain); +} + +void +nc_tls_get_cert_wrap(void *chain, int idx, void **cert) +{ + *cert = sk_X509_value(chain, idx); +} + int nc_server_tls_certs_match_wrap(void *cert1, void *cert2) { diff --git a/src/session_server_tls.c b/src/session_server_tls.c index 9dc4db0a..6d052c81 100644 --- a/src/session_server_tls.c +++ b/src/session_server_tls.c @@ -256,123 +256,223 @@ nc_server_tls_sha512(void *cert) } static int -nc_server_tls_cert_to_name(struct nc_ctn *ctn_first, void *cert, struct nc_ctn_data *data) +nc_server_tls_get_username(void *cert, struct nc_ctn *ctn, char **username) { - int ret = 0; + char *subject, *cn, *san_value = NULL, rdn_separator; + void *sans; + int i, nsans = 0, rc; + NC_TLS_CTN_MAPTYPE san_type = 0; + +#ifdef HAVE_LIBMEDTLS + rdn_separator = ','; +#else + rdn_separator = '/'; +#endif + + if (ctn->map_type == NC_TLS_CTN_SPECIFIED) { + *username = strdup(ctn->name); + NC_CHECK_ERRMEM_RET(!*username, -1); + } else if (ctn->map_type == NC_TLS_CTN_COMMON_NAME) { + subject = nc_server_tls_get_subject_wrap(cert); + if (!subject) { + return -1; + } + + cn = strstr(subject, "CN="); + if (!cn) { + WRN(NULL, "Certificate does not include the commonName field."); + free(subject); + return 1; + } + + /* skip "CN=" */ + cn += 3; + if (strchr(cn, rdn_separator)) { + *strchr(cn, rdn_separator) = '\0'; + } + *username = strdup(cn); + free(subject); + NC_CHECK_ERRMEM_RET(!*username, -1); + } else { + sans = nc_tls_get_sans_wrap(cert); + if (!sans) { + WRN(NULL, "Certificate has no SANs or failed to retrieve them."); + return 1; + } + nsans = nc_tls_get_num_sans_wrap(sans); + + for (i = 0; i < nsans; i++) { + if ((rc = nc_tls_get_san_value_type_wrap(sans, i, &san_value, &san_type))) { + if (rc == -1) { + /* fatal error */ + nc_tls_sans_destroy_wrap(sans); + return -1; + } + + /* got a type that we dont care about */ + continue; + } + + if ((ctn->map_type == NC_TLS_CTN_SAN_ANY) || (ctn->map_type == san_type)) { + /* found a match */ + *username = san_value; + break; + } + free(san_value); + } + + nc_tls_sans_destroy_wrap(sans); + + if (i == nsans) { + switch (ctn->map_type) { + case NC_TLS_CTN_SAN_RFC822_NAME: + WRN(NULL, "Certificate does not include the SAN rfc822Name field."); + break; + case NC_TLS_CTN_SAN_DNS_NAME: + WRN(NULL, "Certificate does not include the SAN dNSName field."); + break; + case NC_TLS_CTN_SAN_IP_ADDRESS: + WRN(NULL, "Certificate does not include the SAN iPAddress field."); + break; + case NC_TLS_CTN_SAN_ANY: + WRN(NULL, "Certificate does not include any relevant SAN fields."); + break; + default: + break; + } + return 1; + } + } + + return 0; +} + +static int +nc_server_tls_cert_to_name(struct nc_ctn *ctn, void *cert_chain, char **username) +{ + int ret = 1, i, cert_count, fingerprint_match; char *digest_md5 = NULL, *digest_sha1 = NULL, *digest_sha224 = NULL; char *digest_sha256 = NULL, *digest_sha384 = NULL, *digest_sha512 = NULL; - struct nc_ctn *ctn; - NC_TLS_CTN_MAPTYPE map_type; + void *cert; + + /* first make sure the entry is valid */ + if (!ctn->map_type || ((ctn->map_type == NC_TLS_CTN_SPECIFIED) && !ctn->name)) { + VRB(NULL, "Cert verify CTN: entry with id %u not valid, skipping.", ctn->id); + return 1; + } - for (ctn = ctn_first; ctn; ctn = ctn->next) { - /* reset map_type */ - map_type = NC_TLS_CTN_UNKNOWN; + cert_count = nc_tls_get_num_certs_wrap(cert_chain); + for (i = 0; i < cert_count; i++) { + /* reset the flag */ + fingerprint_match = 0; - /* first make sure the entry is valid */ - if (!ctn->map_type || ((ctn->map_type == NC_TLS_CTN_SPECIFIED) && !ctn->name)) { - VRB(NULL, "Cert verify CTN: entry with id %u not valid, skipping.", ctn->id); - continue; + /*get next cert */ + nc_tls_get_cert_wrap(cert_chain, i, &cert); + if (!cert) { + ERR(NULL, "Failed to get certificate from the chain."); + ret = -1; + goto cleanup; } - /* if ctn has no fingerprint, it will match any certificate */ if (!ctn->fingerprint) { - map_type = ctn->map_type; + /* if ctn has no fingerprint, it will match any certificate */ + fingerprint_match = 1; /* MD5 */ } else if (!strncmp(ctn->fingerprint, "01", 2)) { + digest_md5 = nc_server_tls_md5(cert); if (!digest_md5) { - digest_md5 = nc_server_tls_md5(cert); - if (!digest_md5) { - ret = -1; - goto cleanup; - } + ret = -1; + goto cleanup; } if (!strcasecmp(ctn->fingerprint + 3, digest_md5)) { /* we got ourselves a potential winner! */ VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found."); - map_type = ctn->map_type; + fingerprint_match = 1; } + free(digest_md5); + digest_md5 = NULL; /* SHA-1 */ } else if (!strncmp(ctn->fingerprint, "02", 2)) { + digest_sha1 = nc_server_tls_sha1(cert); if (!digest_sha1) { - digest_sha1 = nc_server_tls_sha1(cert); - if (!digest_sha1) { - ret = -1; - goto cleanup; - } + ret = -1; + goto cleanup; } if (!strcasecmp(ctn->fingerprint + 3, digest_sha1)) { /* we got ourselves a potential winner! */ VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found."); - map_type = ctn->map_type; + fingerprint_match = 1; } + free(digest_sha1); + digest_sha1 = NULL; /* SHA-224 */ } else if (!strncmp(ctn->fingerprint, "03", 2)) { + digest_sha224 = nc_server_tls_sha224(cert); if (!digest_sha224) { - digest_sha224 = nc_server_tls_sha224(cert); - if (!digest_sha224) { - ret = -1; - goto cleanup; - } + ret = -1; + goto cleanup; } if (!strcasecmp(ctn->fingerprint + 3, digest_sha224)) { /* we got ourselves a potential winner! */ VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found."); - map_type = ctn->map_type; + fingerprint_match = 1; } + free(digest_sha224); + digest_sha224 = NULL; /* SHA-256 */ } else if (!strncmp(ctn->fingerprint, "04", 2)) { + digest_sha256 = nc_server_tls_sha256(cert); if (!digest_sha256) { - digest_sha256 = nc_server_tls_sha256(cert); - if (!digest_sha256) { - ret = -1; - goto cleanup; - } + ret = -1; + goto cleanup; } if (!strcasecmp(ctn->fingerprint + 3, digest_sha256)) { /* we got ourselves a potential winner! */ VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found."); - map_type = ctn->map_type; + fingerprint_match = 1; } + free(digest_sha256); + digest_sha256 = NULL; /* SHA-384 */ } else if (!strncmp(ctn->fingerprint, "05", 2)) { + digest_sha384 = nc_server_tls_sha384(cert); if (!digest_sha384) { - digest_sha384 = nc_server_tls_sha384(cert); - if (!digest_sha384) { - ret = -1; - goto cleanup; - } + ret = -1; + goto cleanup; } if (!strcasecmp(ctn->fingerprint + 3, digest_sha384)) { /* we got ourselves a potential winner! */ VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found."); - map_type = ctn->map_type; + fingerprint_match = 1; } + free(digest_sha384); + digest_sha384 = NULL; /* SHA-512 */ } else if (!strncmp(ctn->fingerprint, "06", 2)) { + digest_sha512 = nc_server_tls_sha512(cert); if (!digest_sha512) { - digest_sha512 = nc_server_tls_sha512(cert); - if (!digest_sha512) { - ret = -1; - goto cleanup; - } + ret = -1; + goto cleanup; } if (!strcasecmp(ctn->fingerprint + 3, digest_sha512)) { /* we got ourselves a potential winner! */ VRB(NULL, "Cert verify CTN: entry with a matching fingerprint found."); - map_type = ctn->map_type; + fingerprint_match = 1; } + free(digest_sha512); + digest_sha512 = NULL; /* unknown */ } else { @@ -380,115 +480,57 @@ nc_server_tls_cert_to_name(struct nc_ctn *ctn_first, void *cert, struct nc_ctn_d continue; } - if (map_type != NC_TLS_CTN_UNKNOWN) { - /* found a fingerprint match */ - if (!(map_type & data->matched_ctns)) { - data->matched_ctns |= map_type; - data->matched_ctn_type[data->matched_ctn_count++] = map_type; - if (!data->username && (map_type == NC_TLS_CTN_SPECIFIED)) { - data->username = ctn->name; - } + if (fingerprint_match) { + /* found a fingerprint match, try to obtain the username */ + ret = nc_server_tls_get_username(cert, ctn, username); + if (ret == -1) { + /* fatal error */ + goto cleanup; + } else if (!ret) { + /* username found */ + goto cleanup; } } } cleanup: - free(digest_md5); - free(digest_sha1); - free(digest_sha224); - free(digest_sha256); - free(digest_sha384); - free(digest_sha512); return ret; } -int -nc_server_tls_get_username_from_cert(void *cert, NC_TLS_CTN_MAPTYPE map_type, char **username) +static int +_nc_server_tls_cert_to_name(struct nc_server_tls_opts *opts, void *cert_chain, char **username) { - char *subject, *cn, *san_value = NULL, rdn_separator; - void *sans; - int i, nsans = 0, rc; - NC_TLS_CTN_MAPTYPE san_type = 0; - -#ifdef HAVE_LIBMEDTLS - rdn_separator = ','; -#else - rdn_separator = '/'; -#endif - - if (map_type == NC_TLS_CTN_COMMON_NAME) { - subject = nc_server_tls_get_subject_wrap(cert); - if (!subject) { - return -1; - } - - cn = strstr(subject, "CN="); - if (!cn) { - WRN(NULL, "Certificate does not include the commonName field."); - free(subject); - return 1; - } + int ret = 1; + struct nc_endpt *referenced_endpt; + struct nc_ctn *ctn; - /* skip "CN=" */ - cn += 3; - if (strchr(cn, rdn_separator)) { - *strchr(cn, rdn_separator) = '\0'; - } - *username = strdup(cn); - free(subject); - NC_CHECK_ERRMEM_RET(!*username, -1); - } else { - sans = nc_tls_get_sans_wrap(cert); - if (!sans) { - WRN(NULL, "Certificate has no SANs or failed to retrieve them."); - return 1; + for (ctn = opts->ctn; ctn; ctn = ctn->next) { + ret = nc_server_tls_cert_to_name(ctn, cert_chain, username); + if (ret != 1) { + /* fatal error or success */ + goto cleanup; } - nsans = nc_tls_get_num_sans_wrap(sans); - - for (i = 0; i < nsans; i++) { - if ((rc = nc_tls_get_san_value_type_wrap(sans, i, &san_value, &san_type))) { - if (rc == -1) { - /* fatal error */ - nc_tls_sans_destroy_wrap(sans); - return -1; - } - - /* got a type that we dont care about */ - continue; - } + } - if ((map_type == NC_TLS_CTN_SAN_ANY) || (map_type == san_type)) { - /* found a match */ - *username = san_value; - break; - } - free(san_value); + /* do the same for referenced endpoint's ctn entries */ + if (opts->referenced_endpt_name) { + if (nc_server_get_referenced_endpt(opts->referenced_endpt_name, &referenced_endpt)) { + ERRINT; + ret = -1; + goto cleanup; } - nc_tls_sans_destroy_wrap(sans); - - if (i == nsans) { - switch (map_type) { - case NC_TLS_CTN_SAN_RFC822_NAME: - WRN(NULL, "Certificate does not include the SAN rfc822Name field."); - break; - case NC_TLS_CTN_SAN_DNS_NAME: - WRN(NULL, "Certificate does not include the SAN dNSName field."); - break; - case NC_TLS_CTN_SAN_IP_ADDRESS: - WRN(NULL, "Certificate does not include the SAN iPAddress field."); - break; - case NC_TLS_CTN_SAN_ANY: - WRN(NULL, "Certificate does not include any relevant SAN fields."); - break; - default: - break; + for (ctn = referenced_endpt->opts.tls->ctn; ctn; ctn = ctn->next) { + ret = nc_server_tls_cert_to_name(ctn, cert_chain, username); + if (ret != 1) { + /* fatal error or success */ + goto cleanup; } - return 1; } } - return 0; +cleanup: + return ret; } static int @@ -557,11 +599,11 @@ nc_server_tls_verify_peer_cert(void *peer_cert, struct nc_server_tls_opts *opts) int nc_server_tls_verify_cert(void *cert, int depth, int trusted, struct nc_tls_verify_cb_data *cb_data) { - int ret = 0, i; + int ret = 0; char *subject = NULL, *issuer = NULL; struct nc_server_tls_opts *opts = cb_data->opts; struct nc_session *session = cb_data->session; - struct nc_endpt *referenced_endpt; + void *cert_chain = cb_data->chain; if (session->username) { /* already verified */ @@ -590,47 +632,17 @@ nc_server_tls_verify_cert(void *cert, int depth, int trusted, struct nc_tls_veri goto cleanup; } } - } - - /* get matching ctn entries */ - ret = nc_server_tls_cert_to_name(opts->ctn, cert, &cb_data->ctn_data); - if (ret == -1) { - /* fatal error */ - goto cleanup; - } - /* check the referenced endpoint's ctn entries */ - if (opts->referenced_endpt_name) { - if (nc_server_get_referenced_endpt(opts->referenced_endpt_name, &referenced_endpt)) { - ERRINT; - ret = -1; - goto cleanup; - } - - ret = nc_server_tls_cert_to_name(referenced_endpt->opts.tls->ctn, cert, &cb_data->ctn_data); + /* get username since we are at depth 0 and have the whole cert chain, + * the whole chain is needed in order to comply with the following issue: + * https://github.com/CESNET/netopeer2/issues/1596 + */ + ret = _nc_server_tls_cert_to_name(opts, cert_chain, &session->username); if (ret == -1) { /* fatal error */ goto cleanup; } - } - /* obtain username from matched ctn entries */ - if (depth == 0) { - for (i = 0; i < cb_data->ctn_data.matched_ctn_count; i++) { - if (cb_data->ctn_data.matched_ctn_type[i] == NC_TLS_CTN_SPECIFIED) { - session->username = strdup(cb_data->ctn_data.username); - NC_CHECK_ERRMEM_GOTO(!session->username, ret = -1, cleanup); - } else { - ret = nc_server_tls_get_username_from_cert(cert, cb_data->ctn_data.matched_ctn_type[i], &session->username); - if (ret == -1) { - /* fatal error */ - goto cleanup; - } else if (!ret) { - /* username obtained */ - break; - } - } - } if (session->username) { VRB(NULL, "Cert verify CTN: new client username recognized as \"%s\".", session->username); } else { @@ -638,12 +650,12 @@ nc_server_tls_verify_cert(void *cert, int depth, int trusted, struct nc_tls_veri ret = 1; goto cleanup; } - } - if (session->username && server_opts.user_verify_clb && !server_opts.user_verify_clb(session)) { - VRB(session, "Cert verify: user verify callback revoked authorization."); - ret = 1; - goto cleanup; + if (server_opts.user_verify_clb && !server_opts.user_verify_clb(session)) { + VRB(session, "Cert verify: user verify callback revoked authorization."); + ret = 1; + goto cleanup; + } } cleanup: diff --git a/src/session_wrapper.h b/src/session_wrapper.h index f5d38951..0590536d 100644 --- a/src/session_wrapper.h +++ b/src/session_wrapper.h @@ -66,12 +66,7 @@ struct nc_tls_ctx { struct nc_tls_verify_cb_data { struct nc_session *session; /**< NETCONF session. */ struct nc_server_tls_opts *opts; /**< TLS server options. */ - struct nc_ctn_data { - char *username; /**< Username. */ - int matched_ctns; /**< OR'd values of currently matched CTN types. */ - int matched_ctn_type[6]; /**< Currently matched CTN types (order matters). */ - int matched_ctn_count; /**< Number of matched CTN types. */ - } ctn_data; + void *chain; /**< Certificate chain used to verify the client cert. */ }; /** @@ -287,6 +282,23 @@ int nc_tls_get_san_value_type_wrap(void *sans, int idx, char **san_value, NC_TLS #endif +/** + * @brief Get the number of certificates in a certificate chain. + * + * @param[in] chain Certificate chain. + * @return Number of certificates in the chain. + */ +int nc_tls_get_num_certs_wrap(void *chain); + +/** + * @brief Get a certificate from a certificate chain. + * + * @param[in] chain Certificate chain. + * @param[in] idx Index of the certificate to get. + * @param[out] cert Certificate. + */ +void nc_tls_get_cert_wrap(void *chain, int idx, void **cert); + /** * @brief Compare two certificates. *