+ if (conn)
+ SSL_free (conn);
+ return false;
+}
+
+#define ASTERISK_EXCLUDES_DOT /* mandated by rfc2818 */
+
+/* Return true is STRING (case-insensitively) matches PATTERN, false
+ otherwise. The recognized wildcard character is "*", which matches
+ any character in STRING except ".". Any number of the "*" wildcard
+ may be present in the pattern.
+
+ This is used to match of hosts as indicated in rfc2818: "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 [or foo.bar.com]."
+
+ If the pattern contain no wildcards, pattern_match(a, b) is
+ equivalent to !strcasecmp(a, b). */
+
+static bool
+pattern_match (const char *pattern, const char *string)
+{
+ const char *p = pattern, *n = string;
+ char c;
+ for (; (c = c_tolower (*p++)) != '\0'; n++)
+ if (c == '*')
+ {
+ for (c = c_tolower (*p); c == '*'; c = c_tolower (*++p))
+ ;
+ for (; *n != '\0'; n++)
+ if (c_tolower (*n) == c && pattern_match (p, n))
+ return true;
+#ifdef ASTERISK_EXCLUDES_DOT
+ else if (*n == '.')
+ return false;
+#endif
+ return c == '\0';
+ }
+ else
+ {
+ if (c != c_tolower (*n))
+ return false;
+ }
+ return *n == '\0';
+}
+
+/* Verify the validity of the certificate presented by the server.
+ Also check that the "common name" of the server, as presented by
+ its certificate, corresponds to HOST. (HOST typically comes from
+ the URL and is what the user thinks he's connecting to.)
+
+ This assumes that ssl_connect has successfully finished, i.e. that
+ the SSL handshake has been performed and that FD is connected to an
+ SSL handle.
+
+ If opt.check_cert is true (the default), this returns 1 if the
+ certificate is valid, 0 otherwise. If opt.check_cert is 0, the
+ function always returns 1, but should still be called because it
+ warns the user about any problems with the certificate. */
+
+bool
+ssl_check_certificate (int fd, const char *host)
+{
+ X509 *cert;
+ char common_name[256];
+ long vresult;
+ bool success = true;
+
+ /* 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 = X509_NAME_oneline (X509_get_subject_name (cert), 0, 0);
+ char *issuer = X509_NAME_oneline (X509_get_issuer_name (cert), 0, 0);
+ DEBUGP (("certificate:\n subject: %s\n issuer: %s\n",
+ quotearg_style (escape_quoting_style, subject),
+ quotearg_style (escape_quoting_style, issuer)));
+ OPENSSL_free (subject);
+ OPENSSL_free (issuer);
+ }
+
+ vresult = SSL_get_verify_result (conn);
+ if (vresult != X509_V_OK)
+ {
+ char *issuer = X509_NAME_oneline (X509_get_issuer_name (cert), 0, 0);
+ logprintf (LOG_NOTQUIET,
+ _("%s: cannot verify %s's certificate, issued by %s:\n"),
+ severity, quotearg_style (escape_quoting_style, host),
+ quote (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:
+
+ - It should use dNSName/ipAddress subjectAltName extensions if
+ available; according to rfc2818: "If a subjectAltName extension
+ of type dNSName is present, that MUST be used as the identity."
+
+ - 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. */
+
+ common_name[0] = '\0';
+ X509_NAME_get_text_by_NID (X509_get_subject_name (cert),
+ 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 (common_name), quote (host));
+ success = false;
+ }
+
+ if (success)
+ DEBUGP (("X509 certificate successfully verified and matches host %s\n",
+ quotearg_style (escape_quoting_style, host)));
+ X509_free (cert);
+
+ no_cert:
+ if (opt.check_cert && !success)
+ logprintf (LOG_NOTQUIET, _("\
+To connect to %s insecurely, use `--no-check-certificate'.\n"),
+ quotearg_style (escape_quoting_style, host));
+
+ /* Allow --no-check-cert to disable certificate checking. */
+ return opt.check_cert ? success : true;