blob: 17c42870b4981d99f278193815e48b0e6572c1b6 [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
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.
20const char* const kDirectives[] = {"module", "include", "includedir"};
21
22// Whitelisted configuration keys in the [libdefaults] section.
23const 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.
46const 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.
51constexpr char kSectionLibdefaults[] = "libdefaults";
52constexpr char kSectionRealms[] = "realms";
53constexpr char kSectionDomainRealm[] = "domain_realm";
54constexpr char kSectionCapaths[] = "capaths";
55
56const char* const kSectionWhitelist[] = {kSectionLibdefaults, kSectionRealms,
57 kSectionDomainRealm, kSectionCapaths};
58
Felipe Andrade66aaf6b2020-03-24 13:05:57 +010059// List of encryption types fields allowed inside [libdefaults] section.
60const 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.
68const 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.
92const 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 Justen51631092019-07-05 09:19:58 +020098ConfigErrorInfo 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 Andradea0cbde72020-04-01 15:40:10 +0200107ConfigParser::ConfigParser()
Lutz Justen51631092019-07-05 09:19:58 +0200108 : 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 Andrade66aaf6b2020-03-24 13:05:57 +0100113 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 Justen51631092019-07-05 09:19:58 +0200118
Felipe Andradea0cbde72020-04-01 15:40:10 +0200119ConfigErrorInfo ConfigParser::Validate(const std::string& krb5conf) const {
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100120 KerberosEncryptionTypes encryption_types;
121 return ParseConfig(krb5conf, &encryption_types);
122}
123
Felipe Andrade90cb84e2020-04-07 20:28:33 +0200124bool 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 Andrade66aaf6b2020-03-24 13:05:57 +0100129}
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 Andradea0cbde72020-04-01 15:40:10 +0200135ConfigErrorInfo ConfigParser::ParseConfig(
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100136 const std::string& krb5conf,
137 KerberosEncryptionTypes* encryption_types) const {
138 // Variables used to keep track of encryption fields and types on |krc5conf|.
Felipe Andradef097d252020-04-08 15:10:50 +0200139 StringSet listed_enctypes_fields;
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100140 bool has_weak_enctype = false;
141 bool has_strong_enctype = false;
142
Felipe Andrade90cb84e2020-04-07 20:28:33 +0200143 // 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 Andrade66aaf6b2020-03-24 13:05:57 +0100145 *encryption_types = KerberosEncryptionTypes::kStrong;
146
Lutz Justen51631092019-07-05 09:19:58 +0200147 // Keep empty lines, they're necessary to get the line numbers right.
Lutz Justen70496c12019-07-24 11:11:55 +0200148 // Note: The MIT krb5 parser does not count \r as newline.
Lutz Justen51631092019-07-05 09:19:58 +0200149 const std::vector<std::string> lines = base::SplitString(
Lutz Justen70496c12019-07-24 11:11:55 +0200150 krb5conf, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
Lutz Justen51631092019-07-05 09:19:58 +0200151
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 Justen70496c12019-07-24 11:11:55 +0200163 // Convert to c_str() and back to get rid of embedded \0's.
164 std::string line = lines.at(line_index).c_str();
Lutz Justen51631092019-07-05 09:19:58 +0200165
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 Justen70496c12019-07-24 11:11:55 +0200188 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 Justen51631092019-07-05 09:19:58 +0200191 return MakeErrorInfo(CONFIG_ERROR_KEY_NOT_SUPPORTED, line_index);
Lutz Justen70496c12019-07-24 11:11:55 +0200192 }
Lutz Justen51631092019-07-05 09:19:58 +0200193 }
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 Fan52439042020-06-17 15:34:38 +0900220 !base::Contains(section_whitelist_, current_section)) {
Lutz Justen51631092019-07-05 09:19:58 +0200221 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 Justen70496c12019-07-24 11:11:55 +0200235 // 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 Justen51631092019-07-05 09:19:58 +0200239 // 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 Andrade66aaf6b2020-03-24 13:05:57 +0100247 const std::string& value = parts.at(1);
Lutz Justen70496c12019-07-24 11:11:55 +0200248 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 Justen70496c12019-07-24 11:11:55 +0200254 if (value.empty()) {
255 expect_opening_curly_brace = true;
256 continue;
257 }
258 if (value == "{") {
259 group_level++;
260 continue;
261 }
Lutz Justen51631092019-07-05 09:19:58 +0200262 }
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 Andrade66aaf6b2020-03-24 13:05:57 +0100267
268 // If |key| is a enctypes field in the [libdefaults] section.
269 if (current_section == kSectionLibdefaults && group_level <= 1 &&
Qijiang Fan52439042020-06-17 15:34:38 +0900270 base::Contains(enctypes_fields_, key)) {
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100271 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 Fan52439042020-06-17 15:34:38 +0900278 has_weak_enctype |= base::Contains(weak_enctypes_, type);
279 has_strong_enctype |= base::Contains(strong_enctypes_, type);
Felipe Andrade66aaf6b2020-03-24 13:05:57 +0100280 }
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 Justen51631092019-07-05 09:19:58 +0200292 }
293
294 ConfigErrorInfo error_info;
295 error_info.set_code(CONFIG_ERROR_NONE);
296 return error_info;
297}
298
Felipe Andradea0cbde72020-04-01 15:40:10 +0200299bool ConfigParser::IsKeySupported(const std::string& key,
300 const std::string& section,
301 int group_level) const {
Lutz Justen51631092019-07-05 09:19:58 +0200302 // 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 Fan52439042020-06-17 15:34:38 +0900313 return base::Contains(libdefaults_whitelist_, key);
Lutz Justen51631092019-07-05 09:19:58 +0200314 }
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 Fan52439042020-06-17 15:34:38 +0900324 return base::Contains(realms_whitelist_, key);
Lutz Justen51631092019-07-05 09:19:58 +0200325
326 // Anything else is fine (all keys of other supported sections).
327 return true;
328}
329
Lutz Justen51631092019-07-05 09:19:58 +0200330} // namespace kerberos