Esempio n. 1
0
bool
ssl_check_certificate (int fd, const char *host)
{
  X509 *cert;
  GENERAL_NAMES *subjectAltNames;
  char common_name[256];
  long vresult;
  bool success = true;
  bool alt_name_checked = false;

  /* If the user has specified --no-check-cert, we still want to warn
     him about problems with the server's certificate.  */
  const char *severity = opt.check_cert ? _("ERROR") : _("WARNING");

  struct openssl_transport_context *ctx = fd_transport_context (fd);
  SSL *conn = ctx->conn;
  assert (conn != NULL);

  cert = SSL_get_peer_certificate (conn);
  if (!cert)
    {
      logprintf (LOG_NOTQUIET, _("%s: No certificate presented by %s.\n"),
                 severity, quotearg_style (escape_quoting_style, host));
      success = false;
      goto no_cert;             /* must bail out since CERT is NULL */
    }

  IF_DEBUG
    {
      char *subject = _get_rfc2253_formatted (X509_get_subject_name (cert));
      char *issuer = _get_rfc2253_formatted (X509_get_issuer_name (cert));
      DEBUGP (("certificate:\n  subject: %s\n  issuer:  %s\n",
               quotearg_n_style (0, escape_quoting_style, subject),
               quotearg_n_style (1, escape_quoting_style, issuer)));
      xfree (subject);
      xfree (issuer);
    }

  vresult = SSL_get_verify_result (conn);
  if (vresult != X509_V_OK)
    {
      char *issuer = _get_rfc2253_formatted (X509_get_issuer_name (cert));
      logprintf (LOG_NOTQUIET,
                 _("%s: cannot verify %s's certificate, issued by %s:\n"),
                 severity, quotearg_n_style (0, escape_quoting_style, host),
                 quote_n (1, issuer));
      xfree(issuer);

      /* Try to print more user-friendly (and translated) messages for
         the frequent verification errors.  */
      switch (vresult)
        {
        case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
          logprintf (LOG_NOTQUIET,
                     _("  Unable to locally verify the issuer's authority.\n"));
          break;
        case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
        case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
          logprintf (LOG_NOTQUIET,
                     _("  Self-signed certificate encountered.\n"));
          break;
        case X509_V_ERR_CERT_NOT_YET_VALID:
          logprintf (LOG_NOTQUIET, _("  Issued certificate not yet valid.\n"));
          break;
        case X509_V_ERR_CERT_HAS_EXPIRED:
          logprintf (LOG_NOTQUIET, _("  Issued certificate has expired.\n"));
          break;
        default:
          /* For the less frequent error strings, simply provide the
             OpenSSL error message.  */
          logprintf (LOG_NOTQUIET, "  %s\n",
                     X509_verify_cert_error_string (vresult));
        }
      success = false;
      /* Fall through, so that the user is warned about *all* issues
         with the cert (important with --no-check-certificate.)  */
    }

  /* Check that HOST matches the common name in the certificate.
     #### The following remains to be done:

     - When matching against common names, it should loop over all
       common names and choose the most specific one, i.e. the last
       one, not the first one, which the current code picks.

     - Ensure that ASN1 strings from the certificate are encoded as
       UTF-8 which can be meaningfully compared to HOST.  */

  subjectAltNames = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL);

  if (subjectAltNames)
    {
      /* Test subject alternative names */

      /* Do we want to check for dNSNAmes or ipAddresses (see RFC 2818)?
       * Signal it by host_in_octet_string. */
      ASN1_OCTET_STRING *host_in_octet_string = a2i_IPADDRESS (host);

      int numaltnames = sk_GENERAL_NAME_num (subjectAltNames);
      int i;
      for (i=0; i < numaltnames; i++)
        {
          const GENERAL_NAME *name =
            sk_GENERAL_NAME_value (subjectAltNames, i);
          if (name)
            {
              if (host_in_octet_string)
                {
                  if (name->type == GEN_IPADD)
                    {
                      /* Check for ipAddress */
                      /* TODO: Should we convert between IPv4-mapped IPv6
                       * addresses and IPv4 addresses? */
                      alt_name_checked = true;
                      if (!ASN1_STRING_cmp (host_in_octet_string,
                            name->d.iPAddress))
                        break;
                    }
                }
              else if (name->type == GEN_DNS)
                {
                  /* dNSName should be IA5String (i.e. ASCII), however who
                   * does trust CA? Convert it into UTF-8 for sure. */
                  unsigned char *name_in_utf8 = NULL;

                  /* Check for dNSName */
                  alt_name_checked = true;

                  if (0 <= ASN1_STRING_to_UTF8 (&name_in_utf8, name->d.dNSName))
                    {
                      /* Compare and check for NULL attack in ASN1_STRING */
                      if (pattern_match ((char *)name_in_utf8, host) &&
                            (strlen ((char *)name_in_utf8) ==
                                (size_t) ASN1_STRING_length (name->d.dNSName)))
                        {
                          OPENSSL_free (name_in_utf8);
                          break;
                        }
                      OPENSSL_free (name_in_utf8);
                    }
                }
            }
        }
      sk_GENERAL_NAME_pop_free(subjectAltNames, GENERAL_NAME_free);
      if (host_in_octet_string)
        ASN1_OCTET_STRING_free(host_in_octet_string);

      if (alt_name_checked == true && i >= numaltnames)
        {
          logprintf (LOG_NOTQUIET,
              _("%s: no certificate subject alternative name matches\n"
                "\trequested host name %s.\n"),
                     severity, quote_n (1, host));
          success = false;
        }
    }

  if (alt_name_checked == false)
    {
      /* Test commomName */
      X509_NAME *xname = X509_get_subject_name(cert);
      common_name[0] = '\0';
      X509_NAME_get_text_by_NID (xname, NID_commonName, common_name,
                                 sizeof (common_name));

      if (!pattern_match (common_name, host))
        {
          logprintf (LOG_NOTQUIET, _("\
    %s: certificate common name %s doesn't match requested host name %s.\n"),
                     severity, quote_n (0, common_name), quote_n (1, host));
          success = false;
        }
Esempio n. 2
0
/* Confirm the peer identity, by checking if the certificate subject 
 * matches the peer's DNS name or IP address. Matching is performed in
 * accordance with RFC 2818:
 *
 * If the certificate has a subjectAltName extension, all names of type
 * IPAddress or dnsName present there, will be compared to data->host,
 * depending on it's contents.
 * In case there's no subjectAltName extension, commonName (CN) parts
 * of the certificate subject field will be used instead of IPAddress
 * and dnsName entries. For IP addresses, common names must contain IPs
 * in presentation format (1.2.3.4 or 2001:DB8:15:dead::)
 * Finally, if no subjectAltName or common names are present, the
 * certificate is considered to not match the peer.
 *
 * The structure of X509 certificates and all fields referenced above
 * are described in RFC 5280.
 *
 * The certificate must be pointed by cert and the peer's host must be
 * placed in data->host. The format is a regular DNS name or an IP in
 * presentation format (see above).
 * 
 * Return value: 1 if the certificate matches the peer, 0 otherwise.
 */
static int ssl_verifycn(X509 *cert, ssl_appdata *data)
{
  char *cn;
  int crit = 0, match = 0;
  ASN1_OCTET_STRING *ip;
  GENERAL_NAMES *altname; /* SubjectAltName ::= GeneralNames */
  
  ip = a2i_IPADDRESS(data->host); /* check if it's an IP or a hostname */
  if ((altname = X509_get_ext_d2i(cert, NID_subject_alt_name, &crit, NULL))) {
    GENERAL_NAME *gn;

    /* Loop through the general names in altname and pick these
       of type ip address or dns name */
    while (!match && (gn = sk_GENERAL_NAME_pop(altname))) {
      /* if the peer's host is an IP, we're only interested in
         matching against iPAddress general names, otherwise
         we'll only look for dnsName's */
      if (ip) {
        if (gn->type == GEN_IPADD)
          match = !ASN1_STRING_cmp(gn->d.ip, ip);
      } else if (gn->type == GEN_DNS) {
        /* IA5string holds ASCII data */
        cn = (char *) ASN1_STRING_data(gn->d.ia5);
        match = ssl_hostmatch(cn, data->host);
      }
    }
    sk_GENERAL_NAME_free(altname);
  } else { /* no subjectAltName, try to match against the subject CNs */
    X509_NAME *subj; /* certificate subject */

    /* the following is just for information */
    switch (crit) {
      case 0:
        debug0("TLS: X509 subjectAltName cannot be decoded");
        break;
      case -1:
        debug0("TLS: X509 has no subjectAltName extension");
        break;
      case -2:
        debug0("TLS: X509 has multiple subjectAltName extensions");
    }
    /* no subject name either? A completely broken certificate :) */
    if (!(subj = X509_get_subject_name(cert))) {
      putlog(data->loglevel, "*", "TLS: peer certificate has no subject: %s",
             data->host);
      match = 0;
    } else { /* we have a subject name, look at it */
      int pos = -1;
      ASN1_STRING *name;
      
      /* Look for commonName attributes in the subject name */
      pos = X509_NAME_get_index_by_NID(subj, NID_commonName, pos);
      if (pos == -1) /* sorry */
        putlog(data->loglevel, "*", "TLS: Peer has no common names and "
              "no subjectAltName extension. Verification failed.");
      /* Loop through all common names which may be present in the subject
         name until we find a match. */
      while (!match && pos != -1) {
        name = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subj, pos));
        cn = (char *) ASN1_STRING_data(name);
        if (ip)
          match = a2i_IPADDRESS(cn) ? (ASN1_STRING_cmp(ip, a2i_IPADDRESS(cn)) ? 0 : 1) : 0;
        else
          match = ssl_hostmatch(cn, data->host);
        pos = X509_NAME_get_index_by_NID(subj, NID_commonName, pos);
      }
    }
  }

  if (ip)
    ASN1_OCTET_STRING_free(ip);
  return match;
}