| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "kerberos/config_parser.h" |
| |
| #include <vector> |
| |
| #include <base/stl_util.h> |
| #include <base/strings/string_split.h> |
| |
| namespace kerberos { |
| namespace { |
| |
| // See |
| // https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html |
| // for a description of the krb5.conf format. |
| |
| // Directives that are not relations (i.e. key=value). All blacklisted. |
| const char* const kDirectives[] = {"module", "include", "includedir"}; |
| |
| // Whitelisted configuration keys in the [libdefaults] section. |
| const char* const kLibDefaultsWhitelist[] = { |
| "canonicalize", |
| "clockskew", |
| "default_tgs_enctypes", |
| "default_tkt_enctypes", |
| "dns_canonicalize_hostname", |
| "dns_lookup_kdc", |
| "extra_addresses", |
| "forwardable", |
| "ignore_acceptor_hostname", |
| "kdc_default_options", |
| "kdc_timesync", |
| "noaddresses", |
| "permitted_enctypes", |
| "preferred_preauth_types", |
| "proxiable", |
| "rdns", |
| "renew_lifetime", |
| "ticket_lifetime", |
| "udp_preference_limit", |
| }; |
| |
| // Whitelisted configuration keys in the [realms] section. |
| const char* const kRealmsWhitelist[] = { |
| "admin_server", "auth_to_local", "kdc", "kpasswd_server", "master_kdc", |
| }; |
| |
| // Whitelisted sections. Any key in "domain_realm" and "capaths" is accepted. |
| constexpr char kSectionLibdefaults[] = "libdefaults"; |
| constexpr char kSectionRealms[] = "realms"; |
| constexpr char kSectionDomainRealm[] = "domain_realm"; |
| constexpr char kSectionCapaths[] = "capaths"; |
| |
| const char* const kSectionWhitelist[] = {kSectionLibdefaults, kSectionRealms, |
| kSectionDomainRealm, kSectionCapaths}; |
| |
| // List of encryption types fields allowed inside [libdefaults] section. |
| const char* const kEnctypesFields[] = { |
| "default_tgs_enctypes", |
| "default_tkt_enctypes", |
| "permitted_enctypes", |
| }; |
| |
| // List of weak encryption types. |DEFAULT| value is also listed because it |
| // includes both weak and strong types. |
| const char* const kWeakEnctypes[] = { |
| "DEFAULT", |
| "des", |
| "des3", |
| "rc4", |
| "des-cbc-crc", |
| "des-cbc-md4", |
| "des-cbc-md5", |
| "des-cbc-raw", |
| "des-hmac-sha1", |
| "des3-cbc-raw", |
| "des3-cbc-sha1", |
| "des3-hmac-sha1", |
| "des3-cbc-sha1-kd", |
| "arcfour-hmac", |
| "rc4-hmac", |
| "arcfour-hmac-md5", |
| "arcfour-hmac-exp", |
| "rc4-hmac-exp", |
| "arcfour-hmac-md5-exp", |
| }; |
| |
| // List of strong encryption types. |DEFAULT| value is also listed because it |
| // includes both weak and strong types. |
| const char* const kStrongEnctypes[] = { |
| "DEFAULT", "aes", "aes256-cts-hmac-sha1-96", |
| "aes256-cts", "AES-256", "aes128-cts-hmac-sha1-96", |
| "aes128-cts", "AES-128", |
| }; |
| |
| ConfigErrorInfo MakeErrorInfo(ConfigErrorCode code, int line_index) { |
| ConfigErrorInfo error_info; |
| error_info.set_code(code); |
| error_info.set_line_index(line_index); |
| return error_info; |
| } |
| |
| } // namespace |
| |
| ConfigParser::ConfigParser() |
| : libdefaults_whitelist_(std::begin(kLibDefaultsWhitelist), |
| std::end(kLibDefaultsWhitelist)), |
| realms_whitelist_(std::begin(kRealmsWhitelist), |
| std::end(kRealmsWhitelist)), |
| section_whitelist_(std::begin(kSectionWhitelist), |
| std::end(kSectionWhitelist)), |
| enctypes_fields_(std::begin(kEnctypesFields), std::end(kEnctypesFields)), |
| weak_enctypes_(std::begin(kWeakEnctypes), std::end(kWeakEnctypes)), |
| strong_enctypes_(std::begin(kStrongEnctypes), std::end(kStrongEnctypes)) { |
| } |
| |
| ConfigErrorInfo ConfigParser::Validate(const std::string& krb5conf) const { |
| KerberosEncryptionTypes encryption_types; |
| return ParseConfig(krb5conf, &encryption_types); |
| } |
| |
| bool ConfigParser::GetEncryptionTypes( |
| const std::string& krb5conf, |
| KerberosEncryptionTypes* encryption_types) const { |
| ConfigErrorInfo error_info = ParseConfig(krb5conf, encryption_types); |
| return error_info.code() == CONFIG_ERROR_NONE; |
| } |
| |
| // Validates the config and gets encryption types from it. Finds the enctypes |
| // fields and maps the union of the enctypes into one of the buckets of |
| // interest: 'All', 'Strong' or 'Legacy'. If an enctypes field is missing, the |
| // default value for this field ('All') will be used. |
| ConfigErrorInfo ConfigParser::ParseConfig( |
| const std::string& krb5conf, |
| KerberosEncryptionTypes* encryption_types) const { |
| // Variables used to keep track of encryption fields and types on |krc5conf|. |
| StringSet listed_enctypes_fields; |
| bool has_weak_enctype = false; |
| bool has_strong_enctype = false; |
| |
| // Initializes |encryption_types| with the default value in our feature. It |
| // will be replaced at the end of this method, if |krb5conf| is valid. |
| *encryption_types = KerberosEncryptionTypes::kStrong; |
| |
| // Keep empty lines, they're necessary to get the line numbers right. |
| // Note: The MIT krb5 parser does not count \r as newline. |
| const std::vector<std::string> lines = base::SplitString( |
| krb5conf, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| |
| // Level of nested curly braces {}. |
| int group_level = 0; |
| |
| // Opening curly braces '{' can be on the same line and on the next line. This |
| // is set to true if a '{' is expected on the next line. |
| bool expect_opening_curly_brace = false; |
| |
| // Current [section]. |
| std::string current_section; |
| |
| for (size_t line_index = 0; line_index < lines.size(); ++line_index) { |
| // Convert to c_str() and back to get rid of embedded \0's. |
| std::string line = lines.at(line_index).c_str(); |
| |
| // Are we expecting a '{' to open a { group }? |
| if (expect_opening_curly_brace) { |
| if (line.empty() || line.at(0) != '{') { |
| return MakeErrorInfo(CONFIG_ERROR_EXPECTED_OPENING_CURLY_BRACE, |
| line_index); |
| } |
| group_level++; |
| expect_opening_curly_brace = false; |
| continue; |
| } |
| |
| // Skip empty lines. |
| if (line.empty()) |
| continue; |
| |
| // Skip comments. |
| if (line.at(0) == ';' || line.at(0) == '#') |
| continue; |
| |
| // Bail on any |kDirectives|. |
| for (const char* directive : kDirectives) { |
| const int len = strlen(directive); |
| const int line_len = static_cast<int>(line.size()); |
| if (strncmp(line.c_str(), directive, len) == 0 && |
| (len >= line_len || isspace(line.at(len)))) { |
| return MakeErrorInfo(CONFIG_ERROR_KEY_NOT_SUPPORTED, line_index); |
| } |
| } |
| |
| // Check for '}' to close a { group }. |
| if (line.at(0) == '}') { |
| if (group_level == 0) |
| return MakeErrorInfo(CONFIG_ERROR_EXTRA_CURLY_BRACE, line_index); |
| group_level--; |
| continue; |
| } |
| |
| // Check for new [section]. |
| if (line.at(0) == '[') { |
| // Bail if section is within a { group }. |
| if (group_level > 0) |
| return MakeErrorInfo(CONFIG_ERROR_SECTION_NESTED_IN_GROUP, line_index); |
| |
| // Bail if closing bracket is missing or if there's more stuff after the |
| // closing bracket (the final marker '*' is fine). |
| std::vector<std::string> parts = base::SplitString( |
| line, "]", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (parts.size() != 2 || !(parts.at(1).empty() || parts.at(1) == "*")) |
| return MakeErrorInfo(CONFIG_ERROR_SECTION_SYNTAX, line_index); |
| |
| current_section = parts.at(0).substr(1); |
| |
| // Bail if the section is not supported, e.g. [appdefaults]. |
| if (current_section.empty() || |
| !base::Contains(section_whitelist_, current_section)) { |
| return MakeErrorInfo(CONFIG_ERROR_SECTION_NOT_SUPPORTED, line_index); |
| } |
| continue; |
| } |
| |
| // Check for "key = value" or "key = {". |
| std::vector<std::string> parts = base::SplitString( |
| line, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| |
| // Remove final marker. |
| std::string& key = parts.at(0); |
| if (key.back() == '*') |
| key.pop_back(); |
| |
| // No space allowed in the key. |
| if (std::find_if(key.begin(), key.end(), isspace) != key.end()) |
| return MakeErrorInfo(CONFIG_ERROR_RELATION_SYNTAX, line_index); |
| |
| // Final marker must come immediately after key. |
| if (key.empty() || isspace(key.back())) |
| return MakeErrorInfo(CONFIG_ERROR_RELATION_SYNTAX, line_index); |
| |
| // Is there at least one '=' sign? |
| if (parts.size() < 2) |
| return MakeErrorInfo(CONFIG_ERROR_RELATION_SYNTAX, line_index); |
| |
| const std::string& value = parts.at(1); |
| if (parts.size() == 2) { |
| // Check for a '{' to start a group. The '{' could also be on the next |
| // line. If there's anything except whitespace after '{', it counts as |
| // value, not as a group. |
| // Note: If there is more than one '=', it cannot be the start of a group, |
| // e.g. key==\n{. |
| if (value.empty()) { |
| expect_opening_curly_brace = true; |
| continue; |
| } |
| if (value == "{") { |
| group_level++; |
| continue; |
| } |
| } |
| |
| // Check whether we support the key. |
| if (!IsKeySupported(key, current_section, group_level)) |
| return MakeErrorInfo(CONFIG_ERROR_KEY_NOT_SUPPORTED, line_index); |
| |
| // If |key| is a enctypes field in the [libdefaults] section. |
| if (current_section == kSectionLibdefaults && group_level <= 1 && |
| base::Contains(enctypes_fields_, key)) { |
| listed_enctypes_fields.insert(key); |
| |
| // Note: encryption types can be delimited by comma or whitespace. |
| const std::vector<std::string> enctypes = base::SplitString( |
| value, ", ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| for (const std::string& type : enctypes) { |
| has_weak_enctype |= base::Contains(weak_enctypes_, type); |
| has_strong_enctype |= base::Contains(strong_enctypes_, type); |
| } |
| } |
| } |
| |
| // Note: if an enctypes field is missing, the default value is 'All'. |
| if (listed_enctypes_fields.size() < enctypes_fields_.size() || |
| (has_weak_enctype && has_strong_enctype)) { |
| *encryption_types = KerberosEncryptionTypes::kAll; |
| } else if (has_strong_enctype) { |
| *encryption_types = KerberosEncryptionTypes::kStrong; |
| } else { |
| *encryption_types = KerberosEncryptionTypes::kLegacy; |
| } |
| |
| ConfigErrorInfo error_info; |
| error_info.set_code(CONFIG_ERROR_NONE); |
| return error_info; |
| } |
| |
| bool ConfigParser::IsKeySupported(const std::string& key, |
| const std::string& section, |
| int group_level) const { |
| // Bail on anything outside of a section. |
| if (section.empty()) |
| return false; |
| |
| // Enforce only whitelisted libdefaults keys on the root and realm levels: |
| // [libdefaults] |
| // clockskew = 300 |
| // EXAMPLE.COM = { |
| // clockskew = 500 |
| // } |
| if (section == kSectionLibdefaults && group_level <= 1) { |
| return base::Contains(libdefaults_whitelist_, key); |
| } |
| |
| // Enforce only whitelisted realm keys on the root and realm levels: |
| // [realms] |
| // kdc = kerberos1.example.com |
| // EXAMPLE.COM = { |
| // kdc = kerberos2.example.com |
| // } |
| // Not sure if they can actually be at the root level, but just in case... |
| if (section == kSectionRealms && group_level <= 1) |
| return base::Contains(realms_whitelist_, key); |
| |
| // Anything else is fine (all keys of other supported sections). |
| return true; |
| } |
| |
| } // namespace kerberos |