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

Bug 5363: Handle IP-based X.509 SANs better #1793

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 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
Feb 29, 2024
e24d680
Enable matchX509CommonNames to match on IP
Feb 29, 2024
7b2541e
Declare compare_ip_addresses as static
Apr 28, 2024
d387570
Switch to Ip::Address for IP verification
walkert May 9, 2024
e1d60a9
Add VerifyAddress class for matching IP/Domainname
walkert May 9, 2024
fd1b782
Pass explicit address types to VerifyAddress
May 15, 2024
5d7b0c0
fixup: Undo unwanted out-of-scope formatting change
rousskov Jul 26, 2024
d3377ec
Migrate from (ASN1_STRING, often-ignored type) pairs to GeneralName
rousskov Jul 26, 2024
f97b58e
Simplified GeneralName by dropping support for UnsupportedVariant
rousskov Jul 29, 2024
5960067
fixup: Addressed duplication flagged in previous branch commits
rousskov Jul 29, 2024
ae203d2
fixup: Documented matchX509CommonNames() short-circuit effect
rousskov Jul 29, 2024
aadf227
fixup: Fixed matchX509CommonNames() name to match the new scope
rousskov Jul 29, 2024
79766df
fixup: Addressed recently added branch XXX
rousskov Jul 29, 2024
45861c0
fixup: Formatted branch-modified sources
rousskov Jul 30, 2024
6a04acc
fixup: Fix #include problems detected by source-maintenance.sh
rousskov Jul 30, 2024
bb979a2
fixup: Detailed raw input reporting problems
rousskov Jul 30, 2024
c0cc468
fixup: Addressed critical documentation TODO
rousskov Jul 30, 2024
d051cff
fixup: Documented another IPv6 handling bug
rousskov Jul 30, 2024
d77c89f
fixup: matchDomainName() is case-insensitive
rousskov Jul 30, 2024
f5f8717
fixup: Polished branch-added comments
rousskov Jul 30, 2024
1a5e766
Added Ip::Address::Parse() to reduce IP parsing problems
rousskov Jul 30, 2024
d47612e
Add my work address to CONTRIBUTORS
Jul 31, 2024
ff58c41
Add AnyP::Host to encapsulate domain-vs-IP URI authority info
rousskov Aug 6, 2024
21d5539
fixup: Disassociated Anyp::Host from URI
rousskov Aug 6, 2024
d6cfbef
Reuse AnyP::Host for Ssl::GeneralName, addressing earlier TODO
rousskov Aug 6, 2024
1cd2bcb
fixup: Polished comments and marked problems
rousskov Aug 7, 2024
9fb154f
fixup: Addressed XXX re "treats CN as a domain name"
rousskov Aug 7, 2024
1d58a5b
fixup: Fix "CONNECT <IP>:443 HTTP/1.1" handling
rousskov Aug 7, 2024
a1af897
fixup: Addressed (invalid) branch-added XXX
rousskov Aug 7, 2024
432eba0
fixup: Addressed branch-added ParseAsWildDomainName() duplication XXX
rousskov Aug 8, 2024
7e4f16a
fixup: Clarified source code comment
rousskov Aug 8, 2024
7098ec4
fixup: Polished parsing method names, API
rousskov Aug 8, 2024
bc9c6b9
Do not bracket IPv6 addresses when matching server_name parameters
rousskov Aug 8, 2024
cdc1ca3
fixup: Fixed DomainName namespace
rousskov Aug 9, 2024
0d5e877
fixup: formatted modified sources
rousskov Aug 9, 2024
1223451
fixup: Fix build on some platforms (missing header)
rousskov Aug 9, 2024
2ea18f7
fixup: Fix "make distcheck" in CodeQL-tests (missing header)
rousskov Aug 9, 2024
a8e992a
fixup: Better names for new functions
rousskov Nov 16, 2024
eb650b0
Merged master to get the new set of CI tests
rousskov Nov 16, 2024
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
1 change: 1 addition & 0 deletions src/security/cert_generators/file/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ security_file_certgen_SOURCES = \

security_file_certgen_LDADD = \
$(top_builddir)/src/ssl/libsslutil.la \
$(top_builddir)/src/ip/libip.la \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required to allow gadgets.cc to refer to ip/Address.h.

$(top_builddir)/src/sbuf/libsbuf.la \
$(top_builddir)/src/debug/libdebug.la \
$(top_builddir)/src/error/liberror.la \
Expand Down
12 changes: 10 additions & 2 deletions src/ssl/gadgets.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/

#include <arpa/inet.h>
#include <cstdio>
#include <iostream>
#include "squid.h"
#include "base/IoManip.h"
#include "error/SysErrorDetail.h"
#include "ip/Address.h"
#include "sbuf/Stream.h"
#include "security/Io.h"
#include "ssl/gadgets.h"
Expand Down Expand Up @@ -484,8 +487,13 @@ addAltNameWithSubjectCn(Security::CertPointer &cert)
if (!cn_data)
return false;

Ip::Address ipAddress;
// Extract the CN string from cn_data and use it to determine its type
// using Ip::Address::operator "="
const char* cn_cstr = (const char*) ASN1_STRING_get0_data(cn_data);
const char* altname_type = (ipAddress = cn_cstr) ? "IP" : "DNS";
char dnsName[1024]; // DNS names are limited to 256 characters
const int res = snprintf(dnsName, sizeof(dnsName), "DNS:%*s", cn_data->length, cn_data->data);
const int res = snprintf(dnsName, sizeof(dnsName), "%s:%.*s", altname_type, cn_data->length, cn_data->data);
if (res <= 0 || res >= static_cast<int>(sizeof(dnsName)))
return false;

Expand Down
1 change: 0 additions & 1 deletion src/ssl/gadgets.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,6 @@ bool CertificatesCmp(const Security::CertPointer &cert1, const Security::CertPoi
/// wrapper for OpenSSL X509_get0_signature() which takes care of
/// portability issues with older OpenSSL versions
const ASN1_BIT_STRING *X509_get_signature(const Security::CertPointer &);

} // namespace Ssl

#endif // USE_OPENSSL
Expand Down
123 changes: 96 additions & 27 deletions src/ssl/support.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
*/
#if USE_OPENSSL

#include <openssl/asn1.h>
#include "acl/FilledChecklist.h"
#include "anyp/PortCfg.h"
#include "anyp/Uri.h"
#include <arpa/inet.h>
#include "fatal.h"
#include "fd.h"
#include "fde.h"
#include "globals.h"
#include "ip/Address.h"
#include "ipc/MemMap.h"
#include "security/CertError.h"
#include "security/Certificate.h"
Expand Down Expand Up @@ -55,6 +58,79 @@ std::vector<const char *> Ssl::BumpModeStr = {
/*,"err"*/
};

// Methods for the VerifyAddress class
//
// The public match method can be called with an ASN1_STRING which
// could either be an X509_NAME or GENERAL_NAME
int VerifyAddress::match(ASN1_STRING *asn1_data) {
// declare an empty server var which will be updated later
const char *server = nullptr;
if (ASN1_STRING_type(asn1_data) == V_ASN1_OCTET_STRING) {
// We've been passed an IP address from a GENERAL_NAME struct
// Attempt to safely convert this to a string
const unsigned char *ip = ASN1_STRING_get0_data(asn1_data);
char buffer[INET6_ADDRSTRLEN];
if (ASN1_STRING_length(asn1_data) == 4) {
// IPv4
inet_ntop(AF_INET, ip, buffer, sizeof(buffer));
rousskov marked this conversation as resolved.
Show resolved Hide resolved
} else if (ASN1_STRING_length(asn1_data) == 16) {
// IPv6
inet_ntop(AF_INET6, ip, buffer, sizeof(buffer));
} else {
debugs(83, 4, "Tried to get an IP from ASN1_OCTET_STRING but failed");
return 1;
}
server = strdup(buffer);
rousskov marked this conversation as resolved.
Show resolved Hide resolved
} else {
// Otherwise, assume we have the cn_data from a typical ASN1_STRING
// This could be a domainname or IP address
server = (const char *)asn1_data->data;
rousskov marked this conversation as resolved.
Show resolved Hide resolved
}
// Attempt to convert server into an IP and use matchIP if it's valid
Ip::Address ipAddress;
if (ipAddress = server) {
debugs(83, 4, "Verifying server IP " << private_check_data << " to certificate name/subjectAltName " << server);
return matchIp(ipAddress);
}
// Otherwise, match the original address as a domain name
return matchDomainNameData(asn1_data);
}

int VerifyAddress::matchDomainNameData(ASN1_STRING *cn_data) {
char cn[1024];
walkert marked this conversation as resolved.
Show resolved Hide resolved

if (cn_data->length == 0)
return 1; // zero length cn, ignore

if (cn_data->length > (int)sizeof(cn) - 1)
return 1; //if does not fit our buffer just ignore

char *s = reinterpret_cast<char*>(cn_data->data);
char *d = cn;
for (int i = 0; i < cn_data->length; ++i, ++d, ++s) {
if (*s == '\0')
return 1; // always a domain mismatch. contains 0x00
*d = *s;
}
cn[cn_data->length] = '\0';
debugs(83, 4, "Verifying server domain " << private_check_data << " to certificate name/subjectAltName " << cn);
return matchDomainName(private_check_data, (cn[0] == '*' ? cn + 1 : cn), mdnRejectSubsubDomains);
}

int VerifyAddress::matchIp(Ip::Address &iaddr) {
// Attempt to convert private_check_data to an IP address
// and compare it to iaddr
Ip::Address check_ip;
if ((check_ip = private_check_data) && (iaddr == check_ip)) {
return 0;
}
return 1;
}

const char* VerifyAddress::processCheckData(void *check_data) {
return reinterpret_cast<const char*>(check_data);
}

/**
\defgroup ServerProtocolSSLInternal Server-Side SSL Internals
\ingroup ServerProtocolSSLAPI
Expand Down Expand Up @@ -213,14 +289,24 @@ int Ssl::matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_fun
int numalts = sk_GENERAL_NAME_num(altnames);
rousskov marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < numalts; ++i) {
const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i);
if (check->type != GEN_DNS) {
continue;
}
ASN1_STRING *cn_data = check->d.dNSName;

if ( (*check_func)(check_data, cn_data) == 0) {
sk_GENERAL_NAME_pop_free(altnames, GENERAL_NAME_free);
return 1;
switch(check->type) {
case GEN_DNS:
// If the type is GEN_DNS, call check_func with the dNSName data
if ( (*check_func)(check_data, check->d.dNSName) == 0) {
sk_GENERAL_NAME_pop_free(altnames, GENERAL_NAME_free);
return 1;
}
break;
case GEN_IPADD:
debugs(83, 4, "Check type is GEN_IPADD, verifying...");
// If it's an IP address, call check_func with the iPAddress data
if ( (*check_func)(check_data, check->d.iPAddress) == 0) {
rousskov marked this conversation as resolved.
Show resolved Hide resolved
sk_GENERAL_NAME_pop_free(altnames, GENERAL_NAME_free);
return 1;
rousskov marked this conversation as resolved.
Show resolved Hide resolved
}
break;
default:
continue;
}
}
sk_GENERAL_NAME_pop_free(altnames, GENERAL_NAME_free);
Expand All @@ -230,25 +316,8 @@ int Ssl::matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_fun

static int check_domain( void *check_data, ASN1_STRING *cn_data)
{
char cn[1024];
const char *server = (const char *)check_data;

if (cn_data->length == 0)
return 1; // zero length cn, ignore

if (cn_data->length > (int)sizeof(cn) - 1)
return 1; //if does not fit our buffer just ignore

char *s = reinterpret_cast<char*>(cn_data->data);
char *d = cn;
for (int i = 0; i < cn_data->length; ++i, ++d, ++s) {
if (*s == '\0')
return 1; // always a domain mismatch. contains 0x00
*d = *s;
}
cn[cn_data->length] = '\0';
debugs(83, 4, "Verifying server domain " << server << " to certificate name/subjectAltName " << cn);
return matchDomainName(server, (cn[0] == '*' ? cn + 1 : cn), mdnRejectSubsubDomains);
VerifyAddress verify(check_data);
return verify.match(cn_data);
}

bool Ssl::checkX509ServerValidity(X509 *cert, const char *server)
Expand Down
16 changes: 16 additions & 0 deletions src/ssl/support.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "base/CbDataList.h"
#include "comm/forward.h"
#include "compat/openssl.h"
#include "ip/Address.h"
#include "sbuf/SBuf.h"
#include "security/Session.h"
#include "ssl/gadgets.h"
Expand Down Expand Up @@ -366,6 +367,21 @@ class VerifyCallbackParameters {

} //namespace Ssl

class VerifyAddress {
public:
// Constructor
VerifyAddress(void *check_data)
: private_check_data(processCheckData(check_data))
{}
int match(ASN1_STRING *cn_data);
private:
int matchDomainNameData(ASN1_STRING *cn_data);
int matchIp(Ip::Address &iaddr);

const char* processCheckData(void *check_data);
const char* private_check_data;
};

#if _SQUID_WINDOWS_

#if defined(__cplusplus)
Expand Down