Skip to content

Commit

Permalink
Merge pull request #710 from bbockelm/allow_sans
Browse files Browse the repository at this point in the history
Allow XRootD client to accept subjectAltNames.
  • Loading branch information
gganis committed Jun 4, 2018
2 parents b7bad34 + 47eb688 commit 9a78c14
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 11 deletions.
47 changes: 47 additions & 0 deletions src/XrdCrypto/XrdCryptoX509.cc
Expand Up @@ -252,3 +252,50 @@ int XrdCryptoX509::DumpExtensions(bool)
ABSTRACTMETHOD("XrdCryptoX509::DumpExtensions");
return -1;
}

//_____________________________________________________________________________
bool XrdCryptoX509::MatchHostnames(const char * match_pattern, const char * hostname)
{
// Compare two hostnames and see if they are the same, including wildcards.
//
// For example,
//
// - foo.example.com and foo.example.com are considered equal.
// - bar.example.com and foo.example.com are not equal.
// - *.example.com and foo.example.com are equal.
// - *.example.com and foo.bar.example.com are NOT equal (wildcard applies to a single label).
// - FOO.example.com and foo.EXAMPLE.COM are equal (comparison is not case sensitive).
// - F*.com and foo.com are equal
//
// Returns true if the hostnames are considered a match

XrdOucString mpatt(match_pattern), hname(hostname);

// Not empty
if (!mpatt.length() || !hname.length()) return false;

// Create a lowercase copy of both hostnames
mpatt.lower(0);
hname.lower(0);

// Are they equal?
if (mpatt == hname) return true;

bool theydomatch = false;

// Get first token of both strings
int mfrom = -1, hfrom = -1;
XrdOucString mfirst, hfirst;
if (((mfrom = mpatt.tokenize(mfirst, mfrom, '.')) != -1) &&
((hfrom = hname.tokenize(hfirst, hfrom, '.')) != -1)) {
if (hfirst.matches(mfirst.c_str())) {
// First tokens matches, the rest should match without wildcards
mpatt.erasefromstart(mfrom);
hname.erasefromstart(hfrom);
if ((hname == mpatt) ||
(!hname.length() && !mpatt.length())) theydomatch = true;
}
}

return theydomatch;
}
11 changes: 11 additions & 0 deletions src/XrdCrypto/XrdCryptoX509.hh
Expand Up @@ -103,12 +103,23 @@ public:
virtual const char *SubjectHash(int); // hash
const char *SubjectHash() { return SubjectHash(0); } // hash

// Returns true if the certificate has a subject alt name which matches
// the given hostnem.
virtual bool MatchesSAN(const char * fqdn) = 0;

// Retrieve a given extension if there (in opaque form)
virtual XrdCryptoX509data GetExtension(const char *oid);

// Verify signature
virtual bool Verify(XrdCryptoX509 *ref);

// Compare two hostnames, handling wildcards as appropriate. Necessary
// for support for accepting connections where the remote X509 certificate
// is a wildcard certificate.
//
// Returns true if the FQDN matches the specified pattern
static bool MatchHostnames(const char *match_pattern, const char *fqdn);

private:

static const char *ctype[4]; // Names of types
Expand Down
49 changes: 49 additions & 0 deletions src/XrdCrypto/XrdCryptosslX509.cc
Expand Up @@ -1091,3 +1091,52 @@ int XrdCryptosslX509::Asn1PrintInfo(int tag, int xclass, int constructed, int in
BIO_free(bp);
return(0);
}

//____________________________________________________________________________
bool XrdCryptosslX509::MatchesSAN(const char *fqdn)
{
EPNAME("MatchesSAN");

// Statically allocated array for hostname lengths. RFC1035 limits
// valid lengths to 255 characters.
char san_fqdn[256];

if (!fqdn)
return false;

// Only an EEC is usable as a host certificate.
if (type != kEEC)
return false;

GENERAL_NAMES *gens = static_cast<GENERAL_NAMES *>(X509_get_ext_d2i(cert,
NID_subject_alt_name, NULL, NULL));
if (!gens)
return false;

bool success = false;
for (int idx = 0; idx < sk_GENERAL_NAME_num(gens); idx++) {
GENERAL_NAME *gen;
ASN1_STRING *cstr;
gen = sk_GENERAL_NAME_value(gens, idx);
if (gen->type != GEN_DNS)
continue;
cstr = gen->d.dNSName;
if (ASN1_STRING_type(cstr) != V_ASN1_IA5STRING)
continue;
int san_fqdn_len = ASN1_STRING_length(cstr);
if (san_fqdn_len > 255)
continue;
memcpy(san_fqdn, ASN1_STRING_data(cstr), san_fqdn_len);
san_fqdn[san_fqdn_len] = '\0';
if (strlen(san_fqdn) != static_cast<size_t>(san_fqdn_len)) // Avoid embedded null's.
continue;
DEBUG("Comparing SAN " << san_fqdn << " with " << fqdn);
if (MatchHostnames(san_fqdn, fqdn)) {
DEBUG("SAN " << san_fqdn << " matches with " << fqdn);
success = true;
break;
}
}
sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
return success;
}
3 changes: 3 additions & 0 deletions src/XrdCrypto/XrdCryptosslX509.hh
Expand Up @@ -98,6 +98,9 @@ public:
const char *SubjectHash(int = 0); // get hash of subject name
const char *IssuerHash(int = 0); // get hash of issuer name

// Check SANs
virtual bool MatchesSAN(const char *);

// Retrieve a given extension if there (in opaque form)
XrdCryptoX509data GetExtension(const char *oid);

Expand Down
31 changes: 20 additions & 11 deletions src/XrdSecgsi/XrdSecProtocolgsi.cc
Expand Up @@ -48,6 +48,7 @@
#include "XrdOuc/XrdOucStream.hh"
#include "XrdOuc/XrdOucEnv.hh"

#include "XrdNet/XrdNetAddr.hh"
#include "XrdSut/XrdSutAux.hh"

#include "XrdCrypto/XrdCryptoMsgDigest.hh"
Expand Down Expand Up @@ -293,7 +294,15 @@ XrdSecProtocolgsi::XrdSecProtocolgsi(int opts, const char *hname,
}

// Set host name and address
Entity.host = strdup(endPoint.Name("*unknown*"));
// The hostname is critical for the GSI protocol; it must match the potential
// names on the remote EEC. However, as we may have been redirected to an IP
// address instead of an actual hostname, we must fallback to a reverse DNS lookup.
XrdNetAddr testAddr;
if (!hname || testAddr.Set(hname) == NULL) {
Entity.host = strdup(endPoint.Name(""));
} else {
Entity.host = strdup(hname);
}
epAddr = endPoint;
Entity.addrInfo = &epAddr;

Expand Down Expand Up @@ -3048,7 +3057,10 @@ int XrdSecProtocolgsi::ClientDoCert(XrdSutBuffer *br, XrdSutBuffer **bm,
}
//
// Verify server identity
if (!ServerCertNameOK(hs->Chain->End()->Subject(), emsg)) {
// First, check the DN. On failure, we will iterate through
// the alternate names.
if (!ServerCertNameOK(hs->Chain->End()->Subject(), emsg) &&
!hs->Chain->End()->MatchesSAN(Entity.host)) {
return -1;
}
//
Expand Down Expand Up @@ -5154,6 +5166,7 @@ XrdSecgsiVOMS_t XrdSecProtocolgsi::LoadVOMSFun(const char *plugin,
return ep;
}


//_____________________________________________________________________________
bool XrdSecProtocolgsi::ServerCertNameOK(const char *subject, XrdOucString &emsg)
{
Expand All @@ -5174,16 +5187,12 @@ bool XrdSecProtocolgsi::ServerCertNameOK(const char *subject, XrdOucString &emsg

// Always check if the server CN is in the standard form "[*/]<target host name>[/*]"
if (Entity.host) {
if (srvcn != (const char *) Entity.host) {
int ih = srvcn.find((const char *) Entity.host);
if (ih == 0 || (ih > 0 && srvcn[ih-1] == '/')) {
ih += strlen(Entity.host);
if (ih >= srvcn.length() ||
srvcn[ih] == '\0' || srvcn[ih] == '/') allowed = 1;
}
} else {
allowed = 1;
size_t ih = srvcn.find("/");
if (ih != std::string::npos) {
srvcn.erasefromstart(ih + 1);
}
allowed = XrdCryptoX509::MatchHostnames(srvcn.c_str(), Entity.host);

// Update the error msg, if the case
if (!allowed) {
if (emsg.length() <= 0) {
Expand Down

0 comments on commit 9a78c14

Please sign in to comment.