untrusted comment: signature from openbsd 5.5 base secret key RWRGy8gxk9N930zY+mHlRyBObN2mIoaUWYLZYGZXiD8Uu9/v4Pgb+rIOtmedlfOfwTsqLjFyzjPWEECAUmjft/nw96q1XTJPOwA= OpenBSD 5.5 errata 3, April 9, 2014: The ftp(1) client would fail to check the server hostname when connecting to an https website. This allowed any trusted CA-signed certificate to impersonate any other website. Apply patch using: signify -Vep /etc/signify/openbsd-55-base.pub -x 003_ftp.patch.sig \ -m - | (cd /usr/src && patch -p0) Then build and install ftp: cd /usr/src/usr.bin/ftp make obj make make install Index: usr.bin/ftp/fetch.c =================================================================== RCS file: /cvs/src/usr.bin/ftp/fetch.c,v retrieving revision 1.114 diff -u -p -r1.114 fetch.c --- usr.bin/ftp/fetch.c 2 Mar 2014 17:57:18 -0000 1.114 +++ usr.bin/ftp/fetch.c 9 Apr 2014 11:34:49 -0000 @@ -63,6 +63,7 @@ #ifndef SMALL #include #include +#include #else /* !SMALL */ #define SSL void #endif /* !SMALL */ @@ -82,6 +83,10 @@ size_t ftp_read(FILE *, SSL *, char *, int proxy_connect(int, char *, char *); int SSL_vprintf(SSL *, const char *, va_list); char *SSL_readline(SSL *, size_t *); +int ssl_match_hostname(char *, char *); +int ssl_check_subject_altname(X509 *, char *); +int ssl_check_common_name(X509 *, char *); +int ssl_check_hostname(X509 *, char *); #endif /* !SMALL */ #define FTP_URL "ftp://" /* ftp URL prefix */ @@ -167,6 +172,165 @@ url_encode(const char *path) return (epath); } +#ifndef SMALL +int +ssl_match_hostname(char *cert_hostname, char *hostname) +{ + if (strcasecmp(cert_hostname, hostname) == 0) + return 0; + + /* wildcard match? */ + if (cert_hostname[0] == '*') { + char *cert_domain, *domain; + + cert_domain = &cert_hostname[1]; + if (cert_domain[0] != '.') + return -1; + if (strlen(cert_domain) == 1) + return -1; + + domain = strchr(hostname, '.'); + /* no wildcard match against a hostname with no domain part */ + if (domain == NULL || strlen(domain) == 1) + return -1; + + if (strcasecmp(cert_domain, domain) == 0) + return 0; + } + + return -1; +} + +int +ssl_check_subject_altname(X509 *cert, char *host) +{ + STACK_OF(GENERAL_NAME) *altname_stack = NULL; + union { struct in_addr ip4; struct in6_addr ip6; } addrbuf; + int addrlen, type; + int count, i; + int rv = -1; + + altname_stack = + X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (altname_stack == NULL) + return -1; + + if (inet_pton(AF_INET, host, &addrbuf) == 1) { + type = GEN_IPADD; + addrlen = 4; + } else if (inet_pton(AF_INET6, host, &addrbuf) == 1) { + type = GEN_IPADD; + addrlen = 16; + } else + type = GEN_DNS; + + count = sk_GENERAL_NAME_num(altname_stack); + for (i = 0; i < count; i++) { + GENERAL_NAME *altname; + + altname = sk_GENERAL_NAME_value(altname_stack, i); + + if (altname->type != type) + continue; + + if (type == GEN_DNS) { + unsigned char *data; + int format; + + format = ASN1_STRING_type(altname->d.dNSName); + if (format == V_ASN1_IA5STRING) { + data = ASN1_STRING_data(altname->d.dNSName); + + if (ASN1_STRING_length(altname->d.dNSName) != + (int)strlen(data)) { + fprintf(ttyout, "%s: NUL byte in " + "subjectAltName, probably a " + "malicious certificate.\n", + getprogname()); + rv = -2; + break; + } + + if (ssl_match_hostname(data, host) == 0) { + rv = 0; + break; + } + } else + fprintf(ttyout, "%s: unhandled subjectAltName " + "dNSName encoding (%d)\n", getprogname(), + format); + + } else if (type == GEN_IPADD) { + unsigned char *data; + int datalen; + + datalen = ASN1_STRING_length(altname->d.iPAddress); + data = ASN1_STRING_data(altname->d.iPAddress); + + if (datalen == addrlen && + memcmp(data, &addrbuf, addrlen) == 0) { + rv = 0; + break; + } + } + } + + sk_GENERAL_NAME_free(altname_stack); + return rv; +} + +int +ssl_check_common_name(X509 *cert, char *host) +{ + X509_NAME *name; + char *common_name = NULL; + int common_name_len; + int rv = -1; + + name = X509_get_subject_name(cert); + if (name == NULL) + goto out; + + common_name_len = X509_NAME_get_text_by_NID(name, NID_commonName, + NULL, 0); + if (common_name_len < 0) + goto out; + + common_name = calloc(common_name_len + 1, 1); + if (common_name == NULL) + goto out; + + X509_NAME_get_text_by_NID(name, NID_commonName, common_name, + common_name_len + 1); + + /* NUL bytes in CN? */ + if (common_name_len != (int)strlen(common_name)) { + fprintf(ttyout, "%s: NUL byte in Common Name field, " + "probably a malicious certificate.\n", getprogname()); + rv = -2; + goto out; + } + + if (ssl_match_hostname(common_name, host) == 0) + rv = 0; +out: + free(common_name); + return rv; +} + +int +ssl_check_hostname(X509 *cert, char *host) +{ + int rv; + + rv = ssl_check_subject_altname(cert, host); + if (rv == 0 || rv == -2) + return rv; + + return ssl_check_common_name(cert, host); +} +#endif + /* * Retrieve URL, via the proxy in $proxyvar if necessary. * Modifies the string argument given. @@ -638,6 +802,25 @@ again: if (SSL_connect(ssl) <= 0) { ERR_print_errors_fp(ttyout); goto cleanup_url_get; + } + if (ssl_verify) { + X509 *cert; + + cert = SSL_get_peer_certificate(ssl); + if (cert == NULL) { + fprintf(ttyout, "%s: no server certificate\n", + getprogname()); + goto cleanup_url_get; + } + + if (ssl_check_hostname(cert, host) != 0) { + fprintf(ttyout, "%s: host `%s' not present in" + " server certificate\n", + getprogname(), host); + goto cleanup_url_get; + } + + X509_free(cert); } } else { fin = fdopen(s, "r+");