Add TLSA support to DNSSEC fetching

This commit is contained in:
Lee Clagett 2020-12-06 16:59:08 -05:00
parent 7ca4ef0d74
commit 386ef03be3
7 changed files with 178 additions and 12 deletions

View file

@ -37,6 +37,7 @@
#include <boost/thread/mutex.hpp> #include <boost/thread/mutex.hpp>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
#include <boost/optional.hpp> #include <boost/optional.hpp>
#include <boost/utility/string_ref.hpp>
using namespace epee; using namespace epee;
#undef MONERO_DEFAULT_LOG_CATEGORY #undef MONERO_DEFAULT_LOG_CATEGORY
@ -124,6 +125,7 @@ static const char *get_record_name(int record_type)
case DNS_TYPE_A: return "A"; case DNS_TYPE_A: return "A";
case DNS_TYPE_TXT: return "TXT"; case DNS_TYPE_TXT: return "TXT";
case DNS_TYPE_AAAA: return "AAAA"; case DNS_TYPE_AAAA: return "AAAA";
case DNS_TYPE_TLSA: return "TLSA";
default: return "unknown"; default: return "unknown";
} }
} }
@ -186,6 +188,13 @@ boost::optional<std::string> txt_to_string(const char* src, size_t len)
return std::string(src+1, len-1); return std::string(src+1, len-1);
} }
boost::optional<std::string> tlsa_to_string(const char* src, size_t len)
{
if (len < 4)
return boost::none;
return std::string(src, len);
}
// custom smart pointer. // custom smart pointer.
// TODO: see if std::auto_ptr and the like support custom destructors // TODO: see if std::auto_ptr and the like support custom destructors
template<typename type, void (*freefunc)(type*)> template<typename type, void (*freefunc)(type*)>
@ -326,11 +335,15 @@ std::vector<std::string> DNSResolver::get_record(const std::string& url, int rec
// destructor takes care of cleanup // destructor takes care of cleanup
ub_result_ptr result; ub_result_ptr result;
MDEBUG("Performing DNSSEC " << get_record_name(record_type) << " record query for " << url);
// call DNS resolver, blocking. if return value not zero, something went wrong // call DNS resolver, blocking. if return value not zero, something went wrong
if (!ub_resolve(m_data->m_ub_context, string_copy(url.c_str()), record_type, DNS_CLASS_IN, &result)) if (!ub_resolve(m_data->m_ub_context, string_copy(url.c_str()), record_type, DNS_CLASS_IN, &result))
{ {
dnssec_available = (result->secure || result->bogus); dnssec_available = (result->secure || result->bogus);
dnssec_valid = result->secure && !result->bogus; dnssec_valid = result->secure && !result->bogus;
if (dnssec_available && !dnssec_valid)
MWARNING("Invalid DNSSEC " << get_record_name(record_type) << " record signature for " << url << ": " << result->why_bogus);
if (result->havedata) if (result->havedata)
{ {
for (size_t i=0; result->data[i] != NULL; i++) for (size_t i=0; result->data[i] != NULL; i++)
@ -338,8 +351,9 @@ std::vector<std::string> DNSResolver::get_record(const std::string& url, int rec
boost::optional<std::string> res = (*reader)(result->data[i], result->len[i]); boost::optional<std::string> res = (*reader)(result->data[i], result->len[i]);
if (res) if (res)
{ {
MINFO("Found \"" << *res << "\" in " << get_record_name(record_type) << " record for " << url); // do not dump dns record directly from dns into log
addresses.push_back(*res); MINFO("Found " << get_record_name(record_type) << " record for " << url);
addresses.push_back(std::move(*res));
} }
} }
} }
@ -363,6 +377,17 @@ std::vector<std::string> DNSResolver::get_txt_record(const std::string& url, boo
return get_record(url, DNS_TYPE_TXT, txt_to_string, dnssec_available, dnssec_valid); return get_record(url, DNS_TYPE_TXT, txt_to_string, dnssec_available, dnssec_valid);
} }
std::vector<std::string> DNSResolver::get_tlsa_tcp_record(const boost::string_ref url, const boost::string_ref port, bool& dnssec_available, bool& dnssec_valid)
{
std::string service_addr;
service_addr.reserve(url.size() + port.size() + 7);
service_addr.push_back('_');
service_addr.append(port.data(), port.size());
service_addr.append("._tcp.");
service_addr.append(url.data(), url.size());
return get_record(service_addr, DNS_TYPE_TLSA, tlsa_to_string, dnssec_available, dnssec_valid);
}
std::string DNSResolver::get_dns_format_from_oa_address(const std::string& oa_addr) std::string DNSResolver::get_dns_format_from_oa_address(const std::string& oa_addr)
{ {
std::string addr(oa_addr); std::string addr(oa_addr);

View file

@ -31,15 +31,17 @@
#include <string> #include <string>
#include <functional> #include <functional>
#include <boost/optional/optional_fwd.hpp> #include <boost/optional/optional_fwd.hpp>
#include <boost/utility/string_ref_fwd.hpp>
namespace tools namespace tools
{ {
// RFC defines for record types and classes for DNS, gleaned from ldns source // RFC defines for record types and classes for DNS, gleaned from ldns source
const static int DNS_CLASS_IN = 1; constexpr const int DNS_CLASS_IN = 1;
const static int DNS_TYPE_A = 1; constexpr const int DNS_TYPE_A = 1;
const static int DNS_TYPE_TXT = 16; constexpr const int DNS_TYPE_TXT = 16;
const static int DNS_TYPE_AAAA = 8; constexpr const int DNS_TYPE_AAAA = 8;
constexpr const int DNS_TYPE_TLSA = 52;
struct DNSResolverData; struct DNSResolverData;
@ -105,6 +107,17 @@ public:
// TODO: modify this to accommodate DNSSEC // TODO: modify this to accommodate DNSSEC
std::vector<std::string> get_txt_record(const std::string& url, bool& dnssec_available, bool& dnssec_valid); std::vector<std::string> get_txt_record(const std::string& url, bool& dnssec_available, bool& dnssec_valid);
/**
* @brief gets all TLSA TCP records from a DNS query for the supplied URL;
* if no TLSA record present returns an empty vector.
*
* @param url A string containing a URL to query for
* @param port The service port number (as string) to query
*
* @return A vector of strings containing all TLSA records; or an empty vector
*/
std::vector<std::string> get_tlsa_tcp_record(boost::string_ref url, boost::string_ref port, bool& dnssec_available, bool& dnssec_valid);
/** /**
* @brief Gets a DNS address from OpenAlias format * @brief Gets a DNS address from OpenAlias format
* *

View file

@ -26,10 +26,10 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(net_sources dandelionpp.cpp error.cpp http.cpp i2p_address.cpp parse.cpp socks.cpp set(net_sources dandelionpp.cpp error.cpp http.cpp i2p_address.cpp parse.cpp resolve.cpp
socks_connect.cpp tor_address.cpp zmq.cpp) socks.cpp socks_connect.cpp tor_address.cpp zmq.cpp)
set(net_headers dandelionpp.h error.h http.cpp i2p_address.h parse.h socks.h socks_connect.h set(net_headers dandelionpp.h error.h http.cpp i2p_address.h parse.h socks.h resolve.h
tor_address.h zmq.h) socks_connect.h tor_address.h zmq.h)
monero_add_library(net ${net_sources} ${net_headers}) monero_add_library(net ${net_sources} ${net_headers})
target_link_libraries(net common epee ${ZMQ_LIB} ${Boost_ASIO_LIBRARY}) target_link_libraries(net common epee ${ZMQ_LIB} ${Boost_ASIO_LIBRARY})

View file

@ -47,12 +47,18 @@ namespace
{ {
switch (net::error(value)) switch (net::error(value))
{ {
case net::error::bogus_dnssec:
return "Invalid response signature from DNSSEC enabled domain";
case net::error::dns_query_failure:
return "Failed to retrieve desired DNS record";
case net::error::expected_tld: case net::error::expected_tld:
return "Expected top-level domain"; return "Expected top-level domain";
case net::error::invalid_host: case net::error::invalid_host:
return "Host value is not valid"; return "Host value is not valid";
case net::error::invalid_i2p_address: case net::error::invalid_i2p_address:
return "Invalid I2P address"; return "Invalid I2P address";
case net::error::invalid_mask:
return "CIDR netmask outside of 0-32 range";
case net::error::invalid_port: case net::error::invalid_port:
return "Invalid port value (expected 0-65535)"; return "Invalid port value (expected 0-65535)";
case net::error::invalid_tor_address: case net::error::invalid_tor_address:
@ -71,6 +77,7 @@ namespace
switch (net::error(value)) switch (net::error(value))
{ {
case net::error::invalid_port: case net::error::invalid_port:
case net::error::invalid_mask:
return std::errc::result_out_of_range; return std::errc::result_out_of_range;
case net::error::expected_tld: case net::error::expected_tld:
case net::error::invalid_tor_address: case net::error::invalid_tor_address:

View file

@ -37,13 +37,16 @@ namespace net
enum class error : int enum class error : int
{ {
// 0 reserved for success (as per expect<T>) // 0 reserved for success (as per expect<T>)
expected_tld = 1, //!< Expected a tld bogus_dnssec = 1, //!< Invalid response signature from DNSSEC enabled domain
dns_query_failure, //!< Failed to retrieve desired DNS record
expected_tld, //!< Expected a tld
invalid_host, //!< Hostname is not valid invalid_host, //!< Hostname is not valid
invalid_i2p_address, invalid_i2p_address,
invalid_mask, //!< Outside of 0-32 range
invalid_port, //!< Outside of 0-65535 range invalid_port, //!< Outside of 0-65535 range
invalid_tor_address,//!< Invalid base32 or length invalid_tor_address,//!< Invalid base32 or length
unsupported_address,//!< Type not supported by `get_network_address` unsupported_address,//!< Type not supported by `get_network_address`
invalid_mask, //!< Outside of 0-32 range
}; };
//! \return `std::error_category` for `net` namespace. //! \return `std::error_category` for `net` namespace.

71
src/net/resolve.cpp Normal file
View file

@ -0,0 +1,71 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "net/resolve.h"
#include <boost/utility/string_ref.hpp>
#include "common/dns_utils.h"
#include "common/expect.h"
#include "net/error.h"
namespace net
{
namespace dnssec
{
expect<service_response> resolve_hostname(const std::string& addr, const std::string& tlsa_port)
{
// use basic (blocking) unbound for now, possibly refactor later
tools::DNSResolver& resolver = tools::DNSResolver::instance();
bool dnssec_available = false;
bool dnssec_valid = false;
std::vector<std::string> ip_records = resolver.get_ipv4(addr, dnssec_available, dnssec_valid);
if (dnssec_available && !dnssec_valid)
return {net::error::bogus_dnssec};
if (ip_records.empty())
{
ip_records = resolver.get_ipv6(addr, dnssec_available, dnssec_valid);
if (dnssec_available && !dnssec_valid)
return {net::error::bogus_dnssec};
if (ip_records.empty())
return {net::error::dns_query_failure};
}
std::vector<std::string> tlsa{};
if (dnssec_available && !tlsa_port.empty())
{
tlsa = resolver.get_tlsa_tcp_record(addr, tlsa_port, dnssec_available, dnssec_valid);
if (!dnssec_valid)
return {net::error::bogus_dnssec};
}
return {{std::move(ip_records), std::move(tlsa)}};
}
} // dnssec
} // net

47
src/net/resolve.h Normal file
View file

@ -0,0 +1,47 @@
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <string>
#include <vector>
template<typename> class expect;
namespace net
{
namespace dnssec
{
struct service_response
{
std::vector<std::string> ip; //!< IPv4/6 records in dotted or semicolon notation
std::vector<std::string> tlsa; //!< DANE/TLSA records
};
//! \return IP + (optionally) DANE/TLSA records, failing if DNSSEC signature is "bogus"
expect<service_response> resolve_hostname(const std::string& addr, const std::string& tlsa_port = {});
} // dnssec
} // net