-
Notifications
You must be signed in to change notification settings - Fork 528
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
Bug 5363: Handle IP-based X.509 SANs better #1793
Closed
Closed
Changes from all commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
9724436
Ensure bumped SSL certs get IP SANs if available
e24d680
Enable matchX509CommonNames to match on IP
7b2541e
Declare compare_ip_addresses as static
d387570
Switch to Ip::Address for IP verification
walkert e1d60a9
Add VerifyAddress class for matching IP/Domainname
walkert fd1b782
Pass explicit address types to VerifyAddress
5d7b0c0
fixup: Undo unwanted out-of-scope formatting change
rousskov d3377ec
Migrate from (ASN1_STRING, often-ignored type) pairs to GeneralName
rousskov f97b58e
Simplified GeneralName by dropping support for UnsupportedVariant
rousskov 5960067
fixup: Addressed duplication flagged in previous branch commits
rousskov ae203d2
fixup: Documented matchX509CommonNames() short-circuit effect
rousskov aadf227
fixup: Fixed matchX509CommonNames() name to match the new scope
rousskov 79766df
fixup: Addressed recently added branch XXX
rousskov 45861c0
fixup: Formatted branch-modified sources
rousskov 6a04acc
fixup: Fix #include problems detected by source-maintenance.sh
rousskov bb979a2
fixup: Detailed raw input reporting problems
rousskov c0cc468
fixup: Addressed critical documentation TODO
rousskov d051cff
fixup: Documented another IPv6 handling bug
rousskov d77c89f
fixup: matchDomainName() is case-insensitive
rousskov f5f8717
fixup: Polished branch-added comments
rousskov 1a5e766
Added Ip::Address::Parse() to reduce IP parsing problems
rousskov d47612e
Add my work address to CONTRIBUTORS
ff58c41
Add AnyP::Host to encapsulate domain-vs-IP URI authority info
rousskov 21d5539
fixup: Disassociated Anyp::Host from URI
rousskov d6cfbef
Reuse AnyP::Host for Ssl::GeneralName, addressing earlier TODO
rousskov 1cd2bcb
fixup: Polished comments and marked problems
rousskov 9fb154f
fixup: Addressed XXX re "treats CN as a domain name"
rousskov 1d58a5b
fixup: Fix "CONNECT <IP>:443 HTTP/1.1" handling
rousskov a1af897
fixup: Addressed (invalid) branch-added XXX
rousskov 432eba0
fixup: Addressed branch-added ParseAsWildDomainName() duplication XXX
rousskov 7e4f16a
fixup: Clarified source code comment
rousskov 7098ec4
fixup: Polished parsing method names, API
rousskov bc9c6b9
Do not bracket IPv6 addresses when matching server_name parameters
rousskov cdc1ca3
fixup: Fixed DomainName namespace
rousskov 0d5e877
fixup: formatted modified sources
rousskov 1223451
fixup: Fix build on some platforms (missing header)
rousskov 2ea18f7
fixup: Fix "make distcheck" in CodeQL-tests (missing header)
rousskov a8e992a
fixup: Better names for new functions
rousskov eb650b0
Merged master to get the new set of CI tests
rousskov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is at the core of this PR changes. In official code, a single address that may be either an IP address or a DNS domain name is either isolated into two or more co-existing objects (e.g., host information inside AnyP::Uri) or morphed into one opaque SBuf object (e.g.,
server
in ssl_verify_cb()). Neither approach works well, for obvious reasons. This PR uses the new Host class to accurately and as-safely-as-possible represent this naming duality in several X.509 SAN-related contexts. Eventually, more code should be converted to use this class. I have left a few related TODOs.The proposed "host" name works well enough IMO, but I would not be surprised if there are better alternatives our there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @rousskov - do you know if there's anything else that needs to be done for the review to continue? Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@walkert, unfortunately, the answer to your question is "no": I do not know how to convince @yadij to review PRs awaiting his review. IMO, this persistent problem is one of the existential threats faced by the Squid Project, but all my attempts to find a solution have failed. I can only hope that we will resume making progress with this PR soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rousskov understood. Thanks for taking the time to respond. I'll keep watching and hoping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @rousskov, is there scope to hand this off to another reviewer if @yadij is unavailable? Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@walkert, there is no formal procedure to overwrite, move, or clear a "this PR needs to be reviewed by me" flag set by a core developer. Project core developers hold a lot of power, and multiple attempts to establish timeouts (or otherwise restructure the governing rules) have failed so far. FWIW, I plan to bring this PR up again during the next meeting which should happen on October 22, 2024.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rousskov understood. Thanks for the info. 🙏