Commit 597c1fe6 authored by Daniel Stenberg's avatar Daniel Stenberg
Browse files

rewritten alternative name check

parent 7201a5a2
Loading
Loading
Loading
Loading
+87 −88
Original line number Diff line number Diff line
@@ -744,118 +744,117 @@ cert_hostcheck(const char *certname, const char *hostname)
  return 0;
}

/* this subjectAltName patch is code originating from OpenLDAP, which uses
   a license as described here: 
   http://www.openldap.org/software/release/license.html
/* Quote from RFC2818 section 3.1 "Server Identity"

   If a subjectAltName extension of type dNSName is present, that MUST
   be used as the identity. Otherwise, the (most specific) Common Name
   field in the Subject field of the certificate MUST be used. Although
   the use of the Common Name is existing practice, it is deprecated and
   Certification Authorities are encouraged to use the dNSName instead.

   Matching is performed using the matching rules specified by
   [RFC2459].  If more than one identity of a given type is present in
   the certificate (e.g., more than one dNSName name, a match in any one
   of the set is considered acceptable.) Names may contain the wildcard
   character * which is considered to match any single domain name
   component or component fragment. E.g., *.a.com matches foo.a.com but
   not bar.foo.a.com. f*.com matches foo.com but not bar.com.

   In some cases, the URI is specified as an IP address rather than a
   hostname. In this case, the iPAddress subjectAltName must be present
   in the certificate and must exactly match the IP in the URI.

   It is not GPL-compatible, so we cannot have this situation in a release-
   version of libcurl.

   This needs to be addressed!
*/

static CURLcode verifyhost(struct connectdata *conn)
{
  char peer_CN[257];
  int ntype = 3; /* 1 = IPv6, 2 = IPv4, 3=DNS */
  int i;
  int altmatch = 0;
  bool matched = FALSE; /* no alternative match yet */
  int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */
  int addrlen;
  struct SessionHandle *data = conn->data;
  STACK_OF(GENERAL_NAME) *altnames;
#ifdef ENABLE_IPV6
  struct in6_addr addr;
#else
  struct in_addr addr;
#endif
  char *ptr;
  struct SessionHandle *data = conn->data;
 
#ifdef ENABLE_IPV6
  if(conn->hostname[0] == '[' && strchr(conn->hostname, ']')) {
    char *n2 = strdup(conn->hostname+1);
    *strchr(n2, ']') = '\0';
    if(Curl_inet_pton(AF_INET6, n2, &addr))
      ntype = 1;
    free(n2);
  if(conn->bits.ipv6_ip && 
     Curl_inet_pton(AF_INET6, conn->hostname, &addr)) {
    target = GEN_IPADD;
    addrlen = sizeof(struct in6_addr);
  }
  else
#endif
  {
    if((ptr = strrchr(conn->hostname, '.')) &&
       isdigit((unsigned char)ptr[1])) {
      if(Curl_inet_pton(AF_INET, conn->hostname, &addr))
        ntype = 2;
    }
    if(Curl_inet_pton(AF_INET, conn->hostname, &addr)) {
      target = GEN_IPADD;
      addrlen = sizeof(struct in_addr);
    }
  
  i = X509_get_ext_by_NID(conn->ssl.server_cert, NID_subject_alt_name, -1);
  if(i >= 0) {
    X509_EXTENSION *ex;
    STACK_OF(GENERAL_NAME) *alt;
  /* get a "list" of alternative names */
  altnames = X509_get_ext_d2i(conn->ssl.server_cert, NID_subject_alt_name,
                              NULL, NULL);
  
    ex = X509_get_ext(conn->ssl.server_cert, i);
    alt = X509V3_EXT_d2i(ex);
    if(alt) {
      int n, len1 = 0, len2 = 0;
      char *domain = NULL;
      GENERAL_NAME *gn;
  if(altnames) {
    int hostlen;
    int domainlen;
    char *domain;
    int numalts;
    int i;
        
      if(ntype == 3) {
        len1 = strlen(conn->hostname);
    if(GEN_DNS == target) {
      hostlen = strlen(conn->hostname);
      domain = strchr(conn->hostname, '.');
        if(domain) {
          len2 = len1 - (domain-conn->hostname);
        }
      if(domain)
        domainlen = strlen(domain);
    }
      n = sk_GENERAL_NAME_num(alt);
      for (i=0; i<n; i++) {
        char *sn;
        int sl;
        gn = sk_GENERAL_NAME_value(alt, i);
        if(gn->type == GEN_DNS) {
          if(ntype != 3)
            continue;

          sn = (char *) ASN1_STRING_data(gn->d.ia5);
          sl = ASN1_STRING_length(gn->d.ia5);
    /* get amount of alternatives, RFC2459 claims there MUST be at least
       one, but we don't depend on it... */
    numalts = sk_GENERAL_NAME_num(altnames);

    /* loop through all alternatives while none has matched */
    for (i=0; (i<numalts) && !matched; i++) {
      /* get a handle to alternative name number i */
      const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i);

      /* only check alternatives of the same type the target is */
      if(check->type == target) {
        /* get data and length */
        const char *altptr = (char *)ASN1_STRING_data(check->d.ia5);
        const int altlen = ASN1_STRING_length(check->d.ia5);

        switch(target) {
        case GEN_DNS: /* name comparison */
          /* Is this an exact match? */
          if((len1 == sl) && curl_strnequal(conn->hostname, sn, len1))
            break;
          if((hostlen == altlen) &&
             curl_strnequal(conn->hostname, altptr, hostlen))
            matched = TRUE;
        
          /* Is this a wildcard match? */
          if((*sn == '*') && domain && (len2 == sl-1) &&
             curl_strnequal(domain, sn+1, len2))
          else if((altptr[0] == '*') &&
                  (domainlen == altlen-1) &&
                  curl_strnequal(domain, altptr+1, domainlen))
            matched = TRUE;
          break;
          
        }
        else if(gn->type == GEN_IPADD) {
          if(ntype == 3)
            continue;
                     
          sn = (char *) ASN1_STRING_data(gn->d.ia5);
          sl = ASN1_STRING_length(gn->d.ia5);
 
#ifdef ENABLE_IPv6
          if(ntype == 1 && sl != sizeof(struct in6_addr))
            continue;
          else
#endif
            if(ntype == 2 && sl != sizeof(struct in_addr))
              continue;
          
          if(!memcmp(sn, &addr, sl))
        case GEN_IPADD: /* IP address comparison */          
          /* compare alternative IP address if the data chunk is the same size
             our server IP address is */
          if((altlen == addrlen) && !memcmp(altptr, &addr, altlen))
            matched = TRUE;
          break;
        }
      }
             
      GENERAL_NAMES_free(alt);
      if(i < n) {	/* got a match in altnames */
        altmatch = 1;
        infof(data, "\t subjectAltName: %s matched\n", conn->hostname);
      }
    }
    GENERAL_NAMES_free(altnames);
  }
 
  if(!altmatch) {
  if(matched)
    /* an alternative name matched the server hostname */
    infof(data, "\t subjectAltName: %s matched\n", conn->hostname);
  else {
    bool obtain=FALSE;
    if(X509_NAME_get_text_by_NID(X509_get_subject_name(conn->ssl.server_cert),
                                 NID_commonName,