2015 lines
52 KiB
C
2015 lines
52 KiB
C
|
/**
|
||
|
* @file session_mbedtls.c
|
||
|
* @author Roman Janota <janota@cesnet.cz>
|
||
|
* @brief libnetconf2 - wrapped MbedTLS function calls for TLS/asymmetric cryptography support
|
||
|
*
|
||
|
* This file is a wrapper for MbedTLS function calls. The implementation is done
|
||
|
* in such a way that the original libnetconf2 code is not dependent on MbedTLS.
|
||
|
* This file is included in the build process only if MbedTLS is being used.
|
||
|
*
|
||
|
* @copyright
|
||
|
* Copyright (c) 2024 CESNET, z.s.p.o.
|
||
|
*
|
||
|
* This source code is licensed under BSD 3-Clause License (the "License").
|
||
|
* You may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* https://opensource.org/licenses/BSD-3-Clause
|
||
|
*/
|
||
|
|
||
|
#define _GNU_SOURCE
|
||
|
|
||
|
#include <ctype.h>
|
||
|
#include <errno.h>
|
||
|
#include <poll.h>
|
||
|
#include <pthread.h>
|
||
|
#include <stdint.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include <curl/curl.h>
|
||
|
|
||
|
#include "compat.h"
|
||
|
#include "config.h"
|
||
|
#include "log_p.h"
|
||
|
#include "session.h"
|
||
|
#include "session_p.h"
|
||
|
#include "session_wrapper.h"
|
||
|
|
||
|
#include <mbedtls/base64.h>
|
||
|
#include <mbedtls/bignum.h>
|
||
|
#include <mbedtls/ctr_drbg.h>
|
||
|
#include <mbedtls/entropy.h>
|
||
|
#include <mbedtls/error.h>
|
||
|
#include <mbedtls/net_sockets.h>
|
||
|
#include <mbedtls/oid.h>
|
||
|
#include <mbedtls/pem.h>
|
||
|
#include <mbedtls/ssl.h>
|
||
|
#include <mbedtls/x509.h>
|
||
|
#include <mbedtls/x509_crl.h>
|
||
|
#include <mbedtls/x509_crt.h>
|
||
|
|
||
|
/**
|
||
|
* @brief Converts mbedTLS error codes to a string.
|
||
|
*
|
||
|
* Some mbedTLS functions may return 'high' and some 'low' level errors, try to handle both cases this way.
|
||
|
*
|
||
|
* @param[in] err MbedTLS error code.
|
||
|
* @return Error string.
|
||
|
*/
|
||
|
static const char *
|
||
|
nc_get_mbedtls_str_err(int err)
|
||
|
{
|
||
|
const char *err_str;
|
||
|
|
||
|
err_str = mbedtls_high_level_strerr(err);
|
||
|
if (err_str) {
|
||
|
return err_str;
|
||
|
}
|
||
|
|
||
|
err_str = mbedtls_low_level_strerr(err);
|
||
|
if (err_str) {
|
||
|
return err_str;
|
||
|
}
|
||
|
|
||
|
return "unknown error";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Converts DN to a string.
|
||
|
*
|
||
|
* @param[in] dn Internal DN representation.
|
||
|
* @return DN string on success, NULL of fail.
|
||
|
*/
|
||
|
static char *
|
||
|
nc_server_tls_dn2str(const mbedtls_x509_name *dn)
|
||
|
{
|
||
|
char *str;
|
||
|
size_t len = 64;
|
||
|
int r;
|
||
|
|
||
|
str = malloc(len);
|
||
|
NC_CHECK_ERRMEM_RET(!str, NULL);
|
||
|
|
||
|
while ((r = mbedtls_x509_dn_gets(str, len, dn)) == MBEDTLS_ERR_X509_BUFFER_TOO_SMALL) {
|
||
|
len <<= 1;
|
||
|
str = nc_realloc(str, len);
|
||
|
NC_CHECK_ERRMEM_RET(!str, NULL);
|
||
|
}
|
||
|
if (r < 1) {
|
||
|
free(str);
|
||
|
ERR(NULL, "Failed to convert DN to string (%s).", nc_get_mbedtls_str_err(r));
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Create a new random number generator context.
|
||
|
*
|
||
|
* @param[out] ctr_drbg Random bit generator context.
|
||
|
* @param[out] entropy Entropy context.
|
||
|
* @return 0 on success, 1 on failure.
|
||
|
*/
|
||
|
static int
|
||
|
nc_tls_rng_new(mbedtls_ctr_drbg_context **ctr_drbg, mbedtls_entropy_context **entropy)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
*ctr_drbg = NULL;
|
||
|
*entropy = NULL;
|
||
|
|
||
|
*entropy = malloc(sizeof **entropy);
|
||
|
NC_CHECK_ERRMEM_GOTO(!*entropy, , fail);
|
||
|
*ctr_drbg = malloc(sizeof **ctr_drbg);
|
||
|
NC_CHECK_ERRMEM_GOTO(!*ctr_drbg, , fail);
|
||
|
|
||
|
mbedtls_entropy_init(*entropy);
|
||
|
mbedtls_ctr_drbg_init(*ctr_drbg);
|
||
|
|
||
|
rc = mbedtls_ctr_drbg_seed(*ctr_drbg, mbedtls_entropy_func, *entropy, NULL, 0);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Seeding ctr_drbg failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
fail:
|
||
|
mbedtls_ctr_drbg_free(*ctr_drbg);
|
||
|
free(*ctr_drbg);
|
||
|
if (*entropy) {
|
||
|
mbedtls_entropy_free(*entropy);
|
||
|
free(*entropy);
|
||
|
}
|
||
|
*ctr_drbg = NULL;
|
||
|
*entropy = NULL;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Destroy the random number generator context.
|
||
|
*
|
||
|
* @param[in] ctr_drbg Random bit generator context.
|
||
|
* @param[in] entropy Entropy context.
|
||
|
*/
|
||
|
static void
|
||
|
nc_tls_rng_destroy(mbedtls_ctr_drbg_context *ctr_drbg, mbedtls_entropy_context *entropy)
|
||
|
{
|
||
|
mbedtls_ctr_drbg_free(ctr_drbg);
|
||
|
free(ctr_drbg);
|
||
|
if (entropy) {
|
||
|
mbedtls_entropy_free(entropy);
|
||
|
free(entropy);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Get a string representation of the verification error.
|
||
|
*
|
||
|
* @param[in] err Verification error code.
|
||
|
* @return String representation of the error. Caller is responsible for freeing it.
|
||
|
*/
|
||
|
static char *
|
||
|
nc_tls_get_verify_err_str(int err)
|
||
|
{
|
||
|
int ret;
|
||
|
char *err_buf = NULL;
|
||
|
|
||
|
err_buf = malloc(256);
|
||
|
NC_CHECK_ERRMEM_RET(!err_buf, NULL);
|
||
|
|
||
|
ret = mbedtls_x509_crt_verify_info(err_buf, 256, "", err);
|
||
|
if (ret < 0) {
|
||
|
free(err_buf);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* strip the NL */
|
||
|
err_buf[ret - 1] = '\0';
|
||
|
|
||
|
return err_buf;
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_session_new_wrap(void *tls_cfg)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_ssl_context *session;
|
||
|
|
||
|
session = malloc(sizeof *session);
|
||
|
NC_CHECK_ERRMEM_RET(!session, NULL);
|
||
|
|
||
|
mbedtls_ssl_init(session);
|
||
|
|
||
|
rc = mbedtls_ssl_setup(session, tls_cfg);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Setting up TLS session failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
mbedtls_ssl_free(session);
|
||
|
free(session);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return session;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_session_destroy_wrap(void *tls_session)
|
||
|
{
|
||
|
mbedtls_ssl_free(tls_session);
|
||
|
free(tls_session);
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_config_new_wrap(int UNUSED(side))
|
||
|
{
|
||
|
mbedtls_ssl_config *tls_cfg;
|
||
|
|
||
|
tls_cfg = malloc(sizeof *tls_cfg);
|
||
|
NC_CHECK_ERRMEM_RET(!tls_cfg, NULL);
|
||
|
|
||
|
mbedtls_ssl_config_init(tls_cfg);
|
||
|
return tls_cfg;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_config_destroy_wrap(void *tls_cfg)
|
||
|
{
|
||
|
if (!tls_cfg) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mbedtls_ssl_config_free(tls_cfg);
|
||
|
free(tls_cfg);
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_cert_new_wrap(void)
|
||
|
{
|
||
|
mbedtls_x509_crt *cert;
|
||
|
|
||
|
cert = malloc(sizeof *cert);
|
||
|
NC_CHECK_ERRMEM_RET(!cert, NULL);
|
||
|
|
||
|
mbedtls_x509_crt_init(cert);
|
||
|
return cert;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_cert_destroy_wrap(void *cert)
|
||
|
{
|
||
|
mbedtls_x509_crt_free(cert);
|
||
|
free(cert);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Create a new private key context.
|
||
|
*
|
||
|
* @return New private key context or NULL.
|
||
|
*/
|
||
|
static void *
|
||
|
nc_tls_pkey_new_wrap(void)
|
||
|
{
|
||
|
mbedtls_pk_context *pkey;
|
||
|
|
||
|
pkey = malloc(sizeof *pkey);
|
||
|
NC_CHECK_ERRMEM_RET(!pkey, NULL);
|
||
|
|
||
|
mbedtls_pk_init(pkey);
|
||
|
return pkey;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_privkey_destroy_wrap(void *pkey)
|
||
|
{
|
||
|
mbedtls_pk_free(pkey);
|
||
|
free(pkey);
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_cert_store_new_wrap(void)
|
||
|
{
|
||
|
/* certificate is the same as a certificate store in MbedTLS */
|
||
|
return nc_tls_cert_new_wrap();
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_cert_store_destroy_wrap(void *cert_store)
|
||
|
{
|
||
|
/* certificate is the same as a certificate store in MbedTLS */
|
||
|
nc_tls_cert_destroy_wrap(cert_store);
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_crl_store_new_wrap(void)
|
||
|
{
|
||
|
mbedtls_x509_crl *crl;
|
||
|
|
||
|
crl = malloc(sizeof *crl);
|
||
|
NC_CHECK_ERRMEM_RET(!crl, NULL);
|
||
|
|
||
|
mbedtls_x509_crl_init(crl);
|
||
|
return crl;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_crl_store_destroy_wrap(void *crl_store)
|
||
|
{
|
||
|
mbedtls_x509_crl_free(crl_store);
|
||
|
free(crl_store);
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_pem_to_cert_wrap(const char *cert_data)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_x509_crt *cert;
|
||
|
|
||
|
cert = nc_tls_cert_new_wrap();
|
||
|
if (!cert) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
rc = mbedtls_x509_crt_parse(cert, (const unsigned char *)cert_data, strlen(cert_data) + 1);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Parsing certificate data failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
nc_tls_cert_destroy_wrap(cert);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return cert;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_add_cert_to_store_wrap(void *cert, void *cert_store)
|
||
|
{
|
||
|
mbedtls_x509_crt *iter;
|
||
|
|
||
|
/* store is a linked list */
|
||
|
iter = cert_store;
|
||
|
while (iter->next) {
|
||
|
iter = iter->next;
|
||
|
}
|
||
|
iter->next = cert;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_pem_to_privkey_wrap(const char *privkey_data)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
mbedtls_pk_context *pkey = NULL;
|
||
|
mbedtls_ctr_drbg_context *ctr_drbg = NULL;
|
||
|
mbedtls_entropy_context *entropy = NULL;
|
||
|
|
||
|
rc = nc_tls_rng_new(&ctr_drbg, &entropy);
|
||
|
if (rc) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
pkey = nc_tls_pkey_new_wrap();
|
||
|
if (!pkey) {
|
||
|
rc = 1;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
rc = mbedtls_pk_parse_key(pkey, (const unsigned char *)privkey_data, strlen(privkey_data) + 1, NULL, 0, mbedtls_ctr_drbg_random, ctr_drbg);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Parsing private key data failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
if (rc) {
|
||
|
nc_tls_privkey_destroy_wrap(pkey);
|
||
|
pkey = NULL;
|
||
|
}
|
||
|
nc_tls_rng_destroy(ctr_drbg, entropy);
|
||
|
return pkey;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_server_tls_add_crl_to_store_wrap(const unsigned char *crl_data, size_t size, void *crl_store)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
/* try DER first */
|
||
|
rc = mbedtls_x509_crl_parse_der(crl_store, crl_data, size);
|
||
|
if (!rc) {
|
||
|
/* success, it was DER */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* DER failed, try PEM */
|
||
|
rc = mbedtls_x509_crl_parse(crl_store, crl_data, size + 1);
|
||
|
if (!rc) {
|
||
|
/* success, it was PEM */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* failed to parse it */
|
||
|
ERR(NULL, "Reading downloaded CRL failed.");
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_server_tls_set_tls_versions_wrap(void *tls_cfg, unsigned int tls_versions)
|
||
|
{
|
||
|
if ((tls_versions & NC_TLS_VERSION_10) || ((tls_versions & NC_TLS_VERSION_11))) {
|
||
|
/* skip TLS versions 1.0 and 1.1 */
|
||
|
WRN(NULL, "mbedTLS does not support TLS1.0 and TLS1.1");
|
||
|
}
|
||
|
|
||
|
/* first set the minimum version */
|
||
|
if (tls_versions & NC_TLS_VERSION_12) {
|
||
|
mbedtls_ssl_conf_min_tls_version(tls_cfg, MBEDTLS_SSL_VERSION_TLS1_2);
|
||
|
} else if (tls_versions & NC_TLS_VERSION_13) {
|
||
|
mbedtls_ssl_conf_min_tls_version(tls_cfg, MBEDTLS_SSL_VERSION_TLS1_3);
|
||
|
}
|
||
|
|
||
|
/* then set the maximum version */
|
||
|
if (tls_versions & NC_TLS_VERSION_13) {
|
||
|
mbedtls_ssl_conf_max_tls_version(tls_cfg, MBEDTLS_SSL_VERSION_TLS1_3);
|
||
|
} else if (tls_versions & NC_TLS_VERSION_12) {
|
||
|
mbedtls_ssl_conf_max_tls_version(tls_cfg, MBEDTLS_SSL_VERSION_TLS1_2);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Duplicates a certificate.
|
||
|
*
|
||
|
* @param[in] cert Certificate to duplicate.
|
||
|
* @return Duplicated certificate or NULL.
|
||
|
*/
|
||
|
static mbedtls_x509_crt *
|
||
|
nc_tls_cert_dup(const mbedtls_x509_crt *cert)
|
||
|
{
|
||
|
mbedtls_x509_crt *new_cert;
|
||
|
|
||
|
new_cert = nc_tls_cert_new_wrap();
|
||
|
if (!new_cert) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (mbedtls_x509_crt_parse_der(new_cert, cert->raw.p, cert->raw.len)) {
|
||
|
free(new_cert);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
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.
|
||
|
*
|
||
|
* @param[in] cb_data Callback data (session, opts, data for CTN).
|
||
|
* @param[in] cert Certificate to verify.
|
||
|
* @param[in] depth Certificate depth in the chain.
|
||
|
* @param[in,out] flags Verification flags. Used to propagate errors.
|
||
|
* @return 0 on success (verification result is based on the value of flags), non-zero on fatal-error.
|
||
|
*/
|
||
|
static int
|
||
|
nc_server_tls_verify_cb(void *cb_data, mbedtls_x509_crt *cert, int depth, uint32_t *flags)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
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);
|
||
|
} else {
|
||
|
/* in-built verification failed, but the client still may be authenticated if:
|
||
|
* 1) the peer cert matches any configured end-entity cert
|
||
|
* 2) the peer cert has a valid chain of trust to any configured certificate authority cert
|
||
|
* otherwise just continue until we reach the peer cert (depth = 0)
|
||
|
*/
|
||
|
if ((depth == 0) && (*flags == MBEDTLS_X509_BADCERT_NOT_TRUSTED)) {
|
||
|
/* not trusted self-signed peer certificate, case 1) */
|
||
|
ret = nc_server_tls_verify_cert(cert, depth, 0, data);
|
||
|
if (!ret) {
|
||
|
*flags &= ~MBEDTLS_X509_BADCERT_NOT_TRUSTED;
|
||
|
}
|
||
|
} else if (*flags == MBEDTLS_X509_BADCERT_MISSING) {
|
||
|
/* full chain of trust is invalid, but it may be valid partially, case 2) */
|
||
|
ret = nc_server_tls_verify_cert(cert, depth, 0, data);
|
||
|
if (!ret) {
|
||
|
*flags &= ~MBEDTLS_X509_BADCERT_MISSING;
|
||
|
}
|
||
|
} else {
|
||
|
err = nc_tls_get_verify_err_str(*flags);
|
||
|
ERR(data->session, "Cert verify: fail (%s).", err);
|
||
|
free(err);
|
||
|
ret = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
} else if (!ret) {
|
||
|
/* success */
|
||
|
if ((depth == 0) && (!data->session->opts.server.client_cert)) {
|
||
|
/* copy the client cert */
|
||
|
data->session->opts.server.client_cert = nc_tls_cert_dup(cert);
|
||
|
if (!data->session->opts.server.client_cert) {
|
||
|
return MBEDTLS_ERR_X509_ALLOC_FAILED;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
} else {
|
||
|
if (depth > 0) {
|
||
|
/* chain verify failed, but peer cert can still match */
|
||
|
return 0;
|
||
|
} else {
|
||
|
/* failed to verify peer cert, but return 0 so that we can propagate the error via the flags */
|
||
|
if (!*flags) {
|
||
|
*flags |= MBEDTLS_X509_BADCERT_OTHER;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_server_tls_set_verify_wrap(void *tls_cfg, struct nc_tls_verify_cb_data *cb_data)
|
||
|
{
|
||
|
mbedtls_ssl_conf_authmode(tls_cfg, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||
|
mbedtls_ssl_conf_verify(tls_cfg, nc_server_tls_verify_cb, cb_data);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_client_tls_set_verify_wrap(void *tls_cfg)
|
||
|
{
|
||
|
mbedtls_ssl_conf_authmode(tls_cfg, MBEDTLS_SSL_VERIFY_REQUIRED);
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
nc_server_tls_get_subject_wrap(void *cert)
|
||
|
{
|
||
|
return nc_server_tls_dn2str(&(((mbedtls_x509_crt *)cert)->subject));
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
nc_server_tls_get_issuer_wrap(void *cert)
|
||
|
{
|
||
|
return nc_server_tls_dn2str(&(((mbedtls_x509_crt *)cert)->issuer));
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_get_sans_wrap(void *cert)
|
||
|
{
|
||
|
return &(((mbedtls_x509_crt *)cert)->subject_alt_names);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_sans_destroy_wrap(void *UNUSED(sans))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_get_num_sans_wrap(void *sans)
|
||
|
{
|
||
|
mbedtls_x509_sequence *iter;
|
||
|
int n = 0;
|
||
|
|
||
|
/* sans are a linked list */
|
||
|
iter = sans;
|
||
|
while (iter) {
|
||
|
++n;
|
||
|
iter = iter->next;
|
||
|
}
|
||
|
|
||
|
return n;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_get_san_value_type_wrap(void *sans, int idx, char **san_value, NC_TLS_CTN_MAPTYPE *san_type)
|
||
|
{
|
||
|
int i, rc, ret = 0;
|
||
|
mbedtls_x509_sequence *iter;
|
||
|
mbedtls_x509_subject_alternative_name san = {0};
|
||
|
const mbedtls_x509_buf *ip;
|
||
|
|
||
|
*san_value = NULL;
|
||
|
*san_type = NC_TLS_CTN_UNKNOWN;
|
||
|
|
||
|
/* find the SAN */
|
||
|
iter = sans;
|
||
|
for (i = 0; i < idx; i++) {
|
||
|
iter = iter->next;
|
||
|
}
|
||
|
|
||
|
/* parse it */
|
||
|
rc = mbedtls_x509_parse_subject_alt_name(&iter->buf, &san);
|
||
|
if (rc && (rc != MBEDTLS_ERR_X509_FEATURE_UNAVAILABLE)) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* get its type and value */
|
||
|
switch (san.type) {
|
||
|
case MBEDTLS_X509_SAN_DNS_NAME:
|
||
|
*san_type = NC_TLS_CTN_SAN_DNS_NAME;
|
||
|
*san_value = strndup((const char *)san.san.unstructured_name.p, san.san.unstructured_name.len);
|
||
|
NC_CHECK_ERRMEM_GOTO(!*san_value, ret = -1, cleanup);
|
||
|
break;
|
||
|
case MBEDTLS_X509_SAN_RFC822_NAME:
|
||
|
*san_type = NC_TLS_CTN_SAN_RFC822_NAME;
|
||
|
*san_value = strndup((const char *)san.san.unstructured_name.p, san.san.unstructured_name.len);
|
||
|
NC_CHECK_ERRMEM_GOTO(!*san_value, ret = -1, cleanup);
|
||
|
break;
|
||
|
case MBEDTLS_X509_SAN_IP_ADDRESS:
|
||
|
*san_type = NC_TLS_CTN_SAN_IP_ADDRESS;
|
||
|
ip = &san.san.unstructured_name;
|
||
|
if (ip->len == 4) {
|
||
|
rc = asprintf(san_value, "%d.%d.%d.%d", ip->p[0], ip->p[1], ip->p[2], ip->p[3]) == -1;
|
||
|
NC_CHECK_ERRMEM_GOTO(rc == -1, ret = -1, cleanup);
|
||
|
} else if (ip->len == 16) {
|
||
|
rc = asprintf(san_value, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||
|
ip->p[0], ip->p[1], ip->p[2], ip->p[3], ip->p[4], ip->p[5],
|
||
|
ip->p[6], ip->p[7], ip->p[8], ip->p[9], ip->p[10], ip->p[11],
|
||
|
ip->p[12], ip->p[13], ip->p[14], ip->p[15]);
|
||
|
NC_CHECK_ERRMEM_GOTO(rc == -1, ret = -1, cleanup);
|
||
|
} else {
|
||
|
WRN(NULL, "SAN IP address in an unknown format (length is %d).", ip->len);
|
||
|
ret = 1;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
/* we dont care about other types */
|
||
|
*san_type = NC_TLS_CTN_UNKNOWN;
|
||
|
ret = 1;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
mbedtls_x509_free_subject_alt_name(&san);
|
||
|
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)
|
||
|
{
|
||
|
mbedtls_x509_crt *c1 = cert1;
|
||
|
mbedtls_x509_crt *c2 = cert2;
|
||
|
|
||
|
if (!c1 || !c2) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* compare raw DER encoded data */
|
||
|
if (!c1->raw.p || !c2->raw.p || (c1->raw.len != c2->raw.len) ||
|
||
|
memcmp(c1->raw.p, c2->raw.p, c1->raw.len)) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_server_tls_md5_wrap(void *cert, unsigned char *buf)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_x509_crt *c = cert;
|
||
|
|
||
|
rc = mbedtls_md5(c->raw.p, c->raw.len, buf);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Calculating MD5 digest failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_server_tls_sha1_wrap(void *cert, unsigned char *buf)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_x509_crt *c = cert;
|
||
|
|
||
|
rc = mbedtls_sha1(c->raw.p, c->raw.len, buf);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Calculating SHA-1 digest failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_server_tls_sha224_wrap(void *cert, unsigned char *buf)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_x509_crt *c = cert;
|
||
|
|
||
|
rc = mbedtls_sha256(c->raw.p, c->raw.len, buf, 1);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Calculating SHA-224 digest failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_server_tls_sha256_wrap(void *cert, unsigned char *buf)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_x509_crt *c = cert;
|
||
|
|
||
|
rc = mbedtls_sha256(c->raw.p, c->raw.len, buf, 0);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Calculating SHA-256 digest failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_server_tls_sha384_wrap(void *cert, unsigned char *buf)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_x509_crt *c = cert;
|
||
|
|
||
|
rc = mbedtls_sha512(c->raw.p, c->raw.len, buf, 1);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Calculating SHA-384 digest failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_server_tls_sha512_wrap(void *cert, unsigned char *buf)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_x509_crt *c = cert;
|
||
|
|
||
|
rc = mbedtls_sha512(c->raw.p, c->raw.len, buf, 0);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Calculating SHA-512 digest failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Callback for sending data.
|
||
|
*
|
||
|
* @param[in] ctx Socket.
|
||
|
* @param[in] buf Data to send.
|
||
|
* @param[in] len Length of the data.
|
||
|
* @return Number of bytes sent or negative value on error.
|
||
|
*/
|
||
|
static int
|
||
|
nc_server_tls_send(void *ctx, const unsigned char *buf, size_t len)
|
||
|
{
|
||
|
int sock, ret;
|
||
|
|
||
|
NC_CHECK_ARG_RET(NULL, ctx, MBEDTLS_ERR_NET_INVALID_CONTEXT);
|
||
|
|
||
|
sock = *(int *)ctx;
|
||
|
|
||
|
ret = send(sock, buf, len, MSG_NOSIGNAL);
|
||
|
if (ret < 0) {
|
||
|
if ((errno == EWOULDBLOCK) || (errno = EAGAIN) || (errno == EINTR)) {
|
||
|
return MBEDTLS_ERR_SSL_WANT_WRITE;
|
||
|
} else if ((errno == EPIPE) || (errno == ECONNRESET)) {
|
||
|
return MBEDTLS_ERR_NET_CONN_RESET;
|
||
|
} else {
|
||
|
return MBEDTLS_ERR_NET_SEND_FAILED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Callback for receiving data.
|
||
|
*
|
||
|
* @param[in] ctx Socket.
|
||
|
* @param[out] buf Buffer to store the received data.
|
||
|
* @param[in] len Length of the buffer.
|
||
|
* @return Number of bytes received or negative value on error.
|
||
|
*/
|
||
|
static int
|
||
|
nc_server_tls_recv(void *ctx, unsigned char *buf, size_t len)
|
||
|
{
|
||
|
int sock, ret;
|
||
|
|
||
|
NC_CHECK_ARG_RET(NULL, ctx, MBEDTLS_ERR_NET_INVALID_CONTEXT);
|
||
|
|
||
|
sock = *(int *)ctx;
|
||
|
|
||
|
ret = recv(sock, buf, len, 0);
|
||
|
if (ret < 0) {
|
||
|
if ((errno == EWOULDBLOCK) || (errno = EAGAIN) || (errno == EINTR)) {
|
||
|
return MBEDTLS_ERR_SSL_WANT_READ;
|
||
|
} else if ((errno == EPIPE) || (errno == ECONNRESET)) {
|
||
|
return MBEDTLS_ERR_NET_CONN_RESET;
|
||
|
} else {
|
||
|
return MBEDTLS_ERR_NET_RECV_FAILED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_set_fd_wrap(void *tls_session, int sock, struct nc_tls_ctx *tls_ctx)
|
||
|
{
|
||
|
/* mbedtls sets a pointer to the sock, which is stored in tls_ctx */
|
||
|
*tls_ctx->sock = sock;
|
||
|
mbedtls_ssl_set_bio(tls_session, tls_ctx->sock, nc_server_tls_send, nc_server_tls_recv, NULL);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_server_tls_handshake_step_wrap(void *tls_session)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
|
||
|
rc = mbedtls_ssl_handshake(tls_session);
|
||
|
if (!rc) {
|
||
|
return 1;
|
||
|
} else if ((rc == MBEDTLS_ERR_SSL_WANT_READ) || (rc == MBEDTLS_ERR_SSL_WANT_WRITE)) {
|
||
|
return 0;
|
||
|
} else {
|
||
|
return rc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_client_tls_handshake_step_wrap(void *tls_session, int sock)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
struct pollfd pfd = {sock, 0, 0};
|
||
|
|
||
|
rc = mbedtls_ssl_handshake(tls_session);
|
||
|
if (!rc) {
|
||
|
return 1;
|
||
|
} else if ((rc == MBEDTLS_ERR_SSL_WANT_READ) || (rc == MBEDTLS_ERR_SSL_WANT_WRITE)) {
|
||
|
/* check for EPIPE */
|
||
|
if (poll(&pfd, 1, 0) < 0) {
|
||
|
return -1;
|
||
|
} else {
|
||
|
if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||
|
return -1;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
return rc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_ctx_destroy_wrap(struct nc_tls_ctx *tls_ctx)
|
||
|
{
|
||
|
nc_tls_rng_destroy(tls_ctx->ctr_drbg, tls_ctx->entropy);
|
||
|
nc_tls_cert_destroy_wrap(tls_ctx->cert);
|
||
|
nc_tls_privkey_destroy_wrap(tls_ctx->pkey);
|
||
|
nc_tls_cert_store_destroy_wrap(tls_ctx->cert_store);
|
||
|
nc_tls_crl_store_destroy_wrap(tls_ctx->crl_store);
|
||
|
free(tls_ctx->sock);
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_import_privkey_file_wrap(const char *privkey_path)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_pk_context *pkey;
|
||
|
mbedtls_ctr_drbg_context *ctr_drbg;
|
||
|
mbedtls_entropy_context *entropy;
|
||
|
|
||
|
if (nc_tls_rng_new(&ctr_drbg, &entropy)) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
pkey = nc_tls_pkey_new_wrap();
|
||
|
if (!pkey) {
|
||
|
nc_tls_rng_destroy(ctr_drbg, entropy);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
rc = mbedtls_pk_parse_keyfile(pkey, privkey_path, NULL, mbedtls_ctr_drbg_random, ctr_drbg);
|
||
|
nc_tls_rng_destroy(ctr_drbg, entropy);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Parsing private key from file \"%s\" failed (%s).", privkey_path, nc_get_mbedtls_str_err(rc));
|
||
|
nc_tls_privkey_destroy_wrap(pkey);
|
||
|
return NULL;
|
||
|
}
|
||
|
return pkey;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_client_tls_load_cert_key_wrap(const char *cert_path, const char *key_path, void **cert, void **pkey)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
mbedtls_x509_crt *c;
|
||
|
mbedtls_pk_context *pk;
|
||
|
|
||
|
NC_CHECK_ARG_RET(NULL, cert_path, key_path, cert, pkey, 1);
|
||
|
|
||
|
c = nc_tls_cert_new_wrap();
|
||
|
if (!c) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
ret = mbedtls_x509_crt_parse_file(c, cert_path);
|
||
|
if (ret) {
|
||
|
ERR(NULL, "Parsing certificate from file \"%s\" failed (%s).", cert_path, nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
pk = nc_tls_import_privkey_file_wrap(key_path);
|
||
|
if (!pk) {
|
||
|
ret = 1;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
*cert = c;
|
||
|
c = NULL;
|
||
|
*pkey = pk;
|
||
|
|
||
|
cleanup:
|
||
|
nc_tls_cert_destroy_wrap(c);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_client_tls_load_trusted_certs_wrap(void *cert_store, const char *file_path, const char *dir_path)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
if (file_path && ((rc = mbedtls_x509_crt_parse_file(cert_store, file_path)) < 0)) {
|
||
|
ERR(NULL, "Loading CA certificate from file \"%s\" failed (%s).", file_path, nc_get_mbedtls_str_err(rc));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if (dir_path && ((rc = mbedtls_x509_crt_parse_path(cert_store, dir_path)) < 0)) {
|
||
|
ERR(NULL, "Loading CA certificate from directory \"%s\" failed (%s).", dir_path, nc_get_mbedtls_str_err(rc));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_client_tls_set_hostname_wrap(void *tls_session, const char *hostname)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
rc = mbedtls_ssl_set_hostname(tls_session, hostname);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Setting hostname failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_init_ctx_wrap(void *cert, void *pkey, void *cert_store, void *crl_store, struct nc_tls_ctx *tls_ctx)
|
||
|
{
|
||
|
/* setup rng */
|
||
|
if (nc_tls_rng_new(&tls_ctx->ctr_drbg, &tls_ctx->entropy)) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* fill the context */
|
||
|
tls_ctx->sock = malloc(sizeof *tls_ctx->sock);
|
||
|
NC_CHECK_ERRMEM_RET(!tls_ctx->sock, 1);
|
||
|
tls_ctx->cert = cert;
|
||
|
tls_ctx->pkey = pkey;
|
||
|
tls_ctx->cert_store = cert_store;
|
||
|
tls_ctx->crl_store = crl_store;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_setup_config_from_ctx_wrap(struct nc_tls_ctx *tls_ctx, int side, void *tls_cfg)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
/* set default config data */
|
||
|
if (side == NC_SERVER) {
|
||
|
rc = mbedtls_ssl_config_defaults(tls_cfg, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
|
||
|
} else {
|
||
|
rc = mbedtls_ssl_config_defaults(tls_cfg, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
|
||
|
}
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Setting default TLS config failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* set config's rng */
|
||
|
mbedtls_ssl_conf_rng(tls_cfg, mbedtls_ctr_drbg_random, tls_ctx->ctr_drbg);
|
||
|
/* set config's cert and key */
|
||
|
mbedtls_ssl_conf_own_cert(tls_cfg, tls_ctx->cert, tls_ctx->pkey);
|
||
|
/* set config's CA and CRL cert store */
|
||
|
mbedtls_ssl_conf_ca_chain(tls_cfg, tls_ctx->cert_store, tls_ctx->crl_store);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
uint32_t
|
||
|
nc_tls_get_verify_result_wrap(void *tls_session)
|
||
|
{
|
||
|
return mbedtls_ssl_get_verify_result(tls_session);
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
nc_tls_verify_error_string_wrap(uint32_t err_code)
|
||
|
{
|
||
|
return nc_tls_get_verify_err_str(err_code);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_client_tls_print_connect_err_wrap(int connect_ret, const char *peername, void *UNUSED(tls_session))
|
||
|
{
|
||
|
const char *err = nc_get_mbedtls_str_err(connect_ret);
|
||
|
|
||
|
if (err) {
|
||
|
ERR(NULL, "TLS connection to \"%s\" failed (%s).", peername, err);
|
||
|
} else {
|
||
|
ERR(NULL, "TLS connection to \"%s\" failed.", peername);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_server_tls_print_accept_err_wrap(int accept_ret, void *UNUSED(tls_session))
|
||
|
{
|
||
|
const char *err = nc_get_mbedtls_str_err(accept_ret);
|
||
|
|
||
|
if (err) {
|
||
|
ERR(NULL, "TLS accept failed (%s).", err);
|
||
|
} else {
|
||
|
ERR(NULL, "TLS accept failed.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_is_der_subpubkey_wrap(unsigned char *der, long len)
|
||
|
{
|
||
|
int ret;
|
||
|
mbedtls_pk_context *pkey;
|
||
|
|
||
|
pkey = nc_tls_pkey_new_wrap();
|
||
|
if (!pkey) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
ret = mbedtls_pk_parse_subpubkey(&der, der + len, pkey);
|
||
|
nc_tls_privkey_destroy_wrap(pkey);
|
||
|
|
||
|
return !ret;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_base64_decode_wrap(const char *base64, unsigned char **bin)
|
||
|
{
|
||
|
size_t size;
|
||
|
int rc;
|
||
|
|
||
|
/* get the size of the decoded data */
|
||
|
rc = mbedtls_base64_decode(NULL, 0, &size, (const unsigned char *)base64, strlen(base64));
|
||
|
if (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
|
||
|
ERR(NULL, "Base64 decoding failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
*bin = malloc(size);
|
||
|
NC_CHECK_ERRMEM_RET(!*bin, -1);
|
||
|
|
||
|
/* decode */
|
||
|
rc = mbedtls_base64_decode(*bin, size, &size, (const unsigned char *)base64, strlen(base64));
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Base64 decoding failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
free(*bin);
|
||
|
*bin = NULL;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_base64_encode_wrap(const unsigned char *bin, size_t len, char **base64)
|
||
|
{
|
||
|
size_t size;
|
||
|
int rc;
|
||
|
|
||
|
rc = mbedtls_base64_encode(NULL, 0, &size, bin, len);
|
||
|
if (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
|
||
|
ERR(NULL, "Base64 encoding failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
*base64 = malloc(size);
|
||
|
NC_CHECK_ERRMEM_RET(!*base64, -1);
|
||
|
|
||
|
rc = mbedtls_base64_encode((unsigned char *)*base64, size, &size, bin, len);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Base64 encoding failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
free(*base64);
|
||
|
*base64 = NULL;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_read_wrap(struct nc_session *session, unsigned char *buf, size_t size)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_ssl_context *tls_session = session->ti.tls.session;
|
||
|
|
||
|
rc = mbedtls_ssl_read(tls_session, buf, size);
|
||
|
if (rc <= 0) {
|
||
|
switch (rc) {
|
||
|
case MBEDTLS_ERR_SSL_WANT_READ:
|
||
|
case MBEDTLS_ERR_SSL_WANT_WRITE:
|
||
|
rc = 0;
|
||
|
break;
|
||
|
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
|
||
|
ERR(session, "Communication socket unexpectedly closed (MbedTLS).");
|
||
|
session->status = NC_STATUS_INVALID;
|
||
|
session->term_reason = NC_SESSION_TERM_DROPPED;
|
||
|
rc = -1;
|
||
|
break;
|
||
|
default:
|
||
|
ERR(session, "TLS communication error occurred (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
session->status = NC_STATUS_INVALID;
|
||
|
session->term_reason = NC_SESSION_TERM_OTHER;
|
||
|
rc = -1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_write_wrap(struct nc_session *session, const unsigned char *buf, size_t size)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
mbedtls_ssl_context *tls_session = session->ti.tls.session;
|
||
|
|
||
|
rc = mbedtls_ssl_write(tls_session, buf, size);
|
||
|
if (rc < 0) {
|
||
|
switch (rc) {
|
||
|
case MBEDTLS_ERR_SSL_WANT_READ:
|
||
|
case MBEDTLS_ERR_SSL_WANT_WRITE:
|
||
|
rc = 0;
|
||
|
break;
|
||
|
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
|
||
|
ERR(session, "TLS connection was properly closed.");
|
||
|
rc = -1;
|
||
|
break;
|
||
|
default:
|
||
|
ERR(session, "TLS communication error occurred (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
rc = -1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_get_num_pending_bytes_wrap(void *tls_session)
|
||
|
{
|
||
|
return mbedtls_ssl_get_bytes_avail(tls_session);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_get_fd_wrap(const struct nc_session *session)
|
||
|
{
|
||
|
return session->ti.tls.ctx.sock ? *session->ti.tls.ctx.sock : -1;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_close_notify_wrap(void *tls_session)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
while ((rc = mbedtls_ssl_close_notify(tls_session))) {
|
||
|
if ((rc != MBEDTLS_ERR_SSL_WANT_READ) && (rc != MBEDTLS_ERR_SSL_WANT_WRITE)) {
|
||
|
/* some error occurred */
|
||
|
ERR(NULL, "Sending TLS close notify failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_import_cert_file_wrap(const char *cert_path)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_x509_crt *c;
|
||
|
|
||
|
c = nc_tls_cert_new_wrap();
|
||
|
if (!c) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
rc = mbedtls_x509_crt_parse_file(c, cert_path);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Parsing certificate from file \"%s\" failed (%s).", cert_path, nc_get_mbedtls_str_err(rc));
|
||
|
nc_tls_cert_destroy_wrap(c);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
nc_tls_export_privkey_pem_wrap(void *pkey)
|
||
|
{
|
||
|
int rc;
|
||
|
char *pem;
|
||
|
size_t size = 128;
|
||
|
|
||
|
pem = malloc(size);
|
||
|
NC_CHECK_ERRMEM_RET(!pem, NULL);
|
||
|
|
||
|
while ((rc = mbedtls_pk_write_key_pem(pkey, (unsigned char *)pem, size)) == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
|
||
|
size <<= 1;
|
||
|
pem = nc_realloc(pem, size);
|
||
|
NC_CHECK_ERRMEM_RET(!pem, NULL);
|
||
|
}
|
||
|
if (rc < 0) {
|
||
|
ERR(NULL, "Exporting private key to PEM format failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
free(pem);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return pem;
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
nc_tls_export_cert_pem_wrap(void *cert)
|
||
|
{
|
||
|
char *b64 = NULL, *pem = NULL;
|
||
|
|
||
|
/* encode the certificate */
|
||
|
if (nc_base64_encode_wrap(((mbedtls_x509_crt *)cert)->raw.p, ((mbedtls_x509_crt *)cert)->raw.len, &b64)) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
if (asprintf(&pem, "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n", b64) == -1) {
|
||
|
ERRMEM;
|
||
|
pem = NULL;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
free(b64);
|
||
|
return pem;
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
nc_tls_export_pubkey_pem_wrap(void *pkey)
|
||
|
{
|
||
|
int rc;
|
||
|
char *pem;
|
||
|
size_t size = 128;
|
||
|
|
||
|
pem = malloc(size);
|
||
|
NC_CHECK_ERRMEM_RET(!pem, NULL);
|
||
|
|
||
|
while ((rc = mbedtls_pk_write_pubkey_pem(pkey, (unsigned char *)pem, size)) == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
|
||
|
size <<= 1;
|
||
|
pem = nc_realloc(pem, size);
|
||
|
NC_CHECK_ERRMEM_RET(!pem, NULL);
|
||
|
}
|
||
|
if (rc < 0) {
|
||
|
ERR(NULL, "Exporting public key to PEM format failed (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
free(pem);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return pem;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_privkey_is_rsa_wrap(void *pkey)
|
||
|
{
|
||
|
return mbedtls_pk_get_type(pkey) == MBEDTLS_PK_RSA;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_get_rsa_pubkey_params_wrap(void *pkey, void **e, void **n)
|
||
|
{
|
||
|
int rc;
|
||
|
mbedtls_mpi *exp = NULL, *mod = NULL;
|
||
|
|
||
|
exp = malloc(sizeof *exp);
|
||
|
mod = malloc(sizeof *mod);
|
||
|
if (!exp || !mod) {
|
||
|
ERRMEM;
|
||
|
goto fail;
|
||
|
}
|
||
|
mbedtls_mpi_init(exp);
|
||
|
mbedtls_mpi_init(mod);
|
||
|
|
||
|
if ((rc = mbedtls_rsa_export(mbedtls_pk_rsa(*(mbedtls_pk_context *)pkey), mod, NULL, NULL, NULL, exp))) {
|
||
|
ERR(NULL, "Failed to export RSA public key parameters (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
*e = exp;
|
||
|
*n = mod;
|
||
|
return 0;
|
||
|
|
||
|
fail:
|
||
|
mbedtls_mpi_free(exp);
|
||
|
mbedtls_mpi_free(mod);
|
||
|
free(exp);
|
||
|
free(mod);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_destroy_mpi_wrap(void *mpi)
|
||
|
{
|
||
|
mbedtls_mpi_free(mpi);
|
||
|
free(mpi);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_privkey_is_ec_wrap(void *pkey)
|
||
|
{
|
||
|
return mbedtls_pk_get_type(pkey) == MBEDTLS_PK_ECKEY;
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
nc_tls_get_ec_group_wrap(void *pkey)
|
||
|
{
|
||
|
const mbedtls_ecp_curve_info *curve_info;
|
||
|
mbedtls_ecp_group_id group_id;
|
||
|
mbedtls_ecp_keypair *ec;
|
||
|
|
||
|
/* get the group ID from the EC key */
|
||
|
ec = mbedtls_pk_ec(*(mbedtls_pk_context *)pkey);
|
||
|
group_id = ec->private_grp.id;
|
||
|
|
||
|
/* get the group name based on the id */
|
||
|
curve_info = mbedtls_ecp_curve_info_from_grp_id(group_id);
|
||
|
return strdup(curve_info->name);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_get_ec_pubkey_params_wrap(void *pkey, void **q, void **q_grp)
|
||
|
{
|
||
|
int ret;
|
||
|
mbedtls_ecp_group *grp = NULL;
|
||
|
mbedtls_ecp_point *p = NULL;
|
||
|
mbedtls_mpi *d = NULL;
|
||
|
|
||
|
/* init group, mpi and point */
|
||
|
grp = malloc(sizeof *grp);
|
||
|
d = malloc(sizeof *d);
|
||
|
p = malloc(sizeof *p);
|
||
|
if (!grp || !p || !d) {
|
||
|
ERRMEM;
|
||
|
ret = 1;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
mbedtls_ecp_group_init(grp);
|
||
|
mbedtls_mpi_init(d);
|
||
|
mbedtls_ecp_point_init(p);
|
||
|
|
||
|
/* get the group and public key */
|
||
|
ret = mbedtls_ecp_export(mbedtls_pk_ec(*(mbedtls_pk_context *)pkey), grp, d, p);
|
||
|
if (ret) {
|
||
|
ERR(NULL, "Failed to export EC public key parameters (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
ret = 1;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
*q_grp = grp;
|
||
|
grp = NULL;
|
||
|
*q = p;
|
||
|
p = NULL;
|
||
|
|
||
|
cleanup:
|
||
|
mbedtls_ecp_group_free(grp);
|
||
|
free(grp);
|
||
|
mbedtls_mpi_free(d);
|
||
|
free(d);
|
||
|
mbedtls_ecp_point_free(p);
|
||
|
free(p);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_ec_point_to_bin_wrap(void *q, void *q_grp, unsigned char **bin, int *bin_len)
|
||
|
{
|
||
|
int rc;
|
||
|
unsigned char *buf;
|
||
|
size_t buf_len = 32, out_len;
|
||
|
|
||
|
buf = malloc(buf_len);
|
||
|
NC_CHECK_ERRMEM_RET(!buf, 1);
|
||
|
|
||
|
while ((rc = (mbedtls_ecp_point_write_binary(q_grp, q, MBEDTLS_ECP_PF_COMPRESSED, &out_len, buf, buf_len)))) {
|
||
|
if (rc != MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL) {
|
||
|
break;
|
||
|
}
|
||
|
buf_len <<= 1;
|
||
|
buf = nc_realloc(buf, buf_len);
|
||
|
NC_CHECK_ERRMEM_RET(!buf, 1);
|
||
|
}
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Failed to write EC public key binary (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
free(buf);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
*bin = buf;
|
||
|
*bin_len = out_len;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_ec_point_destroy_wrap(void *p)
|
||
|
{
|
||
|
mbedtls_ecp_point_free(p);
|
||
|
free(p);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_ec_group_destroy_wrap(void *grp)
|
||
|
{
|
||
|
mbedtls_ecp_group_free(grp);
|
||
|
free(grp);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_mpi2bin_wrap(void *mpi, unsigned char **bin, int *bin_len)
|
||
|
{
|
||
|
int rc;
|
||
|
unsigned char *buf;
|
||
|
int buf_len;
|
||
|
|
||
|
buf_len = mbedtls_mpi_size(mpi);
|
||
|
buf = malloc(buf_len);
|
||
|
NC_CHECK_ERRMEM_RET(!buf, 1);
|
||
|
|
||
|
rc = mbedtls_mpi_write_binary(mpi, buf, buf_len);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Failed to convert MPI to binary (%s).", nc_get_mbedtls_str_err(rc));
|
||
|
free(buf);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
*bin = buf;
|
||
|
*bin_len = buf_len;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void *
|
||
|
nc_tls_import_pubkey_file_wrap(const char *pubkey_path)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
mbedtls_pk_context *pk = NULL;
|
||
|
|
||
|
pk = nc_tls_pkey_new_wrap();
|
||
|
if (!pk) {
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
rc = mbedtls_pk_parse_public_keyfile(pk, pubkey_path);
|
||
|
if (rc) {
|
||
|
ERR(NULL, "Parsing public key from file \"%s\" failed (%s).", pubkey_path, nc_get_mbedtls_str_err(rc));
|
||
|
nc_tls_privkey_destroy_wrap(pk);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
return pk;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Parse the CRL distribution points X509v3 extension and obtain the URIs.
|
||
|
*
|
||
|
* @param[in,out] p Pointer to the DER encoded extension. When the function gets called, this should
|
||
|
* point to the first byte in the value of CRLDistributionPoints.
|
||
|
* @param[in] len Length of the CRLDistributionPoints ASN.1 encoded value.
|
||
|
* @param[out] uris Array of URIs found in the extension.
|
||
|
* @param[out] uri_count Number of URIs found in the extension.
|
||
|
* @return 0 on success, non-zero on error.
|
||
|
*/
|
||
|
static int
|
||
|
nc_server_tls_parse_crl_dist_points(unsigned char **p, size_t len, char ***uris, int *uri_count)
|
||
|
{
|
||
|
char **tmp_uris, *uri;
|
||
|
int ret = 0;
|
||
|
size_t name_len;
|
||
|
unsigned char *end_crl_dist_points, *end_general_names, *end_names, tag, tag_class, tag_value;
|
||
|
|
||
|
/*
|
||
|
* parsing the value of CRLDistributionPoints
|
||
|
*
|
||
|
* CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
|
||
|
*/
|
||
|
end_crl_dist_points = *p + len;
|
||
|
while (*p < end_crl_dist_points) {
|
||
|
/*
|
||
|
* DistributionPoint ::= SEQUENCE {
|
||
|
* distributionPoint [0] DistributionPointName OPTIONAL,
|
||
|
* reasons [1] ReasonFlags OPTIONAL,
|
||
|
* cRLIssuer [2] GeneralNames OPTIONAL }
|
||
|
*/
|
||
|
ret = mbedtls_asn1_get_tag(p, end_crl_dist_points, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE);
|
||
|
if (ret) {
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
if (!len) {
|
||
|
/* empty sequence */
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* parse distributionPoint */
|
||
|
ret = mbedtls_asn1_get_tag(p, end_crl_dist_points, &len, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0);
|
||
|
if (!ret) {
|
||
|
/*
|
||
|
* DistributionPointName ::= CHOICE {
|
||
|
* fullName [0] GeneralNames,
|
||
|
* nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
|
||
|
*/
|
||
|
ret = mbedtls_asn1_get_tag(p, end_crl_dist_points, &len, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0);
|
||
|
if (ret) {
|
||
|
if ((ret == MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) && (**p == (MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1))) {
|
||
|
/* it's nameRelativeToCRLIssuer, but we don't support it */
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (nameRelativeToCRLIssuer not yet supported).");
|
||
|
goto cleanup;
|
||
|
} else {
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName */
|
||
|
end_general_names = *p + len;
|
||
|
ret = mbedtls_asn1_get_tag(p, end_general_names, &name_len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE);
|
||
|
if (ret) {
|
||
|
ERR(NULL, "Failed to parse GeneralNames in CRL distribution points extension (%s)", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
end_names = *p + name_len;
|
||
|
while (*p < end_names) {
|
||
|
tag = **p;
|
||
|
tag_class = tag & MBEDTLS_ASN1_TAG_CLASS_MASK;
|
||
|
tag_value = tag & MBEDTLS_ASN1_TAG_VALUE_MASK;
|
||
|
/*
|
||
|
* read the GeneralName tag and length
|
||
|
*
|
||
|
* GeneralName ::= CHOICE {
|
||
|
* otherName [0] AnotherName,
|
||
|
* rfc822Name [1] IA5String,
|
||
|
* dNSName [2] IA5String,
|
||
|
* x400Address [3] ORAddress,
|
||
|
* directoryName [4] Name,
|
||
|
* ediPartyName [5] EDIPartyName,
|
||
|
* uniformResourceIdentifier [6] IA5String,
|
||
|
* iPAddress [7] OCTET STRING,
|
||
|
* registeredID [8] OBJECT IDENTIFIER }
|
||
|
*/
|
||
|
ret = mbedtls_asn1_get_tag(p, end_names, &name_len, MBEDTLS_ASN1_CONTEXT_SPECIFIC | (tag & MBEDTLS_ASN1_CONSTRUCTED) | tag_value);
|
||
|
if (ret) {
|
||
|
ERR(NULL, "Failed to parse GeneralName in CRL distribution points extension (%s).",
|
||
|
nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* uniformResourceIdentifier [6] IA5String */
|
||
|
if ((tag_class == MBEDTLS_ASN1_CONTEXT_SPECIFIC) &&
|
||
|
(tag_value == MBEDTLS_X509_SAN_UNIFORM_RESOURCE_IDENTIFIER)) {
|
||
|
uri = strndup((char *)*p, name_len);
|
||
|
if (!uri) {
|
||
|
ERRMEM;
|
||
|
ret = 1;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
tmp_uris = realloc(*uris, (*uri_count + 1) * sizeof **uris);
|
||
|
if (!tmp_uris) {
|
||
|
ERRMEM;
|
||
|
free(uri);
|
||
|
ret = 1;
|
||
|
goto cleanup;
|
||
|
}
|
||
|
*uris = tmp_uris;
|
||
|
|
||
|
(*uris)[*uri_count] = uri;
|
||
|
++(*uri_count);
|
||
|
}
|
||
|
|
||
|
/* Move to the next GeneralName */
|
||
|
*p += name_len;
|
||
|
}
|
||
|
|
||
|
} else if (ret != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) {
|
||
|
/* failed to parse it, but not because it's optional */
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_server_tls_get_crl_distpoint_uris_wrap(void *leaf_cert, void *cert_store, char ***uris, int *uri_count)
|
||
|
{
|
||
|
int ret = 0, is_critical = 0, cert_count, i;
|
||
|
mbedtls_x509_crt *cert;
|
||
|
unsigned char *p, *end_v3_ext, *end_ext, *end_ext_octet;
|
||
|
size_t len;
|
||
|
mbedtls_x509_buf ext_oid = {0};
|
||
|
|
||
|
NC_CHECK_ARG_RET(NULL, cert_store, uris, uri_count, 1);
|
||
|
|
||
|
*uris = NULL;
|
||
|
*uri_count = 0;
|
||
|
|
||
|
/* get the number of certs in the store */
|
||
|
cert = cert_store;
|
||
|
cert_count = 0;
|
||
|
while (cert) {
|
||
|
++cert_count;
|
||
|
cert = cert->next;
|
||
|
}
|
||
|
|
||
|
/* iterate over all the certs */
|
||
|
for (i = -1; i < cert_count; i++) {
|
||
|
if (i == -1) {
|
||
|
cert = leaf_cert;
|
||
|
} else if (i == 0) {
|
||
|
cert = cert_store;
|
||
|
} else {
|
||
|
cert = cert->next;
|
||
|
}
|
||
|
|
||
|
if (!cert->v3_ext.len) {
|
||
|
/* no extensions, skip this cert */
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* go over all the extensions and try to find the CRL distribution points */
|
||
|
p = cert->v3_ext.p;
|
||
|
end_v3_ext = p + cert->v3_ext.len;
|
||
|
|
||
|
/*
|
||
|
* Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
||
|
*/
|
||
|
ret = mbedtls_asn1_get_tag(&p, end_v3_ext, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE);
|
||
|
if (ret) {
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
while (p < end_v3_ext) {
|
||
|
/*
|
||
|
* Extension ::= SEQUENCE {
|
||
|
* extnID OBJECT IDENTIFIER,
|
||
|
* critical BOOLEAN DEFAULT FALSE,
|
||
|
* extnValue OCTET STRING }
|
||
|
*/
|
||
|
ret = mbedtls_asn1_get_tag(&p, end_v3_ext, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE);
|
||
|
if (ret) {
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
end_ext = p + len;
|
||
|
|
||
|
/* parse extnID */
|
||
|
ret = mbedtls_asn1_get_tag(&p, end_ext, &ext_oid.len, MBEDTLS_ASN1_OID);
|
||
|
if (ret) {
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
ext_oid.tag = MBEDTLS_ASN1_OID;
|
||
|
ext_oid.p = p;
|
||
|
|
||
|
if (memcmp(ext_oid.p, MBEDTLS_OID_CRL_DISTRIBUTION_POINTS, ext_oid.len)) {
|
||
|
/* not the extension we are looking for */
|
||
|
p = end_ext;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
p += ext_oid.len;
|
||
|
|
||
|
/* parse optional critical */
|
||
|
ret = mbedtls_asn1_get_bool(&p, end_ext, &is_critical);
|
||
|
if (ret && (ret != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG)) {
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* parse extnValue */
|
||
|
ret = mbedtls_asn1_get_tag(&p, end_ext, &len, MBEDTLS_ASN1_OCTET_STRING);
|
||
|
if (ret) {
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
end_ext_octet = p + len;
|
||
|
|
||
|
/*
|
||
|
* parse extnValue, that is CRLDistributionPoints
|
||
|
*
|
||
|
* CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
|
||
|
*/
|
||
|
ret = mbedtls_asn1_get_tag(&p, end_ext_octet, &len, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE);
|
||
|
if (ret) {
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
}
|
||
|
if (p + len != end_ext_octet) {
|
||
|
/* length mismatch */
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (%s).", nc_get_mbedtls_str_err(ret));
|
||
|
goto cleanup;
|
||
|
} else if (!len) {
|
||
|
/* empty sequence, but size is 1..max */
|
||
|
ERR(NULL, "Failed to parse CRL distribution points extension (empty sequence).");
|
||
|
goto cleanup;
|
||
|
}
|
||
|
|
||
|
/* parse the distribution points and obtain the uris */
|
||
|
ret = nc_server_tls_parse_crl_dist_points(&p, len, uris, uri_count);
|
||
|
if (ret) {
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cleanup:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_process_cipher_suite_wrap(const char *cipher, char **out)
|
||
|
{
|
||
|
const char *begin, *ptr;
|
||
|
|
||
|
/* check if it's a TLS 1.3 cipher suite */
|
||
|
if (!strcmp(cipher, "tls-aes-256-gcm-sha384") || !strcmp(cipher, "tls-aes-128-gcm-sha256") ||
|
||
|
!strcmp(cipher, "tls-chacha20-poly1305-sha256") || !strcmp(cipher, "tls-aes-128-ccm-sha256") ||
|
||
|
!strcmp(cipher, "tls-aes-128-ccm-8-sha256")) {
|
||
|
/* + 3 because mbedtls has "TLS1-3" prefix for 1.3 suites */
|
||
|
*out = malloc(strlen(cipher) + 3 + 1);
|
||
|
NC_CHECK_ERRMEM_RET(!*out, 1);
|
||
|
sprintf(*out, "TLS1-3");
|
||
|
begin = cipher + 4;
|
||
|
} else {
|
||
|
*out = malloc(strlen(cipher) + 1);
|
||
|
NC_CHECK_ERRMEM_RET(!*out, 1);
|
||
|
begin = cipher;
|
||
|
}
|
||
|
|
||
|
/* convert to uppercase */
|
||
|
for (ptr = begin; *ptr; ptr++) {
|
||
|
(*out)[ptr - begin] = toupper(*ptr);
|
||
|
}
|
||
|
|
||
|
(*out)[ptr - begin] = '\0';
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
nc_tls_append_cipher_suite_wrap(struct nc_server_tls_opts *opts, const char *cipher_suite)
|
||
|
{
|
||
|
int cipher_id;
|
||
|
|
||
|
cipher_id = mbedtls_ssl_get_ciphersuite_id(cipher_suite);
|
||
|
if (!cipher_id) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* append the cipher suite to a zero terminated array */
|
||
|
if (!opts->ciphers) {
|
||
|
/* first entry, account for terminating 0 */
|
||
|
opts->ciphers = malloc(2 * sizeof *opts->ciphers);
|
||
|
NC_CHECK_ERRMEM_RET(!opts->ciphers, 1);
|
||
|
((int *)opts->ciphers)[0] = cipher_id;
|
||
|
opts->cipher_count = 1;
|
||
|
} else {
|
||
|
/* +2 because of terminating 0 */
|
||
|
opts->ciphers = nc_realloc(opts->ciphers, (opts->cipher_count + 2) * sizeof *opts->ciphers);
|
||
|
NC_CHECK_ERRMEM_RET(!opts->ciphers, 1);
|
||
|
((int *)opts->ciphers)[opts->cipher_count] = cipher_id;
|
||
|
opts->cipher_count++;
|
||
|
}
|
||
|
|
||
|
/* terminate the array */
|
||
|
((int *)opts->ciphers)[opts->cipher_count] = 0;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_server_tls_set_cipher_suites_wrap(void *tls_cfg, void *cipher_suites)
|
||
|
{
|
||
|
mbedtls_ssl_conf_ciphersuites(tls_cfg, cipher_suites);
|
||
|
}
|
||
|
|
||
|
time_t
|
||
|
nc_tls_get_cert_exp_time_wrap(void *cert)
|
||
|
{
|
||
|
struct tm t = {0};
|
||
|
mbedtls_x509_time *valid_to;
|
||
|
|
||
|
valid_to = &((mbedtls_x509_crt *)cert)->valid_to;
|
||
|
|
||
|
t.tm_sec = valid_to->sec;
|
||
|
t.tm_min = valid_to->min;
|
||
|
t.tm_hour = valid_to->hour;
|
||
|
|
||
|
t.tm_mday = valid_to->day;
|
||
|
t.tm_mon = valid_to->mon - 1;
|
||
|
t.tm_year = valid_to->year - 1900;
|
||
|
|
||
|
/* let system figure out the DST */
|
||
|
t.tm_isdst = -1;
|
||
|
|
||
|
return timegm(&t);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Convert the MbedTLS key export type to a label for the keylog file.
|
||
|
*
|
||
|
* @param[in] type MbedTLS key export type.
|
||
|
* @return Label for the keylog file or NULL if the type is not supported.
|
||
|
*/
|
||
|
static const char *
|
||
|
nc_tls_keylog_type2label(mbedtls_ssl_key_export_type type)
|
||
|
{
|
||
|
switch (type) {
|
||
|
case MBEDTLS_SSL_KEY_EXPORT_TLS12_MASTER_SECRET:
|
||
|
return "CLIENT_RANDOM";
|
||
|
#ifdef MBEDTLS_SSL_PROTO_TLS1_3
|
||
|
case MBEDTLS_SSL_KEY_EXPORT_TLS1_3_CLIENT_HANDSHAKE_TRAFFIC_SECRET:
|
||
|
return "CLIENT_HANDSHAKE_TRAFFIC_SECRET";
|
||
|
case MBEDTLS_SSL_KEY_EXPORT_TLS1_3_SERVER_HANDSHAKE_TRAFFIC_SECRET:
|
||
|
return "SERVER_HANDSHAKE_TRAFFIC_SECRET";
|
||
|
case MBEDTLS_SSL_KEY_EXPORT_TLS1_3_CLIENT_APPLICATION_TRAFFIC_SECRET:
|
||
|
return "CLIENT_TRAFFIC_SECRET_0";
|
||
|
case MBEDTLS_SSL_KEY_EXPORT_TLS1_3_SERVER_APPLICATION_TRAFFIC_SECRET:
|
||
|
return "SERVER_TRAFFIC_SECRET_0";
|
||
|
#endif
|
||
|
default:
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @brief Callback for writing a line in the keylog file.
|
||
|
*/
|
||
|
static void
|
||
|
nc_tls_keylog_write_line(void *UNUSED(p_expkey), mbedtls_ssl_key_export_type type, const unsigned char *secret,
|
||
|
size_t secret_len, const unsigned char client_random[32],
|
||
|
const unsigned char UNUSED(server_random[32]), mbedtls_tls_prf_types UNUSED(tls_prf_type))
|
||
|
{
|
||
|
size_t linelen, len = 0, i, client_random_len;
|
||
|
char buf[256];
|
||
|
const char *label;
|
||
|
|
||
|
if (!server_opts.tls_keylog_file) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
label = nc_tls_keylog_type2label(type);
|
||
|
if (!label) {
|
||
|
/* type not supported */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* <Label> <space> 0x<ClientRandom> <space> 0x<Secret> */
|
||
|
linelen = strlen(label) + 1 + 2 * 32 + 1 + 2 * secret_len + 1;
|
||
|
if (linelen > sizeof(buf)) {
|
||
|
/* sanity check, should not happen since the max len should be 196 bytes */
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* write the label */
|
||
|
len += sprintf(buf + len, "%s ", label);
|
||
|
|
||
|
/* write the client random */
|
||
|
client_random_len = 32;
|
||
|
for (i = 0; i < client_random_len; i++) {
|
||
|
len += sprintf(buf + len, "%02x", client_random[i]);
|
||
|
}
|
||
|
len += sprintf(buf + len, " ");
|
||
|
|
||
|
/* write the secret */
|
||
|
for (i = 0; i < secret_len; i++) {
|
||
|
len += sprintf(buf + len, "%02x", secret[i]);
|
||
|
}
|
||
|
|
||
|
len += sprintf(buf + len, "\n");
|
||
|
buf[len] = '\0';
|
||
|
|
||
|
if (len != linelen) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
fputs(buf, server_opts.tls_keylog_file);
|
||
|
fflush(server_opts.tls_keylog_file);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nc_tls_keylog_session_wrap(void *session)
|
||
|
{
|
||
|
mbedtls_ssl_set_export_keys_cb(session, nc_tls_keylog_write_line, NULL);
|
||
|
}
|