Exemplo n.º 1
0
/**
* Tries to find a match for hostname in the certificate's Subject Alternative Name extension.
*
* Returns MatchFound if a match was found.
* Returns MatchNotFound if no matches were found.
* Returns MalformedCertificate if any of the hostnames had a NUL character embedded in it.
* Returns NoSANPresent if the SAN extension was not present in the certificate.
*/
static HostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert) {
        HostnameValidationResult result = MatchNotFound;
        int i;
        int san_names_nb = -1;
        STACK_OF(GENERAL_NAME) *san_names = NULL;

        // Try to extract the names within the SAN extension from the certificate
        san_names = X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL);
        if (san_names == NULL) {
                return NoSANPresent;
        }
        san_names_nb = sk_GENERAL_NAME_num(san_names);

        // Check each name within the extension
        for (i=0; i<san_names_nb; i++) {
                const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);

                if (current_name->type == GEN_DNS) {
                        // Current name is a DNS name, let's check it
                        char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName);

                        // Make sure there isn't an embedded NUL character in the DNS name
                        if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
                                result = MalformedCertificate;
                                break;
                        }
                        else { // Compare expected hostname with the DNS name
                                if (Curl_cert_hostcheck(dns_name, hostname)
                                    == CURL_HOST_MATCH) {
                                        result = MatchFound;
                                        break;
                                }
                        }
                }
        }
        sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);

        return result;
}
Exemplo n.º 2
0
/**
* Tries to find a match for hostname in the certificate's Common Name field.
*
* Returns MatchFound if a match was found.
* Returns MatchNotFound if no matches were found.
* Returns MalformedCertificate if the Common Name had a NUL character embedded in it.
* Returns Error if the Common Name could not be extracted.
*/
static HostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert) {
        int common_name_loc = -1;
        X509_NAME_ENTRY *common_name_entry = NULL;
        ASN1_STRING *common_name_asn1 = NULL;
        char *common_name_str = NULL;

        // Find the position of the CN field in the Subject field of the certificate
        common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *) server_cert), NID_commonName, -1);
        if (common_name_loc < 0) {
                return Error;
        }

        // Extract the CN field
        common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *) server_cert), common_name_loc);
        if (common_name_entry == NULL) {
                return Error;
        }

        // Convert the CN field to a C string
        common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
        if (common_name_asn1 == NULL) {
                return Error;
        }
        common_name_str = (char *) ASN1_STRING_data(common_name_asn1);

        // Make sure there isn't an embedded NUL character in the CN
        if ((size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) {
                return MalformedCertificate;
        }

        // Compare expected hostname with the CN
        if (Curl_cert_hostcheck(common_name_str, hostname) == CURL_HOST_MATCH) {
                return MatchFound;
        }
        else {
                return MatchNotFound;
        }
}
Exemplo n.º 3
0
/*
 * This function is called after the TCP connect has completed. Setup the TLS
 * layer and do all necessary magic.
 */
CURLcode
Curl_axtls_connect(struct connectdata *conn,
                  int sockindex)

{
  struct SessionHandle *data = conn->data;
  SSL_CTX *ssl_ctx;
  SSL *ssl;
  int cert_types[] = {SSL_OBJ_X509_CERT, SSL_OBJ_PKCS12, 0};
  int key_types[] = {SSL_OBJ_RSA_KEY, SSL_OBJ_PKCS8, SSL_OBJ_PKCS12, 0};
  int i, ssl_fcn_return;
  const uint8_t *ssl_sessionid;
  size_t ssl_idsize;
  const char *peer_CN;
  uint32_t dns_altname_index;
  const char *dns_altname;
  int8_t found_subject_alt_names = 0;
  int8_t found_subject_alt_name_matching_conn = 0;

  /* Assuming users will not compile in custom key/cert to axTLS */
  uint32_t client_option = SSL_NO_DEFAULT_KEY|SSL_SERVER_VERIFY_LATER;

  if(conn->ssl[sockindex].state == ssl_connection_complete)
    /* to make us tolerant against being called more than once for the
       same connection */
    return CURLE_OK;

  /* axTLS only supports TLSv1 */
  /* check to see if we've been told to use an explicit SSL/TLS version */
  switch(data->set.ssl.version) {
  case CURL_SSLVERSION_DEFAULT:
  case CURL_SSLVERSION_TLSv1:
    break;
  default:
    failf(data, "axTLS only supports TLSv1");
    return CURLE_SSL_CONNECT_ERROR;
  }

#ifdef  AXTLSDEBUG
  client_option |= SSL_DISPLAY_STATES | SSL_DISPLAY_RSA | SSL_DISPLAY_CERTS;
#endif /* AXTLSDEBUG */

  /* Allocate an SSL_CTX struct */
  ssl_ctx = ssl_ctx_new(client_option, SSL_DEFAULT_CLNT_SESS);
  if(ssl_ctx == NULL) {
    failf(data, "unable to create client SSL context");
    return CURLE_SSL_CONNECT_ERROR;
  }

  /* Load the trusted CA cert bundle file */
  if(data->set.ssl.CAfile) {
    if(ssl_obj_load(ssl_ctx, SSL_OBJ_X509_CACERT, data->set.ssl.CAfile, NULL)
       != SSL_OK) {
      infof(data, "error reading ca cert file %s \n",
            data->set.ssl.CAfile);
      if(data->set.ssl.verifypeer) {
        Curl_axtls_close(conn, sockindex);
        return CURLE_SSL_CACERT_BADFILE;
      }
    }
    else
      infof(data, "found certificates in %s\n", data->set.ssl.CAfile);
  }

  /* curl_gtls.c tasks we're skipping for now:
   * 1) certificate revocation list checking
   * 2) dns name assignment to host
   * 3) set protocol priority.  axTLS is TLSv1 only, so can probably ignore
   * 4) set certificate priority.  axTLS ignores type and sends certs in
   *  order added.  can probably ignore this.
   */

  /* Load client certificate */
  if(data->set.str[STRING_CERT]) {
    i=0;
    /* Instead of trying to analyze cert type here, let axTLS try them all. */
    while(cert_types[i] != 0) {
      ssl_fcn_return = ssl_obj_load(ssl_ctx, cert_types[i],
                                    data->set.str[STRING_CERT], NULL);
      if(ssl_fcn_return == SSL_OK) {
        infof(data, "successfully read cert file %s \n",
              data->set.str[STRING_CERT]);
        break;
      }
      i++;
    }
    /* Tried all cert types, none worked. */
    if(cert_types[i] == 0) {
      failf(data, "%s is not x509 or pkcs12 format",
            data->set.str[STRING_CERT]);
      Curl_axtls_close(conn, sockindex);
      return CURLE_SSL_CERTPROBLEM;
    }
  }

  /* Load client key.
     If a pkcs12 file successfully loaded a cert, then there's nothing to do
     because the key has already been loaded. */
  if(data->set.str[STRING_KEY] && cert_types[i] != SSL_OBJ_PKCS12) {
    i=0;
    /* Instead of trying to analyze key type here, let axTLS try them all. */
    while(key_types[i] != 0) {
      ssl_fcn_return = ssl_obj_load(ssl_ctx, key_types[i],
                                    data->set.str[STRING_KEY], NULL);
      if(ssl_fcn_return == SSL_OK) {
        infof(data, "successfully read key file %s \n",
              data->set.str[STRING_KEY]);
        break;
      }
      i++;
    }
    /* Tried all key types, none worked. */
    if(key_types[i] == 0) {
      failf(data, "Failure: %s is not a supported key file",
            data->set.str[STRING_KEY]);
      Curl_axtls_close(conn, sockindex);
      return CURLE_SSL_CONNECT_ERROR;
    }
  }

  /* curl_gtls.c does more here that is being left out for now
   * 1) set session credentials.  can probably ignore since axtls puts this
   *    info in the ssl_ctx struct
   * 2) setting up callbacks.  these seem gnutls specific
   */

  /* In axTLS, handshaking happens inside ssl_client_new. */
  if(!Curl_ssl_getsessionid(conn, (void **) &ssl_sessionid, &ssl_idsize)) {
    /* we got a session id, use it! */
    infof (data, "SSL re-using session ID\n");
    ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex],
                         ssl_sessionid, (uint8_t)ssl_idsize);
  }
  else
    ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex], NULL, 0);

  /* Check to make sure handshake was ok. */
  ssl_fcn_return = ssl_handshake_status(ssl);
  if(ssl_fcn_return != SSL_OK) {
    Curl_axtls_close(conn, sockindex);
    ssl_display_error(ssl_fcn_return); /* goes to stdout. */
    return map_error_to_curl(ssl_fcn_return);
  }
  infof (data, "handshake completed successfully\n");

  /* Here, curl_gtls.c gets the peer certificates and fails out depending on
   * settings in "data."  axTLS api doesn't have get cert chain fcn, so omit?
   */

  /* Verify server's certificate */
  if(data->set.ssl.verifypeer) {
    if(ssl_verify_cert(ssl) != SSL_OK) {
      Curl_axtls_close(conn, sockindex);
      failf(data, "server cert verify failed");
      return CURLE_SSL_CONNECT_ERROR;
    }
  }
  else
    infof(data, "\t server certificate verification SKIPPED\n");

  /* Here, curl_gtls.c does issuer verification. axTLS has no straightforward
   * equivalent, so omitting for now.*/

  /* Here, curl_gtls.c does the following
   * 1) x509 hostname checking per RFC2818.  axTLS doesn't support this, but
   *    it seems useful. This is now implemented, by Oscar Koeroo
   * 2) checks cert validity based on time.  axTLS does this in ssl_verify_cert
   * 3) displays a bunch of cert information.  axTLS doesn't support most of
   *    this, but a couple fields are available.
   */


  /* There is no (DNS) Altnames count in the version 1.4.8 API. There is a
     risk of an inifite loop */
  for(dns_altname_index = 0; ; dns_altname_index++) {
    dns_altname = ssl_get_cert_subject_alt_dnsname(ssl, dns_altname_index);
    if(dns_altname == NULL) {
      break;
    }
    found_subject_alt_names = 1;

    infof(data, "\tComparing subject alt name DNS with hostname: %s <-> %s\n",
          dns_altname, conn->host.name);
    if(Curl_cert_hostcheck(dns_altname, conn->host.name)) {
      found_subject_alt_name_matching_conn = 1;
      break;
    }
  }

  /* RFC2818 checks */
  if(found_subject_alt_names && !found_subject_alt_name_matching_conn) {
    /* Break connection ! */
    Curl_axtls_close(conn, sockindex);
    failf(data, "\tsubjectAltName(s) do not match %s\n", conn->host.dispname);
    return CURLE_PEER_FAILED_VERIFICATION;
  }
  else if(found_subject_alt_names == 0) {
    /* Per RFC2818, when no Subject Alt Names were available, examine the peer
       CN as a legacy fallback */
    peer_CN = ssl_get_cert_dn(ssl, SSL_X509_CERT_COMMON_NAME);
    if(peer_CN == NULL) {
      /* Similar behaviour to the OpenSSL interface */
      Curl_axtls_close(conn, sockindex);
      failf(data, "unable to obtain common name from peer certificate");
      return CURLE_PEER_FAILED_VERIFICATION;
    }
    else {
      if(!Curl_cert_hostcheck((const char *)peer_CN, conn->host.name)) {
        if(data->set.ssl.verifyhost) {
          /* Break connection ! */
          Curl_axtls_close(conn, sockindex);
          failf(data, "\tcommon name \"%s\" does not match \"%s\"\n",
                peer_CN, conn->host.dispname);
          return CURLE_PEER_FAILED_VERIFICATION;
        }
        else
          infof(data, "\tcommon name \"%s\" does not match \"%s\"\n",
                peer_CN, conn->host.dispname);
      }
    }
  }

  /* General housekeeping */
  conn->ssl[sockindex].state = ssl_connection_complete;
  conn->ssl[sockindex].ssl = ssl;
  conn->ssl[sockindex].ssl_ctx = ssl_ctx;
  conn->recv[sockindex] = axtls_recv;
  conn->send[sockindex] = axtls_send;

  /* Put our freshly minted SSL session in cache */
  ssl_idsize = ssl_get_session_id_size(ssl);
  ssl_sessionid = ssl_get_session_id(ssl);
  if(Curl_ssl_addsessionid(conn, (void *) ssl_sessionid, ssl_idsize)
     != CURLE_OK)
    infof (data, "failed to add session to cache\n");

  return CURLE_OK;
}
Exemplo n.º 4
0
/*
 * For both blocking and non-blocking connects, this function finalizes the
 * SSL connection.
 */
static CURLcode connect_finish(struct connectdata *conn, int sockindex)
{
  struct SessionHandle *data = conn->data;
  SSL *ssl = conn->ssl[sockindex].ssl;
  const uint8_t *ssl_sessionid;
  size_t ssl_idsize;
  const char *peer_CN;
  uint32_t dns_altname_index;
  const char *dns_altname;
  int8_t found_subject_alt_names = 0;
  int8_t found_subject_alt_name_matching_conn = 0;

  /* Here, gtls.c gets the peer certificates and fails out depending on
   * settings in "data."  axTLS api doesn't have get cert chain fcn, so omit?
   */

  /* Verify server's certificate */
  if(data->set.ssl.verifypeer) {
    if(ssl_verify_cert(ssl) != SSL_OK) {
      Curl_axtls_close(conn, sockindex);
      failf(data, "server cert verify failed");
      return CURLE_PEER_FAILED_VERIFICATION;
    }
  }
  else
    infof(data, "\t server certificate verification SKIPPED\n");

  /* Here, gtls.c does issuer verification. axTLS has no straightforward
   * equivalent, so omitting for now.*/

  /* Here, gtls.c does the following
   * 1) x509 hostname checking per RFC2818.  axTLS doesn't support this, but
   *    it seems useful. This is now implemented, by Oscar Koeroo
   * 2) checks cert validity based on time.  axTLS does this in ssl_verify_cert
   * 3) displays a bunch of cert information.  axTLS doesn't support most of
   *    this, but a couple fields are available.
   */

  /* There is no (DNS) Altnames count in the version 1.4.8 API. There is a
     risk of an inifite loop */
  for(dns_altname_index = 0; ; dns_altname_index++) {
    dns_altname = ssl_get_cert_subject_alt_dnsname(ssl, dns_altname_index);
    if(dns_altname == NULL) {
      break;
    }
    found_subject_alt_names = 1;

    infof(data, "\tComparing subject alt name DNS with hostname: %s <-> %s\n",
          dns_altname, conn->host.name);
    if(Curl_cert_hostcheck(dns_altname, conn->host.name)) {
      found_subject_alt_name_matching_conn = 1;
      break;
    }
  }

  /* RFC2818 checks */
  if(found_subject_alt_names && !found_subject_alt_name_matching_conn) {
    if(data->set.ssl.verifyhost) {
      /* Break connection ! */
      Curl_axtls_close(conn, sockindex);
      failf(data, "\tsubjectAltName(s) do not match %s\n",
            conn->host.dispname);
      return CURLE_PEER_FAILED_VERIFICATION;
    }
    else
      infof(data, "\tsubjectAltName(s) do not match %s\n",
            conn->host.dispname);
  }
  else if(found_subject_alt_names == 0) {
    /* Per RFC2818, when no Subject Alt Names were available, examine the peer
       CN as a legacy fallback */
    peer_CN = ssl_get_cert_dn(ssl, SSL_X509_CERT_COMMON_NAME);
    if(peer_CN == NULL) {
      if(data->set.ssl.verifyhost) {
        Curl_axtls_close(conn, sockindex);
        failf(data, "unable to obtain common name from peer certificate");
        return CURLE_PEER_FAILED_VERIFICATION;
      }
      else
        infof(data, "unable to obtain common name from peer certificate");
    }
    else {
      if(!Curl_cert_hostcheck((const char *)peer_CN, conn->host.name)) {
        if(data->set.ssl.verifyhost) {
          /* Break connection ! */
          Curl_axtls_close(conn, sockindex);
          failf(data, "\tcommon name \"%s\" does not match \"%s\"\n",
                peer_CN, conn->host.dispname);
          return CURLE_PEER_FAILED_VERIFICATION;
        }
        else
          infof(data, "\tcommon name \"%s\" does not match \"%s\"\n",
                peer_CN, conn->host.dispname);
      }
    }
  }

  /* General housekeeping */
  conn->ssl[sockindex].state = ssl_connection_complete;
  conn->recv[sockindex] = axtls_recv;
  conn->send[sockindex] = axtls_send;

  /* Put our freshly minted SSL session in cache */
  ssl_idsize = ssl_get_session_id_size(ssl);
  ssl_sessionid = ssl_get_session_id(ssl);
  if(Curl_ssl_addsessionid(conn, (void *) ssl_sessionid, ssl_idsize)
     != CURLE_OK)
    infof (data, "failed to add session to cache\n");

  return CURLE_OK;
}
Exemplo n.º 5
0
Arquivo: cert.c Projeto: michals/sx
CURLcode sxi_verifyhost(sxc_client_t *sx, const char *hostname, X509 *server_cert)
{
    int matched = -1; /* -1 is no alternative match yet, 1 means match and 0
                         means mismatch */
    STACK_OF(GENERAL_NAME) *altnames;
    CURLcode res = CURLE_OK;

    /* get a "list" of alternative names */
    altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL);

    if(altnames) {
        int numalts;
        int i;

        /* 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 != 1); 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 == GEN_DNS) {
                /* get data and length */
                const char *altptr = (char *)ASN1_STRING_data(check->d.ia5);
                size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5);

                /* name/pattern comparison */
                /* The OpenSSL man page explicitly says: "In general it cannot be
                   assumed that the data returned by ASN1_STRING_data() is null
                   terminated or does not contain embedded nulls." But also that
                   "The actual format of the data will depend on the actual string
                   type itself: for example for and IA5String the data will be ASCII"

                   Curl uses strlen(altptr) == altlen here to check for embedded \0s.
                   We use memchr() to be safer from a possible non-null terminated string.
                   */
                if(!memchr(altptr, 0, altlen) &&
                   /* if this isn't true, there was an embedded zero in the name
                      string and we cannot match it. */
                   Curl_cert_hostcheck(altptr, hostname))
                    matched = 1;
                else
                    matched = 0;
            }
        }
        GENERAL_NAMES_free(altnames);
    }

    if(matched == 1)
        /* an alternative name matched the server hostname */
        SXDEBUG("\t subjectAltName: %s matched\n", hostname);
    else if(matched == 0) {
        /* an alternative name field existed, but didn't match and then
           we MUST fail */
        sxi_seterr(sx, SXE_ECOMM, "subjectAltName does not match %s\n", hostname);
        res = CURLE_PEER_FAILED_VERIFICATION;
    }
    else {
        /* we have to look to the last occurrence of a commonName in the
           distinguished one to get the most significant one. */
        int j,i=-1 ;

        /* The following is done because of a bug in 0.9.6b */

        unsigned char *nulstr = (unsigned char *)"";
        unsigned char *peer_CN = nulstr;

        X509_NAME *name = X509_get_subject_name(server_cert) ;
        if(name)
            while((j = X509_NAME_get_index_by_NID(name, NID_commonName, i))>=0)
                i=j;

        /* we have the name entry and we will now convert this to a string
           that we can use for comparison. Doing this we support BMPstring,
           UTF8 etc. */

        if(i>=0) {
            ASN1_STRING *tmp = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name,i));

            /* In OpenSSL 0.9.7d and earlier, ASN1_STRING_to_UTF8 fails if the input
               is already UTF-8 encoded. We check for this case and copy the raw
               string manually to avoid the problem. This code can be made
               conditional in the future when OpenSSL has been fixed. Work-around
               brought by Alexis S. L. Carvalho. */
            if(tmp) {
                if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) {
                    j = ASN1_STRING_length(tmp);
                    if(j >= 0) {
                        peer_CN = OPENSSL_malloc(j+1);
                        if(peer_CN) {
                            memcpy(peer_CN, ASN1_STRING_data(tmp), j);
                            peer_CN[j] = '\0';
                        }
                    }
                }
                else /* not a UTF8 name */
                    j = ASN1_STRING_to_UTF8(&peer_CN, tmp);

                if(peer_CN && memchr(peer_CN, 0, j)) {
                    /* there was a terminating zero before the end of string, this
                       cannot match and we return failure! */
                    sxi_seterr(sx, SXE_ECOMM, "SSL: illegal cert name field");
                    res = CURLE_PEER_FAILED_VERIFICATION;
                }
            }
        }

        if(peer_CN == nulstr)
            peer_CN = NULL;
        /* curl would convert from UTF8 to host encoding if built with HAVE_ICONV (not default) */

        if(res)
            /* error already detected, pass through */
            ;
        else if(!peer_CN) {
            SXDEBUG("SSL: unable to obtain common name from peer certificate");
            res = CURLE_PEER_FAILED_VERIFICATION;
        }
        else if(!Curl_cert_hostcheck((const char *)peer_CN, hostname)) {
            sxi_seterr(sx, SXE_ECOMM, "SSL: certificate subject name '%s' does not match "
                    "target host name '%s'", peer_CN, hostname);
            res = CURLE_PEER_FAILED_VERIFICATION;
        }
        else {
            SXDEBUG("\t common name: %s (matched)\n", peer_CN);
        }
        if(peer_CN)
            OPENSSL_free(peer_CN);
    }
    return res;
}