Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and fixes for URI type #4418

Merged
merged 1 commit into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/fuzzer/uri.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

#include <botan/internal/uri.h>

void fuzz(std::span<const uint8_t> in) {
if(in.size() > max_fuzzer_input_size) {
void fuzz(std::span<const uint8_t> input) {
if(input.size() > max_fuzzer_input_size) {
return;
}

try {
Botan::URI::fromAny(std::string(reinterpret_cast<const char*>(in.data()), in.size()));
Botan::URI::from_any(std::string(reinterpret_cast<const char*>(input.data()), input.size()));
} catch(Botan::Exception& e) {}
}
59 changes: 59 additions & 0 deletions src/lib/utils/parsing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,63 @@ bool host_wildcard_match(std::string_view issued_, std::string_view host_) {
return true;
}

std::string check_and_canonicalize_dns_name(std::string_view name) {
if(name.size() > 255) {
throw Decoding_Error("DNS name exceeds maximum allowed length");
}

if(name.empty()) {
throw Decoding_Error("DNS name cannot be empty");
}

if(name.starts_with(".")) {
throw Decoding_Error("DNS name cannot start with a dot");
}

/*
* Table mapping uppercase to lowercase and only including values for valid DNS names
* namely A-Z, a-z, 0-9, hypen, and dot, plus '*' for wildcarding.
*/
// clang-format off
constexpr uint8_t DNS_CHAR_MAPPING[128] = {
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
'\0', '\0', '\0', '\0', '*', '\0', '\0', '-', '.', '\0', '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', '\0', '\0', '\0', '\0', '\0', '\0', '\0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\0', '\0', '\0', '\0',
'\0', '\0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\0', '\0', '\0', '\0', '\0',
};
// clang-format on

std::string canon;
canon.reserve(name.size());

for(size_t i = 0; i != name.size(); ++i) {
char c = name[i];

if(c == '.') {
if(name[i - 1] == '.') {
throw Decoding_Error("DNS name contains sequential period chars");
}
if(i == name.size() - 1) {
throw Decoding_Error("DNS name cannot end in a period");
}
}

const uint8_t cu = static_cast<uint8_t>(c);
if(cu >= 128) {
throw Decoding_Error("DNS name must not contain any extended ASCII code points");
}
const uint8_t mapped = DNS_CHAR_MAPPING[cu];
if(mapped == 0) {
throw Decoding_Error("DNS name includes invalid character");
}
// TODO check label lengths
canon.push_back(static_cast<char>(mapped));
}

return canon;
}

} // namespace Botan
7 changes: 7 additions & 0 deletions src/lib/utils/parsing.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ std::string tolower_string(std::string_view s);
BOTAN_TEST_API
bool host_wildcard_match(std::string_view wildcard, std::string_view host);

/**
* If name is a valid DNS name, return it canonicalized
*
* Otherwise throws Decoding_Error
*/
std::string check_and_canonicalize_dns_name(std::string_view name);

} // namespace Botan

#endif
6 changes: 3 additions & 3 deletions src/lib/utils/socket/socket_udp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,11 +331,11 @@ std::unique_ptr<OS::SocketUDP> OS::open_socket_udp(std::string_view hostname,
}

std::unique_ptr<OS::SocketUDP> OS::open_socket_udp(std::string_view uri_string, std::chrono::microseconds timeout) {
const auto uri = URI::fromAny(uri_string);
if(uri.port == 0) {
const auto uri = URI::from_any(uri_string);
if(uri.port() == 0) {
throw Invalid_Argument("UDP port not specified");
}
return open_socket_udp(uri.host, std::to_string(uri.port), timeout);
return open_socket_udp(uri.host(), std::to_string(uri.port()), timeout);
}

} // namespace Botan
186 changes: 87 additions & 99 deletions src/lib/utils/socket/uri.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
/*
* (C) 2019 Nuno Goncalves <[email protected]>
* 2023,2024 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include <botan/internal/uri.h>

#include <botan/exceptn.h>

#include <regex>
#include <botan/internal/fmt.h>
#include <botan/internal/parsing.h>

#if defined(BOTAN_TARGET_OS_HAS_SOCKETS)
#include <arpa/inet.h>
Expand All @@ -20,146 +21,133 @@

#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2)

namespace {
namespace Botan {

constexpr bool isdigit(char ch) {
return ch >= '0' && ch <= '9';
}
namespace {

bool isDomain(std::string_view domain) {
std::string domain_str(domain);
std::regex re(
R"(^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$)");
std::cmatch m;
return std::regex_match(domain_str.c_str(), m, re);
bool is_domain_name(std::string_view domain) {
try {
check_and_canonicalize_dns_name(domain);
return true;
} catch(Decoding_Error&) {
return false;
}
}

bool isIPv4(std::string_view ip) {
bool is_ipv4(std::string_view ip) {
std::string ip_str(ip);
sockaddr_storage inaddr;
return !!inet_pton(AF_INET, ip_str.c_str(), &inaddr);
}

bool isIPv6(std::string_view ip) {
bool is_ipv6(std::string_view ip) {
std::string ip_str(ip);
sockaddr_storage in6addr;
return !!inet_pton(AF_INET6, ip_str.c_str(), &in6addr);
}

uint16_t parse_port_number(const char* func_name, std::string_view uri, size_t pos) {
if(pos == std::string::npos || uri.empty()) {
return 0;
}

BOTAN_ARG_CHECK(pos < uri.size(), "URI invalid port specifier");

uint32_t port = 0;

for(char c : uri.substr(pos + 1)) {
size_t digit = c - '0';
if(digit >= 10) {
throw Invalid_Argument(fmt("URI::{} invalid port field in {}", func_name, uri));
}
port = port * 10 + (c - '0');
if(port > 65535) {
throw Invalid_Argument(fmt("URI::{} invalid port field in {}", func_name, uri));
}
}

return static_cast<uint16_t>(port);
}

} // namespace

namespace Botan {
URI URI::from_domain(std::string_view uri) {
BOTAN_ARG_CHECK(!uri.empty(), "URI::from_domain empty URI is invalid");

URI URI::fromDomain(std::string_view uri) {
unsigned port = 0;
uint16_t port = 0;
const auto port_pos = uri.find(':');
if(port_pos != std::string::npos) {
for(char c : uri.substr(port_pos + 1)) {
if(!isdigit(c)) {
throw Invalid_Argument("invalid");
}
port = port * 10 + c - '0';
if(port > 65535) {
throw Invalid_Argument("invalid");
}
}
port = parse_port_number("from_domain", uri, port_pos);
}
const auto domain = uri.substr(0, port_pos);
if(isIPv4(domain)) {
throw Invalid_Argument("invalid");
if(is_ipv4(domain)) {
throw Invalid_Argument("URI::from_domain domain name should not be IP address");
}
if(!isDomain(domain)) {
throw Invalid_Argument("invalid");
if(!is_domain_name(domain)) {
throw Invalid_Argument(fmt("URI::from_domain domain name '{}' not valid", domain));
}
return {Type::Domain, domain, uint16_t(port)};

return URI(Type::Domain, domain, port);
}

URI URI::fromIPv4(std::string_view uri) {
unsigned port = 0;
URI URI::from_ipv4(std::string_view uri) {
BOTAN_ARG_CHECK(!uri.empty(), "URI::from_ipv4 empty URI is invalid");

const auto port_pos = uri.find(':');
if(port_pos != std::string::npos) {
for(char c : uri.substr(port_pos + 1)) {
if(!isdigit(c)) {
throw Invalid_Argument("invalid");
}
port = port * 10 + c - '0';
if(port > 65535) {
throw Invalid_Argument("invalid");
}
}
}
const uint16_t port = parse_port_number("from_ipv4", uri, port_pos);
const auto ip = uri.substr(0, port_pos);
if(!isIPv4(ip)) {
throw Invalid_Argument("invalid");
if(!is_ipv4(ip)) {
throw Invalid_Argument("URI::from_ipv4: Invalid IPv4 specifier");
}
return {Type::IPv4, ip, uint16_t(port)};
return URI(Type::IPv4, ip, port);
}

URI URI::fromIPv6(std::string_view uri) {
unsigned port = 0;
URI URI::from_ipv6(std::string_view uri) {
BOTAN_ARG_CHECK(!uri.empty(), "URI::from_ipv6 empty URI is invalid");

const auto port_pos = uri.find(']');
const bool with_braces = (port_pos != std::string::npos);
if((uri[0] == '[') != with_braces) {
throw Invalid_Argument("invalid");
throw Invalid_Argument("URI::from_ipv6 Invalid IPv6 address with mismatch braces");
}

uint16_t port = 0;
if(with_braces && (uri.size() > port_pos + 1)) {
if(uri[port_pos + 1] != ':') {
throw Invalid_Argument("invalid");
}
for(char c : uri.substr(port_pos + 2)) {
if(!isdigit(c)) {
throw Invalid_Argument("invalid");
}
port = port * 10 + c - '0';
if(port > 65535) {
throw Invalid_Argument("invalid");
}
throw Invalid_Argument("URI::from_ipv6 Invalid IPv6 address");
}

port = parse_port_number("from_ipv6", uri, port_pos + 1);
}
const auto ip = uri.substr((with_braces ? 1 : 0), port_pos - with_braces);
if(!isIPv6(ip)) {
throw Invalid_Argument("invalid");
if(!is_ipv6(ip)) {
throw Invalid_Argument("URI::from_ipv6 URI has invalid IPv6 address");
}
return {Type::IPv6, ip, uint16_t(port)};
return URI(Type::IPv6, ip, port);
}

URI URI::fromAny(std::string_view uri) {
bool colon_seen = false;
bool non_number = false;
if(uri[0] == '[') {
return fromIPv6(uri);
}
for(auto c : uri) {
if(c == ':') {
if(colon_seen) //seen two ':'
{
return fromIPv6(uri);
}
colon_seen = true;
} else if(!isdigit(c) && c != '.') {
non_number = true;
}
}
if(!non_number) {
if(isIPv4(uri.substr(0, uri.find(':')))) {
return fromIPv4(uri);
}
}
return fromDomain(uri);
URI URI::from_any(std::string_view uri) {
BOTAN_ARG_CHECK(!uri.empty(), "URI::from_any empty URI is invalid");

try {
return URI::from_ipv4(uri);
} catch(Invalid_Argument&) {}

try {
return URI::from_ipv6(uri);
} catch(Invalid_Argument&) {}

return URI::from_domain(uri);
}

std::string URI::to_string() const {
if(type == Type::NotSet) {
throw Invalid_Argument("not set");
}

if(port != 0) {
if(type == Type::IPv6) {
return "[" + host + "]:" + std::to_string(port);
if(m_port != 0) {
if(m_type == Type::IPv6) {
return "[" + m_host + "]:" + std::to_string(m_port);
}
return host + ":" + std::to_string(port);
return m_host + ":" + std::to_string(m_port);
}
return host;
return m_host;
}

} // namespace Botan
Expand All @@ -168,19 +156,19 @@ std::string URI::to_string() const {

namespace Botan {

URI URI::fromDomain(std::string_view) {
URI URI::from_domain(std::string_view) {
throw Not_Implemented("No socket support enabled in build");
}

URI URI::fromIPv4(std::string_view) {
URI URI::from_ipv4(std::string_view) {
throw Not_Implemented("No socket support enabled in build");
}

URI URI::fromIPv6(std::string_view) {
URI URI::from_ipv6(std::string_view) {
throw Not_Implemented("No socket support enabled in build");
}

URI URI::fromAny(std::string_view) {
URI URI::from_any(std::string_view) {
throw Not_Implemented("No socket support enabled in build");
}

Expand Down
Loading
Loading