blob: 0013597335779cf14deacb71ac8d6761de62391d [file] [log] [blame]
Lutz Justen51631092019-07-05 09:19:58 +02001// 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 Andradea0cbde72020-04-01 15:40:10 +02005#include "kerberos/config_parser.h"
Lutz Justen51631092019-07-05 09:19:58 +02006
7#include <vector>
8
9#include <base/stl_util.h>
10#include <base/strings/string_split.h>
11
12namespace kerberos {
13namespace {
14
Sergey Poromov38cec242021-03-12 13:47:57 +010015// Maximum depth of nested '{' in the config.
16constexpr int kMaxGroupLevelDepth = 1000;
17
Lutz Justen51631092019-07-05 09:19:58 +020018// See
19// https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html
20// for a description of the krb5.conf format.
21
22// Directives that are not relations (i.e. key=value). All blacklisted.
23const char* const kDirectives[] = {"module", "include", "includedir"};
24
25// Whitelisted configuration keys in the [libdefaults] section.
26const char* const kLibDefaultsWhitelist[] = {
27 "canonicalize",
28 "clockskew",
29 "default_tgs_enctypes",
30 "default_tkt_enctypes",
31 "dns_canonicalize_hostname",
32 "dns_lookup_kdc",
33 "extra_addresses",
34 "forwardable",
35 "ignore_acceptor_hostname",
36 "kdc_default_options",
37 "kdc_timesync",
38 "noaddresses",
39 "permitted_enctypes",
40 "preferred_preauth_types",
41 "proxiable",
42 "rdns",
43 "renew_lifetime",
44 "ticket_lifetime",
45 "udp_preference_limit",
46};
47
48// Whitelisted configuration keys in the [realms] section.
49const char* const kRealmsWhitelist[] = {
50 "admin_server", "auth_to_local", "kdc", "kpasswd_server", "master_kdc",
51};
52
53// Whitelisted sections. Any key in "domain_realm" and "capaths" is accepted.
54constexpr char kSectionLibdefaults[] = "libdefaults";
55constexpr char kSectionRealms[] = "realms";
56constexpr char kSectionDomainRealm[] = "domain_realm";
57constexpr char kSectionCapaths[] = "capaths";
58
59const char* const kSectionWhitelist[] = {kSectionLibdefaults, kSectionRealms,
60 kSectionDomainRealm, kSectionCapaths};
61
Felipe Andrade66aaf6b2020-03-24 13:05:57 +010062// List of encryption types fields allowed inside [libdefaults] section.
63const char* const kEnctypesFields[] = {
64 "default_tgs_enctypes",
65 "default_tkt_enctypes",
66 "permitted_enctypes",
67};
68
69// List of weak encryption types. |DEFAULT| value is also listed because it
70// includes both weak and strong types.
71const char* const kWeakEnctypes[] = {
72 "DEFAULT",
73 "des",
74 "des3",
75 "rc4",
76 "des-cbc-crc",
77 "des-cbc-md4",
78 "des-cbc-md5",
79 "des-cbc-raw",
80 "des-hmac-sha1",
81 "des3-cbc-raw",
82 "des3-cbc-sha1",
83 "des3-hmac-sha1",
84 "des3-cbc-sha1-kd",
85 "arcfour-hmac",
86 "rc4-hmac",
87 "arcfour-hmac-md5",
88 "arcfour-hmac-exp",
89 "rc4-hmac-exp",
90 "arcfour-hmac-md5-exp",
91};
92
93// List of strong encryption types. |DEFAULT| value is also listed because it
94// includes both weak and strong types.
95const char* const kStrongEnctypes[] = {
96 "DEFAULT", "aes", "aes256-cts-hmac-sha1-96",
97 "aes256-cts", "AES-256", "aes128-cts-hmac-sha1-96",
98 "aes128-cts", "AES-128",
99};
100
Lutz Justen51631092019-07-05 09:19:58 +0200101ConfigErrorInfo MakeErrorInfo(ConfigErrorCode code, int line_index) {
102 ConfigErrorInfo error_info;
103 error_info.set_code(code);
104 error_info.set_line_index(line_index);
105 return error_info;
106}
107
108} // namespace
109
Felipe Andradea0cbde72020-04-01 15:40:10 +0200110ConfigParser::ConfigParser()
Lutz Justen51631092019-07-05 09:19:58 +0200111 : libdefaults_whitelist_(std::begin(kLibDefaultsWhitelist),
112 std::end(kLibDefaultsWhitelist)),
113 realms_whitelist_(std::begin(kRealmsWhitelist),
114 std::end(kRealmsWhitelist)),
115 section_whitelist_(std::begin(kSectionWhitelist),
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100116 std::end(kSectionWhitelist)),
117 enctypes_fields_(std::begin(kEnctypesFields), std::end(kEnctypesFields)),
118 weak_enctypes_(std::begin(kWeakEnctypes), std::end(kWeakEnctypes)),
119 strong_enctypes_(std::begin(kStrongEnctypes), std::end(kStrongEnctypes)) {
120}
Lutz Justen51631092019-07-05 09:19:58 +0200121
Felipe Andradea0cbde72020-04-01 15:40:10 +0200122ConfigErrorInfo ConfigParser::Validate(const std::string& krb5conf) const {
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100123 KerberosEncryptionTypes encryption_types;
124 return ParseConfig(krb5conf, &encryption_types);
125}
126
Felipe Andrade90cb84e2020-04-07 20:28:33 +0200127bool ConfigParser::GetEncryptionTypes(
128 const std::string& krb5conf,
129 KerberosEncryptionTypes* encryption_types) const {
130 ConfigErrorInfo error_info = ParseConfig(krb5conf, encryption_types);
131 return error_info.code() == CONFIG_ERROR_NONE;
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100132}
133
134// Validates the config and gets encryption types from it. Finds the enctypes
135// fields and maps the union of the enctypes into one of the buckets of
136// interest: 'All', 'Strong' or 'Legacy'. If an enctypes field is missing, the
137// default value for this field ('All') will be used.
Felipe Andradea0cbde72020-04-01 15:40:10 +0200138ConfigErrorInfo ConfigParser::ParseConfig(
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100139 const std::string& krb5conf,
140 KerberosEncryptionTypes* encryption_types) const {
141 // Variables used to keep track of encryption fields and types on |krc5conf|.
Felipe Andradef097d252020-04-08 15:10:50 +0200142 StringSet listed_enctypes_fields;
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100143 bool has_weak_enctype = false;
144 bool has_strong_enctype = false;
145
Felipe Andrade90cb84e2020-04-07 20:28:33 +0200146 // Initializes |encryption_types| with the default value in our feature. It
147 // will be replaced at the end of this method, if |krb5conf| is valid.
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100148 *encryption_types = KerberosEncryptionTypes::kStrong;
149
Lutz Justen51631092019-07-05 09:19:58 +0200150 // Keep empty lines, they're necessary to get the line numbers right.
Lutz Justen70496c12019-07-24 11:11:55 +0200151 // Note: The MIT krb5 parser does not count \r as newline.
Lutz Justen51631092019-07-05 09:19:58 +0200152 const std::vector<std::string> lines = base::SplitString(
Lutz Justen70496c12019-07-24 11:11:55 +0200153 krb5conf, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
Lutz Justen51631092019-07-05 09:19:58 +0200154
155 // Level of nested curly braces {}.
156 int group_level = 0;
157
158 // Opening curly braces '{' can be on the same line and on the next line. This
159 // is set to true if a '{' is expected on the next line.
160 bool expect_opening_curly_brace = false;
161
162 // Current [section].
163 std::string current_section;
164
165 for (size_t line_index = 0; line_index < lines.size(); ++line_index) {
Lutz Justen70496c12019-07-24 11:11:55 +0200166 // Convert to c_str() and back to get rid of embedded \0's.
167 std::string line = lines.at(line_index).c_str();
Lutz Justen51631092019-07-05 09:19:58 +0200168
169 // Are we expecting a '{' to open a { group }?
170 if (expect_opening_curly_brace) {
171 if (line.empty() || line.at(0) != '{') {
172 return MakeErrorInfo(CONFIG_ERROR_EXPECTED_OPENING_CURLY_BRACE,
173 line_index);
174 }
175 group_level++;
Sergey Poromov38cec242021-03-12 13:47:57 +0100176 // If too nested config, exit here to prevent krb5 stack overflow.
177 if (group_level > kMaxGroupLevelDepth)
178 return MakeErrorInfo(CONFIG_ERROR_TOO_MANY_NESTED_GROUPS, line_index);
Lutz Justen51631092019-07-05 09:19:58 +0200179 expect_opening_curly_brace = false;
180 continue;
181 }
182
183 // Skip empty lines.
184 if (line.empty())
185 continue;
186
187 // Skip comments.
188 if (line.at(0) == ';' || line.at(0) == '#')
189 continue;
190
191 // Bail on any |kDirectives|.
192 for (const char* directive : kDirectives) {
193 const int len = strlen(directive);
Lutz Justen70496c12019-07-24 11:11:55 +0200194 const int line_len = static_cast<int>(line.size());
195 if (strncmp(line.c_str(), directive, len) == 0 &&
196 (len >= line_len || isspace(line.at(len)))) {
Lutz Justen51631092019-07-05 09:19:58 +0200197 return MakeErrorInfo(CONFIG_ERROR_KEY_NOT_SUPPORTED, line_index);
Lutz Justen70496c12019-07-24 11:11:55 +0200198 }
Lutz Justen51631092019-07-05 09:19:58 +0200199 }
200
201 // Check for '}' to close a { group }.
202 if (line.at(0) == '}') {
203 if (group_level == 0)
204 return MakeErrorInfo(CONFIG_ERROR_EXTRA_CURLY_BRACE, line_index);
205 group_level--;
206 continue;
207 }
208
209 // Check for new [section].
210 if (line.at(0) == '[') {
211 // Bail if section is within a { group }.
212 if (group_level > 0)
213 return MakeErrorInfo(CONFIG_ERROR_SECTION_NESTED_IN_GROUP, line_index);
214
215 // Bail if closing bracket is missing or if there's more stuff after the
216 // closing bracket (the final marker '*' is fine).
217 std::vector<std::string> parts = base::SplitString(
218 line, "]", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
219 if (parts.size() != 2 || !(parts.at(1).empty() || parts.at(1) == "*"))
220 return MakeErrorInfo(CONFIG_ERROR_SECTION_SYNTAX, line_index);
221
222 current_section = parts.at(0).substr(1);
223
224 // Bail if the section is not supported, e.g. [appdefaults].
225 if (current_section.empty() ||
Qijiang Fan52439042020-06-17 15:34:38 +0900226 !base::Contains(section_whitelist_, current_section)) {
Lutz Justen51631092019-07-05 09:19:58 +0200227 return MakeErrorInfo(CONFIG_ERROR_SECTION_NOT_SUPPORTED, line_index);
228 }
229 continue;
230 }
231
232 // Check for "key = value" or "key = {".
233 std::vector<std::string> parts = base::SplitString(
234 line, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
235
236 // Remove final marker.
237 std::string& key = parts.at(0);
238 if (key.back() == '*')
239 key.pop_back();
240
Lutz Justen70496c12019-07-24 11:11:55 +0200241 // No space allowed in the key.
242 if (std::find_if(key.begin(), key.end(), isspace) != key.end())
243 return MakeErrorInfo(CONFIG_ERROR_RELATION_SYNTAX, line_index);
244
Lutz Justen51631092019-07-05 09:19:58 +0200245 // Final marker must come immediately after key.
246 if (key.empty() || isspace(key.back()))
247 return MakeErrorInfo(CONFIG_ERROR_RELATION_SYNTAX, line_index);
248
249 // Is there at least one '=' sign?
250 if (parts.size() < 2)
251 return MakeErrorInfo(CONFIG_ERROR_RELATION_SYNTAX, line_index);
252
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100253 const std::string& value = parts.at(1);
Lutz Justen70496c12019-07-24 11:11:55 +0200254 if (parts.size() == 2) {
255 // Check for a '{' to start a group. The '{' could also be on the next
256 // line. If there's anything except whitespace after '{', it counts as
257 // value, not as a group.
258 // Note: If there is more than one '=', it cannot be the start of a group,
259 // e.g. key==\n{.
Lutz Justen70496c12019-07-24 11:11:55 +0200260 if (value.empty()) {
261 expect_opening_curly_brace = true;
262 continue;
263 }
264 if (value == "{") {
265 group_level++;
Sergey Poromov38cec242021-03-12 13:47:57 +0100266 // If too nested config, exit here to prevent krb5 stack overflow.
267 if (group_level > kMaxGroupLevelDepth)
268 return MakeErrorInfo(CONFIG_ERROR_TOO_MANY_NESTED_GROUPS, line_index);
Lutz Justen70496c12019-07-24 11:11:55 +0200269 continue;
270 }
Lutz Justen51631092019-07-05 09:19:58 +0200271 }
272
273 // Check whether we support the key.
274 if (!IsKeySupported(key, current_section, group_level))
275 return MakeErrorInfo(CONFIG_ERROR_KEY_NOT_SUPPORTED, line_index);
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100276
277 // If |key| is a enctypes field in the [libdefaults] section.
278 if (current_section == kSectionLibdefaults && group_level <= 1 &&
Qijiang Fan52439042020-06-17 15:34:38 +0900279 base::Contains(enctypes_fields_, key)) {
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100280 listed_enctypes_fields.insert(key);
281
282 // Note: encryption types can be delimited by comma or whitespace.
283 const std::vector<std::string> enctypes = base::SplitString(
284 value, ", ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
285
286 for (const std::string& type : enctypes) {
Qijiang Fan52439042020-06-17 15:34:38 +0900287 has_weak_enctype |= base::Contains(weak_enctypes_, type);
288 has_strong_enctype |= base::Contains(strong_enctypes_, type);
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100289 }
290 }
291 }
292
293 // Note: if an enctypes field is missing, the default value is 'All'.
294 if (listed_enctypes_fields.size() < enctypes_fields_.size() ||
295 (has_weak_enctype && has_strong_enctype)) {
296 *encryption_types = KerberosEncryptionTypes::kAll;
297 } else if (has_strong_enctype) {
298 *encryption_types = KerberosEncryptionTypes::kStrong;
299 } else {
300 *encryption_types = KerberosEncryptionTypes::kLegacy;
Lutz Justen51631092019-07-05 09:19:58 +0200301 }
302
303 ConfigErrorInfo error_info;
304 error_info.set_code(CONFIG_ERROR_NONE);
305 return error_info;
306}
307
Felipe Andradea0cbde72020-04-01 15:40:10 +0200308bool ConfigParser::IsKeySupported(const std::string& key,
309 const std::string& section,
310 int group_level) const {
Lutz Justen51631092019-07-05 09:19:58 +0200311 // Bail on anything outside of a section.
312 if (section.empty())
313 return false;
314
315 // Enforce only whitelisted libdefaults keys on the root and realm levels:
316 // [libdefaults]
317 // clockskew = 300
318 // EXAMPLE.COM = {
319 // clockskew = 500
320 // }
321 if (section == kSectionLibdefaults && group_level <= 1) {
Qijiang Fan52439042020-06-17 15:34:38 +0900322 return base::Contains(libdefaults_whitelist_, key);
Lutz Justen51631092019-07-05 09:19:58 +0200323 }
324
325 // Enforce only whitelisted realm keys on the root and realm levels:
326 // [realms]
327 // kdc = kerberos1.example.com
328 // EXAMPLE.COM = {
329 // kdc = kerberos2.example.com
330 // }
331 // Not sure if they can actually be at the root level, but just in case...
332 if (section == kSectionRealms && group_level <= 1)
Qijiang Fan52439042020-06-17 15:34:38 +0900333 return base::Contains(realms_whitelist_, key);
Lutz Justen51631092019-07-05 09:19:58 +0200334
335 // Anything else is fine (all keys of other supported sections).
336 return true;
337}
338
Lutz Justen51631092019-07-05 09:19:58 +0200339} // namespace kerberos