Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 1 | // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Felipe Andrade | a0cbde7 | 2020-04-01 15:40:10 +0200 | [diff] [blame] | 5 | #include "kerberos/config_parser.h" |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 6 | |
| 7 | #include <vector> |
| 8 | |
| 9 | #include <base/stl_util.h> |
| 10 | #include <base/strings/string_split.h> |
| 11 | |
| 12 | namespace kerberos { |
| 13 | namespace { |
| 14 | |
| 15 | // See |
| 16 | // https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html |
| 17 | // for a description of the krb5.conf format. |
| 18 | |
| 19 | // Directives that are not relations (i.e. key=value). All blacklisted. |
| 20 | const char* const kDirectives[] = {"module", "include", "includedir"}; |
| 21 | |
| 22 | // Whitelisted configuration keys in the [libdefaults] section. |
| 23 | const char* const kLibDefaultsWhitelist[] = { |
| 24 | "canonicalize", |
| 25 | "clockskew", |
| 26 | "default_tgs_enctypes", |
| 27 | "default_tkt_enctypes", |
| 28 | "dns_canonicalize_hostname", |
| 29 | "dns_lookup_kdc", |
| 30 | "extra_addresses", |
| 31 | "forwardable", |
| 32 | "ignore_acceptor_hostname", |
| 33 | "kdc_default_options", |
| 34 | "kdc_timesync", |
| 35 | "noaddresses", |
| 36 | "permitted_enctypes", |
| 37 | "preferred_preauth_types", |
| 38 | "proxiable", |
| 39 | "rdns", |
| 40 | "renew_lifetime", |
| 41 | "ticket_lifetime", |
| 42 | "udp_preference_limit", |
| 43 | }; |
| 44 | |
| 45 | // Whitelisted configuration keys in the [realms] section. |
| 46 | const char* const kRealmsWhitelist[] = { |
| 47 | "admin_server", "auth_to_local", "kdc", "kpasswd_server", "master_kdc", |
| 48 | }; |
| 49 | |
| 50 | // Whitelisted sections. Any key in "domain_realm" and "capaths" is accepted. |
| 51 | constexpr char kSectionLibdefaults[] = "libdefaults"; |
| 52 | constexpr char kSectionRealms[] = "realms"; |
| 53 | constexpr char kSectionDomainRealm[] = "domain_realm"; |
| 54 | constexpr char kSectionCapaths[] = "capaths"; |
| 55 | |
| 56 | const char* const kSectionWhitelist[] = {kSectionLibdefaults, kSectionRealms, |
| 57 | kSectionDomainRealm, kSectionCapaths}; |
| 58 | |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 59 | // List of encryption types fields allowed inside [libdefaults] section. |
| 60 | const char* const kEnctypesFields[] = { |
| 61 | "default_tgs_enctypes", |
| 62 | "default_tkt_enctypes", |
| 63 | "permitted_enctypes", |
| 64 | }; |
| 65 | |
| 66 | // List of weak encryption types. |DEFAULT| value is also listed because it |
| 67 | // includes both weak and strong types. |
| 68 | const char* const kWeakEnctypes[] = { |
| 69 | "DEFAULT", |
| 70 | "des", |
| 71 | "des3", |
| 72 | "rc4", |
| 73 | "des-cbc-crc", |
| 74 | "des-cbc-md4", |
| 75 | "des-cbc-md5", |
| 76 | "des-cbc-raw", |
| 77 | "des-hmac-sha1", |
| 78 | "des3-cbc-raw", |
| 79 | "des3-cbc-sha1", |
| 80 | "des3-hmac-sha1", |
| 81 | "des3-cbc-sha1-kd", |
| 82 | "arcfour-hmac", |
| 83 | "rc4-hmac", |
| 84 | "arcfour-hmac-md5", |
| 85 | "arcfour-hmac-exp", |
| 86 | "rc4-hmac-exp", |
| 87 | "arcfour-hmac-md5-exp", |
| 88 | }; |
| 89 | |
| 90 | // List of strong encryption types. |DEFAULT| value is also listed because it |
| 91 | // includes both weak and strong types. |
| 92 | const char* const kStrongEnctypes[] = { |
| 93 | "DEFAULT", "aes", "aes256-cts-hmac-sha1-96", |
| 94 | "aes256-cts", "AES-256", "aes128-cts-hmac-sha1-96", |
| 95 | "aes128-cts", "AES-128", |
| 96 | }; |
| 97 | |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 98 | ConfigErrorInfo MakeErrorInfo(ConfigErrorCode code, int line_index) { |
| 99 | ConfigErrorInfo error_info; |
| 100 | error_info.set_code(code); |
| 101 | error_info.set_line_index(line_index); |
| 102 | return error_info; |
| 103 | } |
| 104 | |
| 105 | } // namespace |
| 106 | |
Felipe Andrade | a0cbde7 | 2020-04-01 15:40:10 +0200 | [diff] [blame] | 107 | ConfigParser::ConfigParser() |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 108 | : libdefaults_whitelist_(std::begin(kLibDefaultsWhitelist), |
| 109 | std::end(kLibDefaultsWhitelist)), |
| 110 | realms_whitelist_(std::begin(kRealmsWhitelist), |
| 111 | std::end(kRealmsWhitelist)), |
| 112 | section_whitelist_(std::begin(kSectionWhitelist), |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 113 | std::end(kSectionWhitelist)), |
| 114 | enctypes_fields_(std::begin(kEnctypesFields), std::end(kEnctypesFields)), |
| 115 | weak_enctypes_(std::begin(kWeakEnctypes), std::end(kWeakEnctypes)), |
| 116 | strong_enctypes_(std::begin(kStrongEnctypes), std::end(kStrongEnctypes)) { |
| 117 | } |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 118 | |
Felipe Andrade | a0cbde7 | 2020-04-01 15:40:10 +0200 | [diff] [blame] | 119 | ConfigErrorInfo ConfigParser::Validate(const std::string& krb5conf) const { |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 120 | KerberosEncryptionTypes encryption_types; |
| 121 | return ParseConfig(krb5conf, &encryption_types); |
| 122 | } |
| 123 | |
Felipe Andrade | 90cb84e | 2020-04-07 20:28:33 +0200 | [diff] [blame] | 124 | bool ConfigParser::GetEncryptionTypes( |
| 125 | const std::string& krb5conf, |
| 126 | KerberosEncryptionTypes* encryption_types) const { |
| 127 | ConfigErrorInfo error_info = ParseConfig(krb5conf, encryption_types); |
| 128 | return error_info.code() == CONFIG_ERROR_NONE; |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 129 | } |
| 130 | |
| 131 | // Validates the config and gets encryption types from it. Finds the enctypes |
| 132 | // fields and maps the union of the enctypes into one of the buckets of |
| 133 | // interest: 'All', 'Strong' or 'Legacy'. If an enctypes field is missing, the |
| 134 | // default value for this field ('All') will be used. |
Felipe Andrade | a0cbde7 | 2020-04-01 15:40:10 +0200 | [diff] [blame] | 135 | ConfigErrorInfo ConfigParser::ParseConfig( |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 136 | const std::string& krb5conf, |
| 137 | KerberosEncryptionTypes* encryption_types) const { |
| 138 | // Variables used to keep track of encryption fields and types on |krc5conf|. |
Felipe Andrade | f097d25 | 2020-04-08 15:10:50 +0200 | [diff] [blame] | 139 | StringSet listed_enctypes_fields; |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 140 | bool has_weak_enctype = false; |
| 141 | bool has_strong_enctype = false; |
| 142 | |
Felipe Andrade | 90cb84e | 2020-04-07 20:28:33 +0200 | [diff] [blame] | 143 | // Initializes |encryption_types| with the default value in our feature. It |
| 144 | // will be replaced at the end of this method, if |krb5conf| is valid. |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 145 | *encryption_types = KerberosEncryptionTypes::kStrong; |
| 146 | |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 147 | // Keep empty lines, they're necessary to get the line numbers right. |
Lutz Justen | 70496c1 | 2019-07-24 11:11:55 +0200 | [diff] [blame] | 148 | // Note: The MIT krb5 parser does not count \r as newline. |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 149 | const std::vector<std::string> lines = base::SplitString( |
Lutz Justen | 70496c1 | 2019-07-24 11:11:55 +0200 | [diff] [blame] | 150 | krb5conf, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 151 | |
| 152 | // Level of nested curly braces {}. |
| 153 | int group_level = 0; |
| 154 | |
| 155 | // Opening curly braces '{' can be on the same line and on the next line. This |
| 156 | // is set to true if a '{' is expected on the next line. |
| 157 | bool expect_opening_curly_brace = false; |
| 158 | |
| 159 | // Current [section]. |
| 160 | std::string current_section; |
| 161 | |
| 162 | for (size_t line_index = 0; line_index < lines.size(); ++line_index) { |
Lutz Justen | 70496c1 | 2019-07-24 11:11:55 +0200 | [diff] [blame] | 163 | // Convert to c_str() and back to get rid of embedded \0's. |
| 164 | std::string line = lines.at(line_index).c_str(); |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 165 | |
| 166 | // Are we expecting a '{' to open a { group }? |
| 167 | if (expect_opening_curly_brace) { |
| 168 | if (line.empty() || line.at(0) != '{') { |
| 169 | return MakeErrorInfo(CONFIG_ERROR_EXPECTED_OPENING_CURLY_BRACE, |
| 170 | line_index); |
| 171 | } |
| 172 | group_level++; |
| 173 | expect_opening_curly_brace = false; |
| 174 | continue; |
| 175 | } |
| 176 | |
| 177 | // Skip empty lines. |
| 178 | if (line.empty()) |
| 179 | continue; |
| 180 | |
| 181 | // Skip comments. |
| 182 | if (line.at(0) == ';' || line.at(0) == '#') |
| 183 | continue; |
| 184 | |
| 185 | // Bail on any |kDirectives|. |
| 186 | for (const char* directive : kDirectives) { |
| 187 | const int len = strlen(directive); |
Lutz Justen | 70496c1 | 2019-07-24 11:11:55 +0200 | [diff] [blame] | 188 | const int line_len = static_cast<int>(line.size()); |
| 189 | if (strncmp(line.c_str(), directive, len) == 0 && |
| 190 | (len >= line_len || isspace(line.at(len)))) { |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 191 | return MakeErrorInfo(CONFIG_ERROR_KEY_NOT_SUPPORTED, line_index); |
Lutz Justen | 70496c1 | 2019-07-24 11:11:55 +0200 | [diff] [blame] | 192 | } |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 193 | } |
| 194 | |
| 195 | // Check for '}' to close a { group }. |
| 196 | if (line.at(0) == '}') { |
| 197 | if (group_level == 0) |
| 198 | return MakeErrorInfo(CONFIG_ERROR_EXTRA_CURLY_BRACE, line_index); |
| 199 | group_level--; |
| 200 | continue; |
| 201 | } |
| 202 | |
| 203 | // Check for new [section]. |
| 204 | if (line.at(0) == '[') { |
| 205 | // Bail if section is within a { group }. |
| 206 | if (group_level > 0) |
| 207 | return MakeErrorInfo(CONFIG_ERROR_SECTION_NESTED_IN_GROUP, line_index); |
| 208 | |
| 209 | // Bail if closing bracket is missing or if there's more stuff after the |
| 210 | // closing bracket (the final marker '*' is fine). |
| 211 | std::vector<std::string> parts = base::SplitString( |
| 212 | line, "]", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| 213 | if (parts.size() != 2 || !(parts.at(1).empty() || parts.at(1) == "*")) |
| 214 | return MakeErrorInfo(CONFIG_ERROR_SECTION_SYNTAX, line_index); |
| 215 | |
| 216 | current_section = parts.at(0).substr(1); |
| 217 | |
| 218 | // Bail if the section is not supported, e.g. [appdefaults]. |
| 219 | if (current_section.empty() || |
Qijiang Fan | 5243904 | 2020-06-17 15:34:38 +0900 | [diff] [blame^] | 220 | !base::Contains(section_whitelist_, current_section)) { |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 221 | return MakeErrorInfo(CONFIG_ERROR_SECTION_NOT_SUPPORTED, line_index); |
| 222 | } |
| 223 | continue; |
| 224 | } |
| 225 | |
| 226 | // Check for "key = value" or "key = {". |
| 227 | std::vector<std::string> parts = base::SplitString( |
| 228 | line, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| 229 | |
| 230 | // Remove final marker. |
| 231 | std::string& key = parts.at(0); |
| 232 | if (key.back() == '*') |
| 233 | key.pop_back(); |
| 234 | |
Lutz Justen | 70496c1 | 2019-07-24 11:11:55 +0200 | [diff] [blame] | 235 | // No space allowed in the key. |
| 236 | if (std::find_if(key.begin(), key.end(), isspace) != key.end()) |
| 237 | return MakeErrorInfo(CONFIG_ERROR_RELATION_SYNTAX, line_index); |
| 238 | |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 239 | // Final marker must come immediately after key. |
| 240 | if (key.empty() || isspace(key.back())) |
| 241 | return MakeErrorInfo(CONFIG_ERROR_RELATION_SYNTAX, line_index); |
| 242 | |
| 243 | // Is there at least one '=' sign? |
| 244 | if (parts.size() < 2) |
| 245 | return MakeErrorInfo(CONFIG_ERROR_RELATION_SYNTAX, line_index); |
| 246 | |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 247 | const std::string& value = parts.at(1); |
Lutz Justen | 70496c1 | 2019-07-24 11:11:55 +0200 | [diff] [blame] | 248 | if (parts.size() == 2) { |
| 249 | // Check for a '{' to start a group. The '{' could also be on the next |
| 250 | // line. If there's anything except whitespace after '{', it counts as |
| 251 | // value, not as a group. |
| 252 | // Note: If there is more than one '=', it cannot be the start of a group, |
| 253 | // e.g. key==\n{. |
Lutz Justen | 70496c1 | 2019-07-24 11:11:55 +0200 | [diff] [blame] | 254 | if (value.empty()) { |
| 255 | expect_opening_curly_brace = true; |
| 256 | continue; |
| 257 | } |
| 258 | if (value == "{") { |
| 259 | group_level++; |
| 260 | continue; |
| 261 | } |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 262 | } |
| 263 | |
| 264 | // Check whether we support the key. |
| 265 | if (!IsKeySupported(key, current_section, group_level)) |
| 266 | return MakeErrorInfo(CONFIG_ERROR_KEY_NOT_SUPPORTED, line_index); |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 267 | |
| 268 | // If |key| is a enctypes field in the [libdefaults] section. |
| 269 | if (current_section == kSectionLibdefaults && group_level <= 1 && |
Qijiang Fan | 5243904 | 2020-06-17 15:34:38 +0900 | [diff] [blame^] | 270 | base::Contains(enctypes_fields_, key)) { |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 271 | listed_enctypes_fields.insert(key); |
| 272 | |
| 273 | // Note: encryption types can be delimited by comma or whitespace. |
| 274 | const std::vector<std::string> enctypes = base::SplitString( |
| 275 | value, ", ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| 276 | |
| 277 | for (const std::string& type : enctypes) { |
Qijiang Fan | 5243904 | 2020-06-17 15:34:38 +0900 | [diff] [blame^] | 278 | has_weak_enctype |= base::Contains(weak_enctypes_, type); |
| 279 | has_strong_enctype |= base::Contains(strong_enctypes_, type); |
Felipe Andrade | 66aaf6b | 2020-03-24 13:05:57 +0100 | [diff] [blame] | 280 | } |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | // Note: if an enctypes field is missing, the default value is 'All'. |
| 285 | if (listed_enctypes_fields.size() < enctypes_fields_.size() || |
| 286 | (has_weak_enctype && has_strong_enctype)) { |
| 287 | *encryption_types = KerberosEncryptionTypes::kAll; |
| 288 | } else if (has_strong_enctype) { |
| 289 | *encryption_types = KerberosEncryptionTypes::kStrong; |
| 290 | } else { |
| 291 | *encryption_types = KerberosEncryptionTypes::kLegacy; |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 292 | } |
| 293 | |
| 294 | ConfigErrorInfo error_info; |
| 295 | error_info.set_code(CONFIG_ERROR_NONE); |
| 296 | return error_info; |
| 297 | } |
| 298 | |
Felipe Andrade | a0cbde7 | 2020-04-01 15:40:10 +0200 | [diff] [blame] | 299 | bool ConfigParser::IsKeySupported(const std::string& key, |
| 300 | const std::string& section, |
| 301 | int group_level) const { |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 302 | // Bail on anything outside of a section. |
| 303 | if (section.empty()) |
| 304 | return false; |
| 305 | |
| 306 | // Enforce only whitelisted libdefaults keys on the root and realm levels: |
| 307 | // [libdefaults] |
| 308 | // clockskew = 300 |
| 309 | // EXAMPLE.COM = { |
| 310 | // clockskew = 500 |
| 311 | // } |
| 312 | if (section == kSectionLibdefaults && group_level <= 1) { |
Qijiang Fan | 5243904 | 2020-06-17 15:34:38 +0900 | [diff] [blame^] | 313 | return base::Contains(libdefaults_whitelist_, key); |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 314 | } |
| 315 | |
| 316 | // Enforce only whitelisted realm keys on the root and realm levels: |
| 317 | // [realms] |
| 318 | // kdc = kerberos1.example.com |
| 319 | // EXAMPLE.COM = { |
| 320 | // kdc = kerberos2.example.com |
| 321 | // } |
| 322 | // Not sure if they can actually be at the root level, but just in case... |
| 323 | if (section == kSectionRealms && group_level <= 1) |
Qijiang Fan | 5243904 | 2020-06-17 15:34:38 +0900 | [diff] [blame^] | 324 | return base::Contains(realms_whitelist_, key); |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 325 | |
| 326 | // Anything else is fine (all keys of other supported sections). |
| 327 | return true; |
| 328 | } |
| 329 | |
Lutz Justen | 5163109 | 2019-07-05 09:19:58 +0200 | [diff] [blame] | 330 | } // namespace kerberos |