forked from squid-cache/squid
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 5363: Handle IP-based X.509 SANs better (squid-cache#1793)
Most X.509 Subject Alternate Name extensions encountered by Squid are based on DNS domain names. However, real-world servers (including publicly available servers that use vanity IP addresses) also use IP-based SANs. Squid mishandled IP-based SANs in several ways: * When generating certificates for servers targeted by their IP addresses, addAltNameWithSubjectCn() used that target IP as a DNS-based SAN, resulting in a frankenstein DNS:[ip] SAN value that clients ignored when validating a Squid-generated certificate. * When validating a received certificate, Squid was ignoring IP-based SANs. When Subject CN did not match the requested IP target, Squid only looked at DNS-based SANs, incorrectly failing validation. * When checking certificate-related ACLs like ssl::server_name, matchX509CommonNames() ignored IP-based SANs, not matching certificates containing ACL-listed IP addresses. Squid now recognizes and generates IP-based SANs. Squid now attempts to match IP-based SANs with ACL-listed IP addresses, but the success of that attempt depends on whether ACL IP parameters are formatted the same way inet_ntop(3) formats those IP addresses: Matching is still done using c-string/domain-based ::matchDomainName() (for ssl::server_name) and string-based regexes (for ssl::server_name_regex). Similar problems affect dstdomain and dstdomain_regex ACLs. A dedicated fix is needed to stop treating IPs as domain names in those contexts. This change introduces partial support for preserving IP-vs-domain distinction in parsed/internal Squid state rather than converting both to a string and then assuming that string is a DNS domain name.
- Loading branch information
1 parent
c8ea6a3
commit 22b2a7a
Showing
21 changed files
with
649 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -521,6 +521,7 @@ Thank you! | |
Tomas Hozza <[email protected]> | ||
tomofumi-yoshida <[email protected]> | ||
Tony Lorimer <[email protected]> | ||
Tony Walker <[email protected]> | ||
trapexit <[email protected]> | ||
Trever Adams <[email protected]> | ||
Tsantilas Christos <[email protected]> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors | ||
* | ||
* Squid software is distributed under GPLv2+ license and includes | ||
* contributions from numerous individuals and organizations. | ||
* Please see the COPYING and CONTRIBUTORS files for details. | ||
*/ | ||
|
||
#include "squid.h" | ||
#include "anyp/Host.h" | ||
|
||
#include <iostream> | ||
|
||
std::optional<AnyP::Host> | ||
AnyP::Host::ParseIp(const Ip::Address &ip) | ||
{ | ||
// any preparsed IP value is acceptable | ||
debugs(23, 7, ip); | ||
return Host(ip); | ||
} | ||
|
||
/// common parts of FromSimpleDomain() and FromWildDomain() | ||
std::optional<AnyP::Host> | ||
AnyP::Host::ParseDomainName(const SBuf &rawName) | ||
{ | ||
if (rawName.isEmpty()) { | ||
debugs(23, 3, "rejecting empty name"); | ||
return std::nullopt; | ||
} | ||
|
||
// Reject bytes incompatible with rfc1035NamePack() and ::matchDomainName() | ||
// implementations (at least). Such bytes can come from percent-encoded HTTP | ||
// URIs or length-based X.509 fields, for example. Higher-level parsers must | ||
// reject or convert domain name encodings like UTF-16, but this low-level | ||
// check works as an additional (albeit unreliable) layer of defense against | ||
// those (unsupported by Squid DNS code) encodings. | ||
if (rawName.find('\0') != SBuf::npos) { | ||
debugs(83, 3, "rejecting ASCII NUL character in " << rawName); | ||
return std::nullopt; | ||
} | ||
|
||
// TODO: Consider rejecting names with isspace(3) bytes. | ||
|
||
debugs(23, 7, rawName); | ||
return Host(rawName); | ||
} | ||
|
||
std::optional<AnyP::Host> | ||
AnyP::Host::ParseSimpleDomainName(const SBuf &rawName) | ||
{ | ||
if (rawName.find('*') != SBuf::npos) { | ||
debugs(23, 3, "rejecting wildcard in " << rawName); | ||
return std::nullopt; | ||
} | ||
return ParseDomainName(rawName); | ||
} | ||
|
||
std::optional<AnyP::Host> | ||
AnyP::Host::ParseWildDomainName(const SBuf &rawName) | ||
{ | ||
const static SBuf wildcardLabel("*."); | ||
if (rawName.startsWith(wildcardLabel)) { | ||
if (rawName.find('*', 2) != SBuf::npos) { | ||
debugs(23, 3, "rejecting excessive wildcards in " << rawName); | ||
return std::nullopt; | ||
} | ||
// else: fall through to final checks | ||
} else { | ||
if (rawName.find('*', 0) != SBuf::npos) { | ||
// this case includes "*" and "example.*" input | ||
debugs(23, 3, "rejecting unsupported wildcard in " << rawName); | ||
return std::nullopt; | ||
} | ||
// else: fall through to final checks | ||
} | ||
return ParseDomainName(rawName); | ||
} | ||
|
||
std::ostream & | ||
AnyP::operator <<(std::ostream &os, const Host &host) | ||
{ | ||
if (const auto ip = host.ip()) { | ||
char buf[MAX_IPSTRLEN]; | ||
(void)ip->toStr(buf, sizeof(buf)); // no brackets | ||
os << buf; | ||
} else { | ||
// If Host object creators start applying Uri::Decode() to reg-names, | ||
// then we must start applying Uri::Encode() here, but only to names | ||
// that require it. See "The reg-name syntax allows percent-encoded | ||
// octets" paragraph in RFC 3986. | ||
const auto domainName = host.domainName(); | ||
Assure(domainName); | ||
os << *domainName; | ||
} | ||
return os; | ||
} | ||
|
||
std::ostream & | ||
AnyP::operator <<(std::ostream &os, const Bracketed &hostWrapper) | ||
{ | ||
bool addBrackets = false; | ||
if (const auto ip = hostWrapper.host.ip()) | ||
addBrackets = ip->isIPv6(); | ||
|
||
if (addBrackets) | ||
os << '['; | ||
os << hostWrapper.host; | ||
if (addBrackets) | ||
os << ']'; | ||
|
||
return os; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors | ||
* | ||
* Squid software is distributed under GPLv2+ license and includes | ||
* contributions from numerous individuals and organizations. | ||
* Please see the COPYING and CONTRIBUTORS files for details. | ||
*/ | ||
|
||
#ifndef SQUID_SRC_ANYP_HOST_H | ||
#define SQUID_SRC_ANYP_HOST_H | ||
|
||
#include "dns/forward.h" | ||
#include "ip/Address.h" | ||
#include "sbuf/SBuf.h" | ||
|
||
#include <iosfwd> | ||
#include <optional> | ||
#include <variant> | ||
|
||
namespace AnyP | ||
{ | ||
|
||
/// either a domain name (as defined in DNS RFC 1034) or an IP address | ||
class Host | ||
{ | ||
public: | ||
/// converts an already parsed IP address to a Host object | ||
static std::optional<Host> ParseIp(const Ip::Address &); | ||
|
||
/// Parses input as a literal ASCII domain name (A-labels OK; see RFC 5890). | ||
/// Does not allow wildcards; \sa ParseWildDomainName(). | ||
static std::optional<Host> ParseSimpleDomainName(const SBuf &); | ||
|
||
/// Same as ParseSimpleDomainName() but allows the first label to be a | ||
/// wildcard (RFC 9525 Section 6.3). | ||
static std::optional<Host> ParseWildDomainName(const SBuf &); | ||
|
||
// Accessor methods below are mutually exclusive: Exactly one method is | ||
// guaranteed to return a result other than std::nullopt. | ||
|
||
/// stored IPv or IPv6 address (if any) | ||
/// | ||
/// Ip::Address::isNoAddr() may be true for the returned address. | ||
/// Ip::Address::isAnyAddr() may be true for the returned address. | ||
auto ip() const { return std::get_if<Ip::Address>(&raw_); } | ||
|
||
/// stored domain name (if any) | ||
auto domainName() const { return std::get_if<SBuf>(&raw_); } | ||
|
||
private: | ||
using Storage = std::variant<Ip::Address, Dns::DomainName>; | ||
|
||
static std::optional<Host> ParseDomainName(const SBuf &); | ||
|
||
// use a Parse*() function to create Host objects | ||
Host(const Storage &raw): raw_(raw) {} | ||
|
||
Storage raw_; ///< the host we are providing access to | ||
}; | ||
|
||
/// helps print Host value in RFC 3986 Section 3.2.2 format, with square | ||
/// brackets around an IPv6 address (if the Host value is an IPv6 address) | ||
class Bracketed | ||
{ | ||
public: | ||
explicit Bracketed(const Host &aHost): host(aHost) {} | ||
const Host &host; | ||
}; | ||
|
||
/// prints Host value _without_ square brackets around an IPv6 address (even | ||
/// when the Host value is an IPv6 address); \sa Bracketed | ||
std::ostream &operator <<(std::ostream &, const Host &); | ||
|
||
/// prints Host value _without_ square brackets around an IPv6 address (even | ||
/// when the Host value is an IPv6 address); \sa Bracketed | ||
std::ostream &operator <<(std::ostream &, const Bracketed &); | ||
|
||
} // namespace Anyp | ||
|
||
#endif /* SQUID_SRC_ANYP_HOST_H */ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.