/* based on verify_extract_name from tls_client.c in postfix */ static gboolean irssi_ssl_verify_hostname(X509 *cert, const char *hostname) { int gen_index, gen_count; gboolean matched = FALSE, has_dns_name = FALSE; const char *cert_dns_name; char *cert_subject_cn; const GENERAL_NAME *gn; STACK_OF(GENERAL_NAME) * gens; GString *alt_names; alt_names = g_string_new(""); /* Verify the dNSName(s) in the peer certificate against the hostname. */ gens = X509_get_ext_d2i(cert, NID_subject_alt_name, 0, 0); if (gens) { gen_count = sk_GENERAL_NAME_num(gens); for (gen_index = 0; gen_index < gen_count && !matched; ++gen_index) { gn = sk_GENERAL_NAME_value(gens, gen_index); if (gn->type != GEN_DNS) continue; /* Even if we have an invalid DNS name, we still ultimately ignore the CommonName, because subjectAltName:DNS is present (though malformed). */ has_dns_name = TRUE; cert_dns_name = tls_dns_name(gn); if (cert_dns_name && *cert_dns_name) { g_string_append_printf(alt_names, " '%s'", cert_dns_name); matched = match_hostname(cert_dns_name, hostname); } } /* Free stack *and* member GENERAL_NAME objects */ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); } if (has_dns_name) { if (! matched) { /* The CommonName in the issuer DN is obsolete when SubjectAltName is available. */ g_warning("None of the Subject Alt Names%s in the certificate match hostname '%s'", alt_names->str, hostname); } g_string_free(alt_names, TRUE); return matched; } else { /* No subjectAltNames, look at CommonName */ g_string_free(alt_names, TRUE); cert_subject_cn = tls_text_name(X509_get_subject_name(cert), NID_commonName); if (cert_subject_cn && *cert_subject_cn) { matched = match_hostname(cert_subject_cn, hostname); if (! matched) { g_warning("SSL certificate common name '%s' doesn't match host name '%s'", cert_subject_cn, hostname); } } else { g_warning("No subjectAltNames and no valid common name in certificate"); } free(cert_subject_cn); } return matched; }
/* * returns 0 if we get a negative match for the hostname or the ip * or if we get no match at all. returns 1 otherwise. */ int match_host_and_ip(const char *host, const char *ipaddr, const char *patterns) { int mhost, mip; /* negative ipaddr match */ if ((mip = match_hostname(ipaddr, patterns, strlen(patterns))) == -1) return 0; /* negative hostname match */ if ((mhost = match_hostname(host, patterns, strlen(patterns))) == -1) return 0; /* no match at all */ if (mhost == 0 && mip == 0) return 0; return 1; }
/* This code is based heavily on the example provided in "Secure Programming * Cookbook for C and C++". */ int _mosquitto_verify_certificate_hostname(X509 *cert, const char *hostname) { int i; char name[256]; X509_NAME *subj; bool have_san_dns = false; STACK_OF(GENERAL_NAME) *san; const GENERAL_NAME *nval; const unsigned char *data; unsigned char ipv6_addr[16]; unsigned char ipv4_addr[4]; int ipv6_ok; int ipv4_ok; #ifdef WIN32 ipv6_ok = InetPton(AF_INET6, hostname, &ipv6_addr); ipv4_ok = InetPton(AF_INET, hostname, &ipv4_addr); #else ipv6_ok = inet_pton(AF_INET6, hostname, &ipv6_addr); ipv4_ok = inet_pton(AF_INET, hostname, &ipv4_addr); #endif san = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); if(san){ for(i=0; i<sk_GENERAL_NAME_num(san); i++){ nval = sk_GENERAL_NAME_value(san, i); if(nval->type == GEN_DNS){ data = ASN1_STRING_data(nval->d.dNSName); if(data && match_hostname((char *)data, hostname)){ return 1; } have_san_dns = true; }else if(nval->type == GEN_IPADD){ data = ASN1_STRING_data(nval->d.iPAddress); if(nval->d.iPAddress->length == 4 && ipv4_ok){ if(!memcmp(ipv4_addr, data, 4)){ return 1; } }else if(nval->d.iPAddress->length == 16 && ipv6_ok){ if(!memcmp(ipv6_addr, data, 16)){ return 1; } } } } if(have_san_dns){ /* Only check CN if subjectAltName DNS entry does not exist. */ return 0; } } subj = X509_get_subject_name(cert); if(X509_NAME_get_text_by_NID(subj, NID_commonName, name, sizeof(name)) > 0){ name[sizeof(name) - 1] = '\0'; if (!strcasecmp(name, hostname)) return 1; } return 0; }
static int match_maybe_hashed(const char *host, const char *names, int *was_hashed) { int hashed = *names == HASH_DELIM; const char *hashed_host; size_t nlen = strlen(names); if (was_hashed != NULL) *was_hashed = hashed; if (hashed) { if ((hashed_host = host_hash(host, names, nlen)) == NULL) return -1; return nlen == strlen(hashed_host) && strncmp(hashed_host, names, nlen) == 0; } return match_hostname(host, names) == 1; }
static int access_match(const char *list, const char *addr, const char **host_ptr) { char *tok; char *list2 = strdup(list); if (!list2) out_of_memory("access_match"); strlower(list2); for (tok = strtok(list2, " ,\t"); tok; tok = strtok(NULL, " ,\t")) { if (match_hostname(host_ptr, addr, tok) || match_address(addr, tok)) { free(list2); return 1; } } free(list2); return 0; }
/* * returns 0 if we get a negative match for the hostname or the ip * or if we get no match at all. returns -1 on error, or 1 on * successful match. */ int match_host_and_ip(const char *host, const char *ipaddr, const char *patterns) { int mhost, mip; /* error in ipaddr match */ if ((mip = addr_match_list(ipaddr, patterns)) == -2) return -1; else if (mip == -1) /* negative ip address match */ return 0; /* negative hostname match */ if ((mhost = match_hostname(host, patterns, strlen(patterns))) == -1) return 0; /* no match at all */ if (mhost == 0 && mip == 0) return 0; return 1; }
/* * Check if the user is allowed to log in via ssh. If user is listed * in DenyUsers or one of user's groups is listed in DenyGroups, false * will be returned. If AllowUsers isn't empty and user isn't listed * there, or if AllowGroups isn't empty and one of user's groups isn't * listed there, false will be returned. * If the user's shell is not executable, false will be returned. * Otherwise true is returned. */ int allowed_user(struct passwd * pw) { #ifdef HAVE_LOGIN_CAP extern login_cap_t *lc; int match_name, match_ip; char *cap_hlist, *hp; #endif struct ssh *ssh = active_state; /* XXX */ struct stat st; const char *hostname = NULL, *ipaddr = NULL; int r; u_int i; /* Shouldn't be called if pw is NULL, but better safe than sorry... */ if (!pw || !pw->pw_name) return 0; #ifdef HAVE_LOGIN_CAP hostname = auth_get_canonical_hostname(ssh, options.use_dns); ipaddr = ssh_remote_ipaddr(ssh); lc = login_getclass(pw->pw_class); /* * Check the deny list. */ cap_hlist = login_getcapstr(lc, "host.deny", NULL, NULL); if (cap_hlist != NULL) { hp = strtok(cap_hlist, ","); while (hp != NULL) { match_name = match_hostname(hostname, hp); match_ip = match_hostname(ipaddr, hp); /* * Only a positive match here causes a "deny". */ if (match_name > 0 || match_ip > 0) { free(cap_hlist); login_close(lc); return 0; } hp = strtok(NULL, ","); } free(cap_hlist); } /* * Check the allow list. If the allow list exists, and the * remote host is not in it, the user is implicitly denied. */ cap_hlist = login_getcapstr(lc, "host.allow", NULL, NULL); if (cap_hlist != NULL) { hp = strtok(cap_hlist, ","); if (hp == NULL) { /* Just in case there's an empty string... */ free(cap_hlist); login_close(lc); return 0; } while (hp != NULL) { match_name = match_hostname(hostname, hp); match_ip = match_hostname(ipaddr, hp); /* * Negative match causes an immediate "deny". * Positive match causes us to break out * of the loop (allowing a fallthrough). */ if (match_name < 0 || match_ip < 0) { free(cap_hlist); login_close(lc); return 0; } if (match_name > 0 || match_ip > 0) break; hp = strtok(NULL, ","); } free(cap_hlist); if (hp == NULL) { login_close(lc); return 0; } } login_close(lc); #endif #ifdef USE_PAM if (!options.use_pam) { #endif /* * password/account expiration. */ if (pw->pw_change || pw->pw_expire) { struct timeval tv; (void)gettimeofday(&tv, (struct timezone *)NULL); if (pw->pw_expire) { if (tv.tv_sec >= pw->pw_expire) { logit("User %.100s not allowed because account has expired", pw->pw_name); return 0; /* expired */ } } #ifdef _PASSWORD_CHGNOW if (pw->pw_change == _PASSWORD_CHGNOW) { logit("User %.100s not allowed because password needs to be changed", pw->pw_name); return 0; /* can't force password change (yet) */ } #endif if (pw->pw_change) { if (tv.tv_sec >= pw->pw_change) { logit("User %.100s not allowed because password has expired", pw->pw_name); return 0; /* expired */ } } } #ifdef USE_PAM } #endif /* * Deny if shell does not exist or is not executable unless we * are chrooting. */ /* * XXX Should check to see if it is executable by the * XXX requesting user. --thorpej */ if (options.chroot_directory == NULL || strcasecmp(options.chroot_directory, "none") == 0) { char *shell = xstrdup((pw->pw_shell[0] == '\0') ? _PATH_BSHELL : pw->pw_shell); /* empty = /bin/sh */ if (stat(shell, &st) != 0) { logit("User %.100s not allowed because shell %.100s " "does not exist", pw->pw_name, shell); free(shell); return 0; } if (S_ISREG(st.st_mode) == 0 || (st.st_mode & (S_IXOTH|S_IXUSR|S_IXGRP)) == 0) { logit("User %.100s not allowed because shell %.100s " "is not executable", pw->pw_name, shell); free(shell); return 0; } free(shell); } /* * XXX Consider nuking {Allow,Deny}{Users,Groups}. We have the * XXX login_cap(3) mechanism which covers all other types of * XXX logins, too. */ if (options.num_deny_users > 0 || options.num_allow_users > 0 || options.num_deny_groups > 0 || options.num_allow_groups > 0) { hostname = auth_get_canonical_hostname(ssh, options.use_dns); ipaddr = ssh_remote_ipaddr(ssh); } /* Return false if user is listed in DenyUsers */ if (options.num_deny_users > 0) { for (i = 0; i < options.num_deny_users; i++) { r = match_user(pw->pw_name, hostname, ipaddr, options.deny_users[i]); if (r < 0) { fatal("Invalid DenyUsers pattern \"%.100s\"", options.deny_users[i]); } else if (r != 0) { logit("User %.100s from %.100s not allowed " "because listed in DenyUsers", pw->pw_name, hostname); return 0; } } } /* Return false if AllowUsers isn't empty and user isn't listed there */ if (options.num_allow_users > 0) { for (i = 0; i < options.num_allow_users; i++) { r = match_user(pw->pw_name, hostname, ipaddr, options.allow_users[i]); if (r < 0) { fatal("Invalid AllowUsers pattern \"%.100s\"", options.allow_users[i]); } else if (r == 1) break; } /* i < options.num_allow_users iff we break for loop */ if (i >= options.num_allow_users) { logit("User %.100s from %.100s not allowed because " "not listed in AllowUsers", pw->pw_name, hostname); return 0; } } if (options.num_deny_groups > 0 || options.num_allow_groups > 0) { /* Get the user's group access list (primary and supplementary) */ if (ga_init(pw->pw_name, pw->pw_gid) == 0) { logit("User %.100s from %.100s not allowed because " "not in any group", pw->pw_name, hostname); return 0; } /* Return false if one of user's groups is listed in DenyGroups */ if (options.num_deny_groups > 0) if (ga_match(options.deny_groups, options.num_deny_groups)) { ga_free(); logit("User %.100s from %.100s not allowed " "because a group is listed in DenyGroups", pw->pw_name, hostname); return 0; } /* * Return false if AllowGroups isn't empty and one of user's groups * isn't listed there */ if (options.num_allow_groups > 0) if (!ga_match(options.allow_groups, options.num_allow_groups)) { ga_free(); logit("User %.100s from %.100s not allowed " "because none of user's groups are listed " "in AllowGroups", pw->pw_name, hostname); return 0; } ga_free(); } /* We found no reason not to let this user try to log on... */ return 1; }
void load_hostkeys(struct hostkeys *hostkeys, const char *host, const char *path) { FILE *f; char line[8192]; u_long linenum = 0, num_loaded = 0; char *cp, *cp2, *hashed_host; HostkeyMarker marker; Key *key; int kbits; if ((f = fopen(path, "r")) == NULL) return; debug3("%s: loading entries for host \"%.100s\" from file \"%s\"", __func__, host, path); while (read_keyfile_line(f, path, line, sizeof(line), &linenum) == 0) { cp = line; /* Skip any leading whitespace, comments and empty lines. */ for (; *cp == ' ' || *cp == '\t'; cp++) ; if (!*cp || *cp == '#' || *cp == '\n') continue; if ((marker = check_markers(&cp)) == MRK_ERROR) { verbose("%s: invalid marker at %s:%lu", __func__, path, linenum); continue; } /* Find the end of the host name portion. */ for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) ; /* Check if the host name matches. */ if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1) { if (*cp != HASH_DELIM) continue; hashed_host = host_hash(host, cp, (u_int) (cp2 - cp)); if (hashed_host == NULL) { debug("Invalid hashed host line %lu of %s", linenum, path); continue; } if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0) continue; } /* Got a match. Skip host name. */ cp = cp2; /* * Extract the key from the line. This will skip any leading * whitespace. Ignore badly formatted lines. */ key = key_new(KEY_UNSPEC); if (!hostfile_read_key(&cp, &kbits, key)) { key_free(key); key = key_new(KEY_RSA1); if (!hostfile_read_key(&cp, &kbits, key)) { key_free(key); continue; } } if (!hostfile_check_key(kbits, key, host, path, linenum)) continue; debug3("%s: found %skey type %s in file %s:%lu", __func__, marker == MRK_NONE ? "" : (marker == MRK_CA ? "ca " : "revoked "), key_type(key), path, linenum); hostkeys->entries = xrealloc(hostkeys->entries, hostkeys->num_entries + 1, sizeof(*hostkeys->entries)); hostkeys->entries[hostkeys->num_entries].host = xstrdup(host); hostkeys->entries[hostkeys->num_entries].file = xstrdup(path); hostkeys->entries[hostkeys->num_entries].line = linenum; hostkeys->entries[hostkeys->num_entries].key = key; hostkeys->entries[hostkeys->num_entries].marker = marker; hostkeys->num_entries++; num_loaded++; } debug3("%s: loaded %lu keys", __func__, num_loaded); fclose(f); return; }
static int match_cfg_line(char **condition, int line, const char *user, const char *host, const char *address) { int result = 1; char *arg, *attrib, *cp = *condition; size_t len; if (user == NULL) debug3("checking syntax for 'Match %s'", cp); else debug3("checking match for '%s' user %s host %s addr %s", cp, user ? user : "******", host ? host : "(null)", address ? address : "(null)"); while ((attrib = strdelim(&cp)) && *attrib != '\0') { if ((arg = strdelim(&cp)) == NULL || *arg == '\0') { error("Missing Match criteria for %s", attrib); return -1; } len = strlen(arg); if (strcasecmp(attrib, "user") == 0) { if (!user) { result = 0; continue; } if (match_pattern_list(user, arg, len, 0) != 1) result = 0; else debug("user %.100s matched 'User %.100s' at " "line %d", user, arg, line); } else if (strcasecmp(attrib, "group") == 0) { switch (match_cfg_line_group(arg, line, user)) { case -1: return -1; case 0: result = 0; } } else if (strcasecmp(attrib, "host") == 0) { if (!host) { result = 0; continue; } if (match_hostname(host, arg, len) != 1) result = 0; else debug("connection from %.100s matched 'Host " "%.100s' at line %d", host, arg, line); } else if (strcasecmp(attrib, "address") == 0) { switch (addr_match_list(address, arg)) { case 1: debug("connection from %.100s matched 'Address " "%.100s' at line %d", address, arg, line); break; case 0: case -1: result = 0; break; case -2: return -1; } } else { error("Unsupported Match attribute %s", attrib); return -1; } } if (user != NULL) debug3("match %sfound", result ? "" : "not "); *condition = cp; return result; }
/** * @brief Check if the server is known. * * Checks the user's known host file for a previous connection to the * current server. * * @param[in] session The SSH session to use. * * @returns SSH_SERVER_KNOWN_OK: The server is known and has not changed.\n * SSH_SERVER_KNOWN_CHANGED: The server key has changed. Either you * are under attack or the administrator * changed the key. You HAVE to warn the * user about a possible attack.\n * SSH_SERVER_FOUND_OTHER: The server gave use a key of a type while * we had an other type recorded. It is a * possible attack.\n * SSH_SERVER_NOT_KNOWN: The server is unknown. User should * confirm the MD5 is correct.\n * SSH_SERVER_FILE_NOT_FOUND: The known host file does not exist. The * host is thus unknown. File will be * created if host key is accepted.\n * SSH_SERVER_ERROR: Some error happened. * * @see ssh_get_pubkey_hash() * * @bug There is no current way to remove or modify an entry into the known * host table. */ int ssh_is_server_known(ssh_session session) { FILE *file = NULL; char **tokens; char *host; char *hostport; const char *type; int match; int ret = SSH_SERVER_NOT_KNOWN; enter_function(); if (session->knownhosts == NULL) { if (ssh_options_apply(session) < 0) { ssh_set_error(session, SSH_REQUEST_DENIED, "Can't find a known_hosts file"); leave_function(); return SSH_SERVER_FILE_NOT_FOUND; } } if (session->host == NULL) { ssh_set_error(session, SSH_FATAL, "Can't verify host in known hosts if the hostname isn't known"); leave_function(); return SSH_SERVER_ERROR; } if (session->current_crypto == NULL){ ssh_set_error(session, SSH_FATAL, "ssh_is_host_known called without cryptographic context"); leave_function(); return SSH_SERVER_ERROR; } host = ssh_lowercase(session->host); hostport = ssh_hostport(host,session->port); if (host == NULL || hostport == NULL) { ssh_set_error_oom(session); SAFE_FREE(host); SAFE_FREE(hostport); leave_function(); return SSH_SERVER_ERROR; } do { tokens = ssh_get_knownhost_line(session, &file, session->knownhosts, &type); /* End of file, return the current state */ if (tokens == NULL) { break; } match = match_hashed_host(session, host, tokens[0]); if (match == 0){ match = match_hostname(hostport, tokens[0], strlen(tokens[0])); } if (match == 0) { match = match_hostname(host, tokens[0], strlen(tokens[0])); } if (match == 0) { match = match_hashed_host(session, hostport, tokens[0]); } if (match) { /* We got a match. Now check the key type */ if (strcmp(session->current_crypto->server_pubkey_type, type) != 0) { /* Different type. We don't override the known_changed error which is * more important */ if (ret != SSH_SERVER_KNOWN_CHANGED) ret = SSH_SERVER_FOUND_OTHER; tokens_free(tokens); continue; } /* so we know the key type is good. We may get a good key or a bad key. */ match = check_public_key(session, tokens); tokens_free(tokens); if (match < 0) { ret = SSH_SERVER_ERROR; break; } else if (match == 1) { ret = SSH_SERVER_KNOWN_OK; break; } else if(match == 0) { /* We override the status with the wrong key state */ ret = SSH_SERVER_KNOWN_CHANGED; } } else { tokens_free(tokens); } } while (1); if ( (ret == SSH_SERVER_NOT_KNOWN) && (session->StrictHostKeyChecking == 0) ) { ssh_write_knownhost(session); ret = SSH_SERVER_KNOWN_OK; } SAFE_FREE(host); SAFE_FREE(hostport); if (file != NULL) { fclose(file); } /* Return the current state at end of file */ leave_function(); return ret; }
static HostStatus check_host_in_hostfile_by_key_or_type(const char *filename, const char *host, const Key *key, int keytype, Key *found, int *numret) { FILE *f; char line[8192]; int linenum = 0; u_int kbits; char *cp, *cp2, *hashed_host; HostStatus end_return; debug3("check_host_in_hostfile: filename %s", filename); /* Open the file containing the list of known hosts. */ f = fopen(filename, "r"); if (!f) return HOST_NEW; /* * Return value when the loop terminates. This is set to * HOST_CHANGED if we have seen a different key for the host and have * not found the proper one. */ end_return = HOST_NEW; /* Go through the file. */ while (fgets(line, sizeof(line), f)) { cp = line; linenum++; /* Skip any leading whitespace, comments and empty lines. */ for (; *cp == ' ' || *cp == '\t'; cp++) ; if (!*cp || *cp == '#' || *cp == '\n') continue; /* Find the end of the host name portion. */ for (cp2 = cp; *cp2 && *cp2 != ' ' && *cp2 != '\t'; cp2++) ; /* Check if the host name matches. */ if (match_hostname(host, cp, (u_int) (cp2 - cp)) != 1) { if (*cp != HASH_DELIM) continue; hashed_host = host_hash(host, cp, (u_int) (cp2 - cp)); if (hashed_host == NULL) { debug("Invalid hashed host line %d of %s", linenum, filename); continue; } if (strncmp(hashed_host, cp, (u_int) (cp2 - cp)) != 0) continue; } /* Got a match. Skip host name. */ cp = cp2; /* * Extract the key from the line. This will skip any leading * whitespace. Ignore badly formatted lines. */ if (!hostfile_read_key(&cp, &kbits, found)) continue; if (numret != NULL) *numret = linenum; if (key == NULL) { /* we found a key of the requested type */ if (found->type == keytype) return HOST_FOUND; continue; } if (!hostfile_check_key(kbits, found, host, filename, linenum)) continue; /* Check if the current key is the same as the given key. */ if (key_equal(key, found)) { /* Ok, they match. */ debug3("check_host_in_hostfile: match line %d", linenum); fclose(f); return HOST_OK; } /* * They do not match. We will continue to go through the * file; however, we note that we will not return that it is * new. */ end_return = HOST_CHANGED; } /* Clear variables and close the file. */ fclose(f); /* * Return either HOST_NEW or HOST_CHANGED, depending on whether we * saw a different key for the host. */ return end_return; }
static int ssh_config_parse_line(ssh_session session, const char *line, unsigned int count, int *parsing) { enum ssh_config_opcode_e opcode; const char *p; char *s, *x; char *keyword; char *lowerhost; size_t len; int i; x = s = strdup(line); if (s == NULL) { ssh_set_error_oom(session); return -1; } /* Remove trailing spaces */ for (len = strlen(s) - 1; len > 0; len--) { if (! isspace(s[len])) { break; } s[len] = '\0'; } keyword = ssh_config_get_token(&s); if (keyword == NULL || *keyword == '#' || *keyword == '\0' || *keyword == '\n') { SAFE_FREE(x); return 0; } opcode = ssh_config_get_opcode(keyword); switch (opcode) { case SOC_HOST: *parsing = 0; lowerhost = (session->host) ? ssh_lowercase(session->host) : NULL; for (p = ssh_config_get_str(&s, NULL); p && *p; p = ssh_config_get_str(&s, NULL)) { if (match_hostname(lowerhost, p, strlen(p))) { *parsing = 1; } } SAFE_FREE(lowerhost); break; case SOC_HOSTNAME: p = ssh_config_get_str(&s, NULL); if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_HOST, p); } break; case SOC_PORT: if (session->port == 22) { p = ssh_config_get_str(&s, NULL); if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_PORT_STR, p); } } break; case SOC_USERNAME: if (session->username == NULL) { p = ssh_config_get_str(&s, NULL); if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_USER, p); } } break; case SOC_IDENTITY: p = ssh_config_get_str(&s, NULL); if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p); } break; case SOC_CIPHERS: p = ssh_config_get_str(&s, NULL); if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p); ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p); } break; case SOC_COMPRESSION: i = ssh_config_get_yesno(&s, -1); if (i >= 0 && *parsing) { if (i) { ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes"); } else { ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); } } break; case SOC_PROTOCOL: p = ssh_config_get_str(&s, NULL); if (p && *parsing) { char *a, *b; b = strdup(p); if (b == NULL) { SAFE_FREE(x); ssh_set_error_oom(session); return -1; } i = 0; ssh_options_set(session, SSH_OPTIONS_SSH1, &i); ssh_options_set(session, SSH_OPTIONS_SSH2, &i); for (a = strtok(b, ","); a; a = strtok(NULL, ",")) { switch (atoi(a)) { case 1: i = 1; ssh_options_set(session, SSH_OPTIONS_SSH1, &i); break; case 2: i = 1; ssh_options_set(session, SSH_OPTIONS_SSH2, &i); break; default: break; } } SAFE_FREE(b); } break; case SOC_TIMEOUT: i = ssh_config_get_int(&s, -1); if (i >= 0 && *parsing) { ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &i); } break; case SOC_STRICTHOSTKEYCHECK: i = ssh_config_get_yesno(&s, -1); if (i >= 0 && *parsing) { ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i); } break; case SOC_KNOWNHOSTS: p = ssh_config_get_str(&s, NULL); if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p); } break; case SOC_PROXYCOMMAND: p = ssh_config_get_str(&s, NULL); if (p && *parsing) { ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p); } break; case SOC_UNSUPPORTED: ssh_log(session, SSH_LOG_RARE, "Unsupported option: %s, line: %d\n", keyword, count); break; default: ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d\n", opcode); SAFE_FREE(x); return -1; break; } SAFE_FREE(x); return 0; }
/* Check certificate identity. Returns zero if identity matches; 1 if * identity does not match, or <0 if the certificate had no identity. * If 'identity' is non-NULL, store the malloc-allocated identity in * *identity. */ static int check_identity(const char *hostname, X509 *cert, char **identity) { STACK_OF(GENERAL_NAME) *names; int match = 0, found = 0; names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); if (names) { int n; /* subjectAltName contains a sequence of GeneralNames */ for (n = 0; n < sk_GENERAL_NAME_num(names) && !match; n++) { GENERAL_NAME *nm = sk_GENERAL_NAME_value(names, n); /* handle dNSName and iPAddress name extensions only. */ if (nm->type == GEN_DNS) { char *name = dup_ia5string(nm->d.ia5); if (identity && !found) *identity = ne_strdup(name); match = match_hostname(name, hostname); ne_free(name); found = 1; } else if (nm->type == GEN_IPADD) { /* compare IP address with server IP address. */ ne_inet_addr *ia; if (nm->d.ip->length == 4) ia = ne_iaddr_make(ne_iaddr_ipv4, nm->d.ip->data); else if (nm->d.ip->length == 16) ia = ne_iaddr_make(ne_iaddr_ipv6, nm->d.ip->data); else ia = NULL; /* ne_iaddr_make returns NULL if address type is unsupported */ if (ia != NULL) { /* address type was supported. */ char buf[128]; match = strcmp(hostname, ne_iaddr_print(ia, buf, sizeof buf)) == 0; found = 1; ne_iaddr_free(ia); } else { NE_DEBUG(NE_DBG_SSL, "iPAddress name with unsupported " "address type (length %d), skipped.\n", nm->d.ip->length); } } /* TODO: handle uniformResourceIdentifier too */ } /* free the whole stack. */ sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); } /* Check against the commonName if no DNS alt. names were found, * as per RFC3280. */ if (!found) { X509_NAME *subj = X509_get_subject_name(cert); X509_NAME_ENTRY *entry; ne_buffer *cname = ne_buffer_ncreate(30); int idx = -1, lastidx; /* find the most specific commonName attribute. */ do { lastidx = idx; idx = X509_NAME_get_index_by_NID(subj, NID_commonName, lastidx); } while (idx >= 0); if (lastidx < 0) { /* no commonNmae attributes at all. */ ne_buffer_destroy(cname); return -1; } /* extract the string from the entry */ entry = X509_NAME_get_entry(subj, lastidx); if (append_dirstring(cname, X509_NAME_ENTRY_get_data(entry))) { ne_buffer_destroy(cname); return -1; } if (identity) *identity = ne_strdup(cname->data); match = match_hostname(cname->data, hostname); ne_buffer_destroy(cname); } NE_DEBUG(NE_DBG_SSL, "Identity match for '%s': %s\n", hostname, match ? "good" : "bad"); return match ? 0 : 1; }
/* Check certificate identity. Returns zero if identity matches; 1 if * identity does not match, or <0 if the certificate had no identity. * If 'identity' is non-NULL, store the malloc-allocated identity in * *identity. If 'server' is non-NULL, it must be the network address * of the server in use, and identity must be NULL. */ static int check_identity(const char *hostname, gnutls_x509_crt cert, char **identity) { char name[255]; unsigned int critical; int ret, seq = 0; int match = 0, found = 0; size_t len; do { len = sizeof name; ret = gnutls_x509_crt_get_subject_alt_name(cert, seq, name, &len, &critical); switch (ret) { case GNUTLS_SAN_DNSNAME: if (identity && !found) *identity = ne_strdup(name); match = match_hostname(name, hostname); found = 1; break; case GNUTLS_SAN_IPADDRESS: { ne_inet_addr *ia; if (len == 4) ia = ne_iaddr_make(ne_iaddr_ipv4, (unsigned char *)name); else if (len == 16) ia = ne_iaddr_make(ne_iaddr_ipv6, (unsigned char *)name); else ia = NULL; if (ia) { char buf[128]; match = strcmp(hostname, ne_iaddr_print(ia, buf, sizeof buf)) == 0; if (identity) *identity = ne_strdup(buf); found = 1; ne_iaddr_free(ia); } else { NE_DEBUG(NE_DBG_SSL, "iPAddress name with unsupported " "address type (length %" NE_FMT_SIZE_T "), skipped.\n", len); } } break; default: break; } seq++; } while (!match && ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); /* Check against the commonName if no DNS alt. names were found, * as per RFC3280. */ if (!found) { seq = oid_find_highest_index(cert, 1, GNUTLS_OID_X520_COMMON_NAME); if (seq >= 0) { len = sizeof name; name[0] = '\0'; ret = gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, seq, 0, name, &len); if (ret == 0) { if (identity) *identity = ne_strdup(name); match = match_hostname(name, hostname); } } else { return -1; } } NE_DEBUG(NE_DBG_SSL, "Identity match: %s\n", match ? "good" : "bad"); return match ? 0 : 1; }
static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert, const TLS_CLIENT_START_PROPS *props) { int i; int r; int matched = 0; int dnsname_match; int verify_peername = 0; int log_certmatch; int verbose; const char *dnsname; const GENERAL_NAME *gn; STACK_OF(GENERAL_NAME) * gens; /* * On exit both peer_CN and issuer_CN should be set. */ TLScontext->issuer_CN = tls_issuer_CN(peercert, TLScontext); /* * Is the certificate trust chain valid and trusted? */ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED; if (TLS_CERT_IS_TRUSTED(TLScontext) && props->tls_level >= TLS_LEV_VERIFY) verify_peername = 1; /* Force cert processing so we can log the data? */ log_certmatch = TLScontext->log_mask & TLS_LOG_CERTMATCH; /* Log cert details when processing? */ verbose = log_certmatch || (TLScontext->log_mask & TLS_LOG_VERBOSE); if (verify_peername || log_certmatch) { /* * Verify the dNSName(s) in the peer certificate against the nexthop * and hostname. * * If DNS names are present, we use the first matching (or else simply * the first) DNS name as the subject CN. The CommonName in the * issuer DN is obsolete when SubjectAltName is available. This * yields much less surprising logs, because we log the name we * verified or a name we checked and failed to match. * * XXX: The nexthop and host name may both be the same network address * rather than a DNS name. In this case we really should be looking * for GEN_IPADD entries, not GEN_DNS entries. * * XXX: In ideal world the caller who used the address to build the * connection would tell us that the nexthop is the connection * address, but if that is not practical, we can parse the nexthop * again here. */ gens = X509_get_ext_d2i(peercert, NID_subject_alt_name, 0, 0); if (gens) { r = sk_GENERAL_NAME_num(gens); for (i = 0; i < r; ++i) { gn = sk_GENERAL_NAME_value(gens, i); if (gn->type != GEN_DNS) continue; /* * Even if we have an invalid DNS name, we still ultimately * ignore the CommonName, because subjectAltName:DNS is * present (though malformed). Replace any previous peer_CN * if empty or we get a match. * * We always set at least an empty peer_CN if the ALTNAME cert * flag is set. If not, we set peer_CN from the cert * CommonName below, so peer_CN is always non-null on return. */ TLScontext->peer_status |= TLS_CERT_FLAG_ALTNAME; dnsname = tls_dns_name(gn, TLScontext); if (dnsname && *dnsname) { if ((dnsname_match = match_hostname(dnsname, props)) != 0) matched++; /* Keep the first matched name. */ if (TLScontext->peer_CN && ((dnsname_match && matched == 1) || *TLScontext->peer_CN == 0)) { myfree(TLScontext->peer_CN); TLScontext->peer_CN = 0; } if (verbose) msg_info("%s: %ssubjectAltName: %s", props->namaddr, dnsname_match ? "Matched " : "", dnsname); } if (TLScontext->peer_CN == 0) TLScontext->peer_CN = mystrdup(dnsname ? dnsname : ""); if (matched && !log_certmatch) break; } if (verify_peername && matched) TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED; /* * (Sam Rushing, Ironport) Free stack *and* member GENERAL_NAME * objects */ sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free); } /* * No subjectAltNames, peer_CN is taken from CommonName. */ if (TLScontext->peer_CN == 0) { TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext); if (*TLScontext->peer_CN) matched = match_hostname(TLScontext->peer_CN, props); if (verify_peername && matched) TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED; if (verbose) msg_info("%s %sCommonName %s", props->namaddr, matched ? "Matched " : "", TLScontext->peer_CN); } else if (verbose) { char *tmpcn = tls_peer_CN(peercert, TLScontext); /* * Though the CommonName was superceded by a subjectAltName, log * it when certificate match debugging was requested. */ msg_info("%s CommonName %s", TLScontext->namaddr, tmpcn); myfree(tmpcn); } } else TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext); /* * Give them a clue. Problems with trust chain verification were logged * when the session was first negotiated, before the session was stored * into the cache. We don't want mystery failures, so log the fact the * real problem is to be found in the past. */ if (TLScontext->session_reused && !TLS_CERT_IS_TRUSTED(TLScontext) && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) msg_info("%s: re-using session with untrusted certificate, " "look for details earlier in the log", props->namaddr); }
/* cf. RFC2818 and RFC2459 */ static int match_cert_hostname(struct openconnect_info *vpninfo, X509 *peer_cert) { STACK_OF(GENERAL_NAME) *altnames; X509_NAME *subjname; ASN1_STRING *subjasn1; char *subjstr = NULL; int addrlen = 0; int i, altdns = 0; char addrbuf[sizeof(struct in6_addr)]; int ret; /* Allow GEN_IP in the certificate only if we actually connected by IP address rather than by name. */ if (inet_pton(AF_INET, vpninfo->hostname, addrbuf) > 0) addrlen = 4; else if (inet_pton(AF_INET6, vpninfo->hostname, addrbuf) > 0) addrlen = 16; else if (vpninfo->hostname[0] == '[' && vpninfo->hostname[strlen(vpninfo->hostname)-1] == ']') { char *p = &vpninfo->hostname[strlen(vpninfo->hostname)-1]; *p = 0; if (inet_pton(AF_INET6, vpninfo->hostname + 1, addrbuf) > 0) addrlen = 16; *p = ']'; } altnames = X509_get_ext_d2i(peer_cert, NID_subject_alt_name, NULL, NULL); for (i = 0; i < sk_GENERAL_NAME_num(altnames); i++) { const GENERAL_NAME *this = sk_GENERAL_NAME_value(altnames, i); if (this->type == GEN_DNS) { char *str; int len = ASN1_STRING_to_UTF8((void *)&str, this->d.ia5); if (len < 0) continue; altdns = 1; /* We don't like names with embedded NUL */ if (strlen(str) != len) continue; if (!match_hostname(vpninfo->hostname, str)) { vpn_progress(vpninfo, PRG_TRACE, _("Matched DNS altname '%s'\n"), str); GENERAL_NAMES_free(altnames); OPENSSL_free(str); return 0; } else { vpn_progress(vpninfo, PRG_TRACE, _("No match for altname '%s'\n"), str); } OPENSSL_free(str); } else if (this->type == GEN_IPADD && addrlen) { char host[80]; int family; if (this->d.ip->length == 4) { family = AF_INET; } else if (this->d.ip->length == 16) { family = AF_INET6; } else { vpn_progress(vpninfo, PRG_ERR, _("Certificate has GEN_IPADD altname with bogus length %d\n"), this->d.ip->length); continue; } /* We only do this for the debug messages */ inet_ntop(family, this->d.ip->data, host, sizeof(host)); if (this->d.ip->length == addrlen && !memcmp(addrbuf, this->d.ip->data, addrlen)) { vpn_progress(vpninfo, PRG_TRACE, _("Matched %s address '%s'\n"), (family == AF_INET6) ? "IPv6" : "IPv4", host); GENERAL_NAMES_free(altnames); return 0; } else { vpn_progress(vpninfo, PRG_TRACE, _("No match for %s address '%s'\n"), (family == AF_INET6) ? "IPv6" : "IPv4", host); } } else if (this->type == GEN_URI) { char *str; char *url_proto, *url_host, *url_path, *url_host2; int url_port; int len = ASN1_STRING_to_UTF8((void *)&str, this->d.ia5); if (len < 0) continue; /* We don't like names with embedded NUL */ if (strlen(str) != len) continue; if (internal_parse_url(str, &url_proto, &url_host, &url_port, &url_path, 0)) { OPENSSL_free(str); continue; } if (!url_proto || strcasecmp(url_proto, "https")) goto no_uri_match; if (url_port != vpninfo->port) goto no_uri_match; /* Leave url_host as it was so that it can be freed */ url_host2 = url_host; if (addrlen == 16 && vpninfo->hostname[0] != '[' && url_host[0] == '[' && url_host[strlen(url_host)-1] == ']') { /* Cope with https://[IPv6]/ when the hostname is bare IPv6 */ url_host[strlen(url_host)-1] = 0; url_host2++; } if (strcasecmp(vpninfo->hostname, url_host2)) goto no_uri_match; if (url_path) { vpn_progress(vpninfo, PRG_TRACE, _("URI '%s' has non-empty path; ignoring\n"), str); goto no_uri_match_silent; } vpn_progress(vpninfo, PRG_TRACE, _("Matched URI '%s'\n"), str); free(url_proto); free(url_host); free(url_path); OPENSSL_free(str); GENERAL_NAMES_free(altnames); return 0; no_uri_match: vpn_progress(vpninfo, PRG_TRACE, _("No match for URI '%s'\n"), str); no_uri_match_silent: free(url_proto); free(url_host); free(url_path); OPENSSL_free(str); } } GENERAL_NAMES_free(altnames); /* According to RFC2818, we don't use the legacy subject name if there was an altname with DNS type. */ if (altdns) { vpn_progress(vpninfo, PRG_ERR, _("No altname in peer cert matched '%s'\n"), vpninfo->hostname); return -EINVAL; } subjname = X509_get_subject_name(peer_cert); if (!subjname) { vpn_progress(vpninfo, PRG_ERR, _("No subject name in peer cert!\n")); return -EINVAL; } /* Find the _last_ (most specific) commonName */ i = -1; while (1) { int j = X509_NAME_get_index_by_NID(subjname, NID_commonName, i); if (j >= 0) i = j; else break; } subjasn1 = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(subjname, i)); i = ASN1_STRING_to_UTF8((void *)&subjstr, subjasn1); if (!subjstr || strlen(subjstr) != i) { vpn_progress(vpninfo, PRG_ERR, _("Failed to parse subject name in peer cert\n")); return -EINVAL; } ret = 0; if (match_hostname(vpninfo->hostname, subjstr)) { vpn_progress(vpninfo, PRG_ERR, _("Peer cert subject mismatch ('%s' != '%s')\n"), subjstr, vpninfo->hostname); ret = -EINVAL; } else { vpn_progress(vpninfo, PRG_TRACE, _("Matched peer certificate subject name '%s'\n"), subjstr); } OPENSSL_free(subjstr); return ret; }
/* static int check_identity(const nussl_uri *server, X509 *cert, char **identity) */ static int check_identity(const char *expected_hostname, X509 * cert, char **identity) { STACK_OF(GENERAL_NAME) * names; int match = 0, found = 0; char *found_hostname = NULL; /* hostname = server ? server->host : ""; */ names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); /* if expected_hostname is NULL, do not check subjectAltName, * we are only looking for the CN */ if (names && expected_hostname != NULL) { int n; /* subjectAltName contains a sequence of GeneralNames */ for (n = 0; n < sk_GENERAL_NAME_num(names) && !match; n++) { GENERAL_NAME *nm = sk_GENERAL_NAME_value(names, n); /* handle dNSName and iPAddress name extensions only. */ if (nm->type == GEN_DNS) { found_hostname = dup_ia5string(nm->d.ia5); match = match_hostname(found_hostname, expected_hostname); if (match) { found = 1; if (identity) *identity = nussl_strdup(found_hostname); } } else if (nm->type == GEN_IPADD) { /* compare IP address with server IP address. */ nussl_inet_addr *ia; if (nm->d.ip->length == 4) ia = nussl_iaddr_make(nussl_iaddr_ipv4, nm->d.ip->data); else if (nm->d.ip->length == 16) ia = nussl_iaddr_make(nussl_iaddr_ipv6, nm->d.ip->data); else ia = NULL; /* nussl_iaddr_make returns NULL if address type is unsupported */ /* if (ia != NULL) { /\* address type was supported. *\/ */ /* char buf[128]; */ /* match = strcmp(hostname, */ /* nussl_iaddr_print(ia, buf, sizeof buf)) == 0; */ /* found = 1; */ /* nussl_iaddr_free(ia); */ /* } else { */ /* NUSSL_DEBUG(NUSSL_DBG_SSL, "iPAddress name with unsupported " */ /* "address type (length %d), skipped.\n", */ /* nm->d.ip->length); */ /* } */ } /* else if (nm->type == GEN_URI) { */ /* char *name = dup_ia5string(nm->d.ia5); */ /* nussl_uri uri; */ /* if (nussl_uri_parse(name, &uri) == 0 && uri.host && uri.scheme) { */ /* nussl_uri tmp; */ /* if (identity && !found) *identity = nussl_strdup(name); */ /* found = 1; */ /* if (server) { */ /* /\* For comparison purposes, all that matters is */ /* * host, scheme and port; ignore the rest. *\/ */ /* memset(&tmp, 0, sizeof tmp); */ /* tmp.host = uri.host; */ /* tmp.scheme = uri.scheme; */ /* tmp.port = uri.port; */ /* match = nussl_uri_cmp(server, &tmp) == 0; */ /* } */ /* } */ /* nussl_uri_free(&uri); */ /* nussl_free(name); */ /* } */ } } /* free the whole stack. */ if (names) sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); /* Check against the commonName if no DNS alt. names were found, * as per RFC3280. */ if (!found) { X509_NAME *subj = X509_get_subject_name(cert); X509_NAME_ENTRY *entry; nussl_buffer *cname = nussl_buffer_ncreate(30); int idx = -1, lastidx; /* find the most specific commonName attribute. */ do { lastidx = idx; idx = X509_NAME_get_index_by_NID(subj, NID_commonName, lastidx); } while (idx >= 0); /* check commonName attribute if present */ if (lastidx >= 0) { /* extract the string from the entry */ entry = X509_NAME_get_entry(subj, lastidx); if (append_dirstring(cname, X509_NAME_ENTRY_get_data(entry))) { nussl_buffer_destroy(cname); return -1; } found_hostname = nussl_strdup(cname->data); if (expected_hostname != NULL) match = match_hostname(found_hostname, expected_hostname); } nussl_buffer_destroy(cname); } if (found_hostname == NULL) return 1; /*NUSSL_DEBUG(NUSSL_DBG_SSL, "Identity match for '%s' (identity: %s): %s\n", expected_hostname, found_hostname, match ? "good" : "bad");*/ if (identity != NULL) *identity = nussl_strdup(found_hostname); nussl_free(found_hostname); return match ? 0 : 1; }