/* * Copyright (c) 2019-2021 Apple Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * This file contains a TLS Shim that allows mDNSPosix to use mbedtls to do TLS session * establishment and also to accept TLS connections. */ #include "mDNSEmbeddedAPI.h" // Defines the interface provided to the client layer above #include "DNSCommon.h" #include "mDNSPosix.h" // Defines the specific types needed to run mDNS on this platform #include "PlatformCommon.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Posix TLS server context struct TLSContext_struct { mbedtls_ssl_context context; }; struct TLSServerContext_struct { mbedtls_x509_crt cert; mbedtls_pk_context key; mbedtls_x509_crt cacert; mbedtls_ssl_config config; }; // Context that is shared amongs all TLS connections, regardless of which server cert/key is in use. static mbedtls_entropy_context entropy; static mbedtls_ctr_drbg_context ctr_drbg; mDNSBool mDNSPosixTLSInit(void) { int status; mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&ctr_drbg); status = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0); if (status != 0) { LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Unable to seed RNG: %x", -status); return mDNSfalse; } return mDNStrue; } void mDNSPosixTLSContextFree(TLSContext *tls) { mbedtls_ssl_free(&tls->context); mDNSPlatformMemFree(tls); } TLSContext * mDNSPosixTLSClientStateCreate(TCPSocket *sock) { int status; TLSContext *tls; mbedtls_ssl_config config; status = mbedtls_ssl_config_defaults(&config, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); if (status != 0) { LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Unable to set ssl config defaults: %d", -status); return NULL; } mbedtls_ssl_conf_authmode(&config, (sock->flags & kTCPSocketFlags_TLSValidationNotRequired ? MBEDTLS_SSL_VERIFY_NONE : MBEDTLS_SSL_VERIFY_REQUIRED)); tls = mDNSPlatformMemAllocateClear(sizeof(*tls)); if (tls == mDNSNULL) { return tls; } status = mbedtls_ssl_setup(&tls->context, &config); if (status == 0) { if (sock->hostname) { char serverName[MAX_ESCAPED_DOMAIN_NAME]; ConvertDomainNameToCString_withescape(sock->hostname, serverName, '\\'); status = mbedtls_ssl_set_hostname(&tls->context, serverName); } } if (status != 0) { LogInfo("Unable to set up TLS listener state: %x", -status); mDNSPosixTLSContextFree(tls); return NULL; } return tls; } static int tls_io_send(void *ctx, const unsigned char *buf, size_t len) { ssize_t ret; TCPSocket *sock = ctx; ret = mDNSPosixWriteTCP(sock->events.fd, (const char *)buf, len); if (ret < 0) { if (errno == EAGAIN) { return MBEDTLS_ERR_SSL_WANT_WRITE; } else { return MBEDTLS_ERR_SSL_INTERNAL_ERROR; } } else { return (int)ret; } } static int tls_io_recv(void *ctx, unsigned char *buf, size_t max) { ssize_t ret; TCPSocket *sock = ctx; mDNSBool closed = mDNSfalse; ret = mDNSPosixReadTCP(sock->events.fd, buf, max, &closed); if (ret < 0) { if (errno == EWOULDBLOCK || errno == EAGAIN) { return MBEDTLS_ERR_SSL_WANT_READ; } else { return MBEDTLS_ERR_SSL_INTERNAL_ERROR; } } else if (closed) { return MBEDTLS_ERR_SSL_CONN_EOF; } else { return (int)ret; } } mDNSBool mDNSPosixTLSStart(TCPSocket *sock) { int status; // Set up the I/O functions. mbedtls_ssl_set_bio(&sock->tls->context, sock, tls_io_send, tls_io_recv, NULL); // Start the TLS handshake status = mbedtls_ssl_handshake(&sock->tls->context); if (status != 0 && status != MBEDTLS_ERR_SSL_WANT_READ && status != MBEDTLS_ERR_SSL_WANT_WRITE) { LogInfo("TLS handshake failed: %x", -status); return mDNSfalse; } return mDNStrue; } TLSContext * PosixTLSAccept(TCPListener *listenContext) { int status; TLSContext *tls = mDNSPlatformMemAllocateClear(sizeof(*tls)); if (tls == mDNSNULL) { return tls; } status = mbedtls_ssl_setup(&tls->context, &listenContext->tls->config); if (status != 0) { LogInfo("Unable to set up TLS listener state: %x", -status); mDNSPlatformMemFree(tls); return NULL; } return tls; } int mDNSPosixTLSRead(TCPSocket *sock, void *buf, unsigned long buflen, mDNSBool *closed) { int ret; // Shouldn't ever happen. if (!sock->tls) { LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "mDNSPosixTLSRead: called without TLS context!"); *closed = mDNStrue; return 0; } ret = mbedtls_ssl_read(&sock->tls->context, buf, buflen); if (ret < 0) { switch (ret) { case MBEDTLS_ERR_SSL_WANT_READ: return 0; case MBEDTLS_ERR_SSL_WANT_WRITE: LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Got SSL want write in TLS read!"); // This means we got EWOULDBLOCK on a write operation. // Not implemented yet, but the right way to handle this is probably to // deselect read events until the socket is ready to write, then write, // and then re-enable read events. What we don't want is to keep calling // read, because that will spin. return 0; case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS: LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Got async in progress in TLS read!"); // No idea how to handle this yet. return 0; #ifdef MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS: LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Got crypto in progress in TLS read!"); // No idea how to handle this. return 0; #endif default: LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Unexpected response from SSL read: %x", -ret); return -1; } } else { // mbedtls returns 0 for EOF, just like read(), but we need a different signal, // so we treat 0 as an error (for now). In principle, we should get a notification // when the remote end is done writing, so a clean close should be different than // an abrupt close. if (ret == 0) { if (closed) { *closed = mDNStrue; } return -1; } return ret; } } int mDNSPosixTLSWrite(TCPSocket *sock, const void *buf, unsigned long buflen) { int ret; ret = mbedtls_ssl_write(&sock->tls->context, buf, buflen); if (ret < 0) { switch (ret) { case MBEDTLS_ERR_SSL_WANT_READ: return 0; case MBEDTLS_ERR_SSL_WANT_WRITE: LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Got SSL want write in TLS read!"); return 0; case MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS: LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Got async in progress in TLS read!"); return 0; #ifdef MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS case MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS: LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Got crypto in progress in TLS read!"); return 0; #endif default: LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR, "Unexpected response from SSL read: %x", -ret); return -1; } } return ret; } // Local Variables: // mode: C // tab-width: 4 // c-file-style: "bsd" // c-basic-offset: 4 // fill-column: 108 // indent-tabs-mode: nil // End: