blob: 4d5204a3897082baf46f3ecf8e02402fbd83cd3b [file] [log] [blame]
// 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 <ostream>
#include <string>
#include <base/logging.h>
#include <gtest/gtest.h>
#include "kerberos/kerberos_metrics.h"
#include "kerberos/proto_bindings/kerberos_service.pb.h"
namespace kerberos {
namespace {
constexpr char kCompleteKrb5Conf[] = R"(
# Comment
; Another comment
[libdefaults]
clockskew = 123
default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
default_tgs_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
permitted_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
renew_lifetime* = 7d
ticket_lifetime* = 1d
A.EXAMPLE.COM = {
clockskew = 300
}
B.EXAMPLE.COM =
{
; Tests = whether { can be on new line
clockskew = 500
}
[realms]
kdc = 5.6.7.8
EXAMPLE.COM = {
kdc = 1.2.3.4
admin_server = kdc.example.com
auth_to_local = RULE:[2:$1](johndoe)s/^.*$/guest/
auth_to_local_names = {
hans = jack
joerg = jerk
}
}*
[domain_realm]*
any.thing = IS.ACCEPTED.HERE
[capaths]
here = AS.WELL)";
} // namespace
std::ostream& operator<<(std::ostream& os, ConfigErrorCode code) {
switch (code) {
case CONFIG_ERROR_NONE:
return os << "OK";
case CONFIG_ERROR_SECTION_NESTED_IN_GROUP:
return os << "Section nested in group";
case CONFIG_ERROR_SECTION_SYNTAX:
return os << "ESection syntax error, expected '[section]'";
case CONFIG_ERROR_EXPECTED_OPENING_CURLY_BRACE:
return os << "Expected opening curly brace '{'";
case CONFIG_ERROR_EXTRA_CURLY_BRACE:
return os << "Extra curly brace";
case CONFIG_ERROR_RELATION_SYNTAX:
return os << "Relation syntax error, expected 'key = ...'";
case CONFIG_ERROR_KEY_NOT_SUPPORTED:
return os << "Key not supported";
case CONFIG_ERROR_SECTION_NOT_SUPPORTED:
return os << "Section not supported";
case CONFIG_ERROR_KRB5_FAILED_TO_PARSE:
return os << "KRB5 failed to parse";
case CONFIG_ERROR_COUNT:
NOTREACHED();
}
return os;
}
std::ostream& operator<<(std::ostream& os, const ConfigErrorInfo& error_info) {
if (error_info.code() == CONFIG_ERROR_NONE)
return os << "[no error]";
return os << error_info.code() << " at line " << error_info.line_index();
}
class ConfigParserTest : public ::testing::Test {
protected:
void ExpectNoError(const char* krb5conf) {
ConfigErrorInfo error_info = config_parser_.Validate(krb5conf);
EXPECT_TRUE(error_info.has_code()) << error_info;
EXPECT_EQ(CONFIG_ERROR_NONE, error_info.code()) << error_info;
EXPECT_FALSE(error_info.has_line_index()) << error_info;
}
void ExpectError(const std::string& krb5conf,
ConfigErrorCode code,
int line_index) {
ConfigErrorInfo error_info = config_parser_.Validate(krb5conf);
EXPECT_TRUE(error_info.has_code()) << error_info;
EXPECT_EQ(code, error_info.code()) << error_info;
EXPECT_TRUE(error_info.has_line_index()) << error_info;
EXPECT_EQ(line_index, error_info.line_index()) << error_info;
}
void ExpectEncryptionTypes(
const std::string& krb5conf,
KerberosEncryptionTypes expected_encryption_types) {
KerberosEncryptionTypes encryption_types;
EXPECT_TRUE(config_parser_.GetEncryptionTypes(krb5conf, &encryption_types));
EXPECT_EQ(expected_encryption_types, encryption_types);
}
ConfigParser config_parser_;
};
TEST_F(ConfigParserTest, ValidConfig) {
ExpectNoError(kCompleteKrb5Conf);
}
TEST_F(ConfigParserTest, Empty) {
ExpectNoError("");
ExpectNoError("\n");
ExpectNoError("\n\n\n");
ExpectNoError("[libdefaults]");
ExpectNoError("[libdefaults]\n");
ExpectNoError("[libdefaults]\n\n\n");
}
TEST_F(ConfigParserTest, ModulesAndIncludesBlocked) {
ExpectError("module MODULEPATH:RESIDUAL", CONFIG_ERROR_KEY_NOT_SUPPORTED, 0);
ExpectError("include /path/to/file", CONFIG_ERROR_KEY_NOT_SUPPORTED, 0);
ExpectError("includedir /path/to/files", CONFIG_ERROR_KEY_NOT_SUPPORTED, 0);
constexpr char kKrb5Conf[] = R"(
[libdefaults]
includedir /path/to/files)";
ExpectError(kKrb5Conf, CONFIG_ERROR_KEY_NOT_SUPPORTED, 2);
}
TEST_F(ConfigParserTest, UnsupportedLibdefaultsKey) {
constexpr char kKrb5Conf[] = R"(
[libdefaults]
stonkskew = 123)";
ExpectError(kKrb5Conf, CONFIG_ERROR_KEY_NOT_SUPPORTED, 2);
}
TEST_F(ConfigParserTest, UnsupportedNestedLibdefaultsKey) {
constexpr char kKrb5Conf[] = R"(
[libdefaults]
A.EXAMPLE.COM = {
stonkskew = 300
})";
ExpectError(kKrb5Conf, CONFIG_ERROR_KEY_NOT_SUPPORTED, 3);
}
TEST_F(ConfigParserTest, UnsupportedRealmKey) {
constexpr char kKrb5Conf[] = R"(
[realms]
BEISPIEL.FIR = {
meister_svz = svz.beispiel.fir
})";
ExpectError(kKrb5Conf, CONFIG_ERROR_KEY_NOT_SUPPORTED, 3);
}
TEST_F(ConfigParserTest, RelationSyntaxErrorKeyWithoutEquals) {
constexpr char kKrb5Conf[] = R"(
[libdefaults]
kdc: kdc.example.com
)";
ExpectError(kKrb5Conf, CONFIG_ERROR_RELATION_SYNTAX, 2);
}
TEST_F(ConfigParserTest, UnsupportedSection) {
ExpectError("[appdefaults]", CONFIG_ERROR_SECTION_NOT_SUPPORTED, 0);
}
TEST_F(ConfigParserTest, SectionNestedInGroup) {
constexpr char kKrb5Conf[] = R"(
[realms]
EXAMPLE.COM = {
[libdefaults]
})";
ExpectError(kKrb5Conf, CONFIG_ERROR_SECTION_NESTED_IN_GROUP, 3);
}
TEST_F(ConfigParserTest, MissingSectionBrackets) {
ExpectError("[realms", CONFIG_ERROR_SECTION_SYNTAX, 0);
}
TEST_F(ConfigParserTest, SpacesBeforeSectionEndMarker) {
// Note that the krb5 parser appears to accept spaces before the ']', but
// it's a different section than without the spaces, so we reject it.
ExpectError("[realms ]", CONFIG_ERROR_SECTION_NOT_SUPPORTED, 0);
}
TEST_F(ConfigParserTest, ExtraStuffBeforeSectionBrackets) {
ExpectError("extra [realms]", CONFIG_ERROR_RELATION_SYNTAX, 0);
}
TEST_F(ConfigParserTest, ExtraStuffAfterSectionBrackets) {
ExpectError("[realms] extra", CONFIG_ERROR_SECTION_SYNTAX, 0);
}
TEST_F(ConfigParserTest, FinalMarkersAllowed) {
ExpectNoError("[libdefaults]* \nclockskew*=9");
}
TEST_F(ConfigParserTest, FinalMarkersWithSpacesNotAllowed) {
ExpectError("[libdefaults] *)", CONFIG_ERROR_SECTION_SYNTAX, 0);
ExpectError("[libdefaults]\nclockskew *=9", CONFIG_ERROR_RELATION_SYNTAX, 1);
}
TEST_F(ConfigParserTest, RelationSyntaxError) {
ExpectError("[libdefaults]\nclockskew", CONFIG_ERROR_RELATION_SYNTAX, 1);
ExpectError("[libdefaults]\nclockskew ", CONFIG_ERROR_RELATION_SYNTAX, 1);
ExpectError("[libdefaults]\nclockskew* ", CONFIG_ERROR_RELATION_SYNTAX, 1);
ExpectError("[libdefaults]\n=clockskew*", CONFIG_ERROR_RELATION_SYNTAX, 1);
}
TEST_F(ConfigParserTest, TwoEqualSignsAllowed) {
ExpectNoError("[libdefaults]\nclockskew=1=2");
}
TEST_F(ConfigParserTest, RelationSyntaxEdgeCases) {
ExpectError("*", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError("*=", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError("=", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError(" *", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError(" *=", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError(" =", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError("* ", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError("*= ", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError("= ", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError(" * ", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError(" *= ", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError(" = ", CONFIG_ERROR_RELATION_SYNTAX, 0);
ExpectError(" * = ", CONFIG_ERROR_RELATION_SYNTAX, 0);
}
TEST_F(ConfigParserTest, WhitespaceBeforeAndAfterSectionBrackets) {
ExpectNoError(" [realms] ");
}
TEST_F(ConfigParserTest, MissingOpeningCurlyBrace) {
constexpr char kKrb5Conf[] = R"(
[realms]
EXAMPLE.COM =
kdc = kdc.example.com
})";
ExpectError(kKrb5Conf, CONFIG_ERROR_EXPECTED_OPENING_CURLY_BRACE, 3);
}
TEST_F(ConfigParserTest, ExtraCurlyBraceFound) {
constexpr char kKrb5Conf[] = R"(
[realms]
EXAMPLE.COM =
{
kdc = kdc.example.com
}
})";
ExpectError(kKrb5Conf, CONFIG_ERROR_EXTRA_CURLY_BRACE, 6);
}
// Things that the fuzzer found.
TEST_F(ConfigParserTest, FuzzerRegressionTests) {
// Code was looking at character after "include" to check if it's a space.
ExpectError("include", CONFIG_ERROR_KEY_NOT_SUPPORTED, 0);
// Code was accepting "[realms\0]" as a valid section. Embedded \0's should be
// handled in a c_str() kind of way.
std::string krb5confWithZero = "[realms\0]";
krb5confWithZero[7] = 0;
ExpectError(krb5confWithZero, CONFIG_ERROR_SECTION_SYNTAX, 0);
// Code was allowing spaces in keys. Note that ConfigParser allows all keys
// in the [domain_realm] section, but it should still check spaces!
ExpectError("[domain_realm]\nkey x=", CONFIG_ERROR_RELATION_SYNTAX, 1);
// \r should not be counted as newline character.
ExpectError("[domain_realm]\rkey=", CONFIG_ERROR_SECTION_SYNTAX, 0);
// Double == is always a relation, cannot be the start of a group.
ExpectError("[capaths]\nkey==\n{", CONFIG_ERROR_RELATION_SYNTAX, 2);
}
// |GetEncryptionTypes| with a complete config to be parsed.
TEST_F(ConfigParserTest, GetEncryptionTypesCompleteConfig) {
ExpectEncryptionTypes(kCompleteKrb5Conf, KerberosEncryptionTypes::kStrong);
}
// |GetEncryptionTypes| with all encryption types allowed.
TEST_F(ConfigParserTest, GetEncryptionTypesAll) {
constexpr char kKrb5Conf[] = R"(
[libdefaults]
default_tkt_enctypes = aes256-cts-hmac-sha1-96 arcfour-hmac-md5-exp
default_tgs_enctypes = aes256-cts-hmac-sha1-96 arcfour-hmac-md5-exp
permitted_enctypes = aes256-cts-hmac-sha1-96 arcfour-hmac-md5-exp)";
ExpectEncryptionTypes(kKrb5Conf, KerberosEncryptionTypes::kAll);
}
// |GetEncryptionTypes| with only strong encryption types allowed.
TEST_F(ConfigParserTest, GetEncryptionTypesStrong) {
constexpr char kKrb5Conf[] = R"(
[libdefaults]
default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
default_tgs_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
permitted_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96)";
ExpectEncryptionTypes(kKrb5Conf, KerberosEncryptionTypes::kStrong);
}
// |GetEncryptionTypes| with only legacy encryption types allowed.
TEST_F(ConfigParserTest, GetEncryptionTypesLegacy) {
constexpr char kKrb5Conf[] = R"(
[libdefaults]
default_tkt_enctypes = arcfour-hmac-md5-exp des3-cbc-raw
default_tgs_enctypes = arcfour-hmac-md5-exp des3-cbc-raw
permitted_enctypes = arcfour-hmac-md5-exp des3-cbc-raw)";
ExpectEncryptionTypes(kKrb5Conf, KerberosEncryptionTypes::kLegacy);
}
// |GetEncryptionTypes| with enctypes fields missing.
TEST_F(ConfigParserTest, GetEncryptionTypesMissingFields) {
// Empty config allows all encryption types.
ExpectEncryptionTypes("", KerberosEncryptionTypes::kAll);
constexpr char kKrb5Conf1[] = R"(
[libdefaults]
default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
default_tgs_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96)";
ExpectEncryptionTypes(kKrb5Conf1, KerberosEncryptionTypes::kAll);
constexpr char kKrb5Conf2[] = R"(
[libdefaults]
default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
permitted_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96)";
ExpectEncryptionTypes(kKrb5Conf2, KerberosEncryptionTypes::kAll);
constexpr char kKrb5Conf3[] = R"(
[libdefaults]
default_tgs_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
permitted_enctypes =aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96)";
ExpectEncryptionTypes(kKrb5Conf3, KerberosEncryptionTypes::kAll);
}
// |GetEncryptionTypes| with |DEFAULT| enctypes assigned.
TEST_F(ConfigParserTest, GetEncryptionTypesDefaultValues) {
constexpr char kKrb5Conf[] = R"(
[libdefaults]
default_tkt_enctypes = DEFAULT
default_tgs_enctypes = DEFAULT
permitted_enctypes = DEFAULT)";
// |DEFAULT| value allows all encryption types.
ExpectEncryptionTypes(kKrb5Conf, KerberosEncryptionTypes::kAll);
}
// |GetEncryptionTypes| with comma separated encryption types list.
TEST_F(ConfigParserTest, GetEncryptionTypesCommaSeparated) {
constexpr char kKrb5Conf[] = R"(
[libdefaults]
default_tkt_enctypes = aes256-cts-hmac-sha1-96, arcfour-hmac-md5-exp
default_tgs_enctypes = aes256-cts-hmac-sha1-96, arcfour-hmac-md5-exp
permitted_enctypes = aes256-cts-hmac-sha1-96,arcfour-hmac-md5-exp)";
ExpectEncryptionTypes(kKrb5Conf, KerberosEncryptionTypes::kAll);
}
// |GetEncryptionTypes| with invalid config.
TEST_F(ConfigParserTest, GetEncryptionTypesInvalidConfig) {
constexpr char kKrb5Conf[] = R"(
[libdefaults]
stonkskew = 123)";
KerberosEncryptionTypes encryption_types;
EXPECT_FALSE(config_parser_.GetEncryptionTypes(kKrb5Conf, &encryption_types));
// |encryption_types| should've been set to the default value in our feature.
EXPECT_EQ(KerberosEncryptionTypes::kStrong, encryption_types);
}
} // namespace kerberos