| // Copyright 2016 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 "authpolicy/samba_helper.h" |
| |
| #include <cstring> |
| #include <vector> |
| |
| #include <base/guid.h> |
| #include <base/logging.h> |
| #include <base/stl_util.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/utf_string_conversion_utils.h> |
| #include <base/system/sys_info.h> |
| #include <crypto/random.h> |
| |
| #include "authpolicy/anonymizer.h" |
| #include "authpolicy/log_colors.h" |
| |
| namespace { |
| |
| // Map GUID position to octet position for each byte xx. |
| // The bytes of the first 3 groups have to be reversed. |
| // GUID: |
| // |0 |6 |9|1114|1619|21|24 |34 |
| // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |
| // Octet: |
| // |1 |10|13|16|19|22|25|28|31 |46 |
| // \XX\XX\XX\XX\XX\XX\XX\XX\XX\XX\XX\XX\XX\XX\XX\XX |
| // clang-format off |
| const int octet_pos_map[16][2] = { // Maps GUID position to octet position. |
| {0, 10}, {2, 7}, {4, 4}, {6, 1}, // First group, reversed byte order. |
| {9, 16}, {11, 13}, // Second group, reversed byte order. |
| {14, 22}, {16, 19}, // Third group, reversed byte order. |
| {19, 25}, {21, 28}, // Fourth group, same byte order. |
| {24, 31}, {26, 34}, {28, 37}, {30, 40}, {32, 43}, {34, 46}}; // Last group. |
| // clang-format on |
| |
| const size_t kGuidSize = 36; // 16 bytes, xx each byte, plus 4 '-'. |
| const size_t kOctetSize = 48; // 16 bytes, \XX each byte. |
| |
| // How many random code points are generated at a time during random password |
| // generation. Password lentgh + some more for skipped code points. |
| const size_t kRandCodePointsCount = 48; |
| |
| constexpr char kAttributeValueEscapedCharacters[] = ",+\"\\<>;\r\n=/"; |
| |
| constexpr char kLsbReleaseName[] = "CHROMEOS_RELEASE_NAME"; |
| constexpr char kLsbReleaseMilestone[] = "CHROMEOS_RELEASE_CHROME_MILESTONE"; |
| constexpr char kLsbReleaseBuild[] = "CHROMEOS_RELEASE_BUILD_NUMBER"; |
| constexpr char kLsbReleaseBranch[] = "CHROMEOS_RELEASE_BRANCH_NUMBER"; |
| constexpr char kLsbReleasePatch[] = "CHROMEOS_RELEASE_PATCH_NUMBER"; |
| |
| // Escapes a relative distinguished name attribute value according to |
| // https://msdn.microsoft.com/en-us/library/aa366101(v=vs.85).aspx. |
| std::string EscapeAttributeValue(const std::string& value) { |
| std::string escaped_value; |
| for (size_t n = 0; n < value.size(); ++n) { |
| // Escape |
| // - ' ' and # at the beginning, |
| // - ' ' at the end and |
| // - all characters in kAttributeValueEscapedCharacters. |
| const bool should_escape = |
| (n == 0 && (value[n] == ' ' || value[n] == '#')) || |
| (n + 1 == value.size() && value[n] == ' ') || |
| strchr(kAttributeValueEscapedCharacters, value[n]) != nullptr; |
| if (should_escape) |
| escaped_value += '\\'; |
| escaped_value += value[n]; |
| } |
| return escaped_value; |
| } |
| |
| } // namespace |
| |
| namespace authpolicy { |
| |
| // Prefix for Active Directory account ids. A prefixed |account_id| is usually |
| // called |account_id_key|. Must match Chromium AccountId::kKeyAdIdPrefix. |
| const char kActiveDirectoryPrefix[] = "a-"; |
| |
| // Flags for parsing GPO. |
| const char* const kGpFlagsStr[] = { |
| "0 GPFLAGS_ALL_ENABLED", |
| "1 GPFLAGS_USER_SETTINGS_DISABLED", |
| "2 GPFLAGS_MACHINE_SETTINGS_DISABLED", |
| "3 GPFLAGS_ALL_DISABLED", |
| }; |
| |
| constexpr char kKerberosParam[] = "--kerberos"; |
| constexpr char kConfigParam[] = "--configfile"; |
| constexpr char kDebugParam[] = "--debuglevel"; |
| constexpr char kCommandParam[] = "--command"; |
| constexpr char kUserParam[] = "-U"; |
| constexpr char kMachinepassStdinParam[] = "machinepassStdin"; |
| constexpr char kCreatecomputerParam[] = "createcomputer="; |
| constexpr char kOsNameParam[] = "osName="; |
| constexpr char kOsVersionParam[] = "osVer="; |
| constexpr char kOsServicePackParam[] = "osServicePack=None"; |
| |
| constexpr char kUseKeytabParam[] = "-k"; |
| constexpr char kValidityLifetimeParam[] = "-l"; |
| constexpr char kRenewalLifetimeParam[] = "-r"; |
| constexpr char kRenewParam[] = "-R"; |
| |
| constexpr char kSetExitStatusParam[] = "-s"; |
| constexpr char kCredentialCacheParam[] = "-c"; |
| |
| constexpr char kEncTypesAll[] = "all"; |
| constexpr char kEncTypesStrong[] = "strong"; |
| constexpr char kEncTypesLegacy[] = "legacy"; |
| |
| constexpr char kAffiliationMarker[] = "ad_affiliation_marker"; |
| |
| // Random number generator, used for testing purposes. |
| RandomBytesGenerator* g_rand_bytes = nullptr; |
| |
| bool ParseUserPrincipalName(const std::string& user_principal_name, |
| std::string* user_name, |
| std::string* realm, |
| std::string* normalized_user_principal_name) { |
| std::vector<std::string> parts = base::SplitString( |
| user_principal_name, "@", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (parts.size() != 2 || parts.at(0).empty() || parts.at(1).empty()) { |
| // Don't log user_principal_name, it might contain sensitive data. |
| LOG(ERROR) << "Failed to parse user principal name. Expected form " |
| "'user@some.realm'."; |
| return false; |
| } |
| *user_name = parts.at(0); |
| *realm = base::ToUpperASCII(parts.at(1)); |
| *normalized_user_principal_name = *user_name + "@" + *realm; |
| return true; |
| } |
| |
| bool FindToken(const std::string& in_str, |
| char token_separator, |
| const std::string& token, |
| std::string* result) { |
| std::vector<std::string> lines = base::SplitString( |
| in_str, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| for (const std::string& line : lines) { |
| if (FindTokenInLine(line, token_separator, token, result)) |
| return true; |
| } |
| |
| // Don't log in_str, it might contain sensitive data. |
| LOG(ERROR) << "Failed to find '" << token << "' in string"; |
| return false; |
| } |
| |
| bool FindTokenInLine(const std::string& in_line, |
| char token_separator, |
| const std::string& token, |
| std::string* result) { |
| size_t sep_pos = in_line.find(token_separator); |
| if (sep_pos == std::string::npos) |
| return false; |
| |
| std::string line_token; |
| base::TrimWhitespaceASCII(in_line.substr(0, sep_pos), base::TRIM_ALL, |
| &line_token); |
| if (line_token != token) |
| return false; |
| |
| base::TrimWhitespaceASCII(in_line.substr(sep_pos + 1), base::TRIM_ALL, |
| result); |
| return !result->empty(); |
| } |
| |
| bool ParseGpoVersion(const std::string& str, uint32_t* version) { |
| DCHECK(version); |
| *version = 0; |
| uint32_t version_hex = 0; |
| if (sscanf(str.c_str(), "%u (0x%08x)", version, &version_hex) != 2 || |
| *version != version_hex) |
| return false; |
| |
| return true; |
| } |
| |
| bool ParseGpFlags(const std::string& str, int* gp_flags) { |
| for (int flag = 0; flag < static_cast<int>(base::size(kGpFlagsStr)); ++flag) { |
| if (str == kGpFlagsStr[flag]) { |
| *gp_flags = flag; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool Contains(const std::string& str, const std::string& substr) { |
| return str.find(substr) != std::string::npos; |
| } |
| |
| std::string GuidToOctetString(const std::string& guid) { |
| std::string octet_str; |
| if (!base::IsValidGUID(guid)) |
| return octet_str; |
| DCHECK_EQ(kGuidSize, guid.size()); |
| |
| octet_str.assign(kOctetSize, '\\'); |
| for (size_t n = 0; n < base::size(octet_pos_map); ++n) { |
| for (int hex_digit = 0; hex_digit < 2; ++hex_digit) { |
| octet_str.at(octet_pos_map[n][1] + hex_digit) = |
| toupper(guid.at(octet_pos_map[n][0] + hex_digit)); |
| } |
| } |
| |
| return octet_str; |
| } |
| |
| std::string OctetStringToGuidForTesting(const std::string& octet_str) { |
| std::string guid; |
| if (octet_str.size() != kOctetSize) |
| return guid; |
| |
| guid.assign(kGuidSize, '-'); |
| for (size_t n = 0; n < base::size(octet_pos_map); ++n) { |
| for (int hex_digit = 0; hex_digit < 2; ++hex_digit) { |
| guid.at(octet_pos_map[n][0] + hex_digit) = |
| tolower(octet_str.at(octet_pos_map[n][1] + hex_digit)); |
| } |
| } |
| return guid; |
| } |
| |
| std::string GetAccountIdKey(const std::string& account_id) { |
| return kActiveDirectoryPrefix + account_id; |
| } |
| |
| void LogLongString(const char* color, |
| const std::string& header, |
| const std::string& str, |
| Anonymizer* anonymizer) { |
| if (!LOG_IS_ON(INFO)) |
| return; |
| |
| std::string anonymized_str = anonymizer->Process(str); |
| std::vector<std::string> lines = base::SplitString( |
| anonymized_str, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| if (lines.size() <= 1) { |
| LOG(INFO) << color << header << anonymized_str << kColorReset; |
| } else { |
| LOG(INFO) << color << header << kColorReset; |
| for (const std::string& line : lines) |
| LOG(INFO) << color << " " << line << kColorReset; |
| } |
| } |
| |
| std::string BuildDistinguishedName( |
| const std::vector<std::string>& organizational_units, |
| const std::string& domain) { |
| std::string distinguished_name; |
| |
| for (const std::string& ou : organizational_units) { |
| if (distinguished_name.size() > 0) |
| distinguished_name += ","; |
| distinguished_name += "ou=" + EscapeAttributeValue(ou); |
| } |
| |
| std::vector<std::string> dc_parts = base::SplitString( |
| domain, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| for (const std::string& dc : dc_parts) { |
| if (distinguished_name.size() > 0) |
| distinguished_name += ","; |
| distinguished_name += "dc=" + EscapeAttributeValue(dc); |
| } |
| |
| return distinguished_name; |
| } |
| |
| std::string GenerateRandomMachinePassword() { |
| // Each code point uses at most 3 bytes (and 3 is actually most likely), so |
| // this is a good upper bound. |
| std::string random_password; |
| random_password.reserve(kMachinePasswordCodePoints * 3); |
| |
| // Since invalid code points and zero should be ignored, we cannot simply fill |
| // a wchar_t string with random bytes and convert to UTF-8. Instead, throw |
| // away bad code points (don't just map to valid code points (e.g. 0->1) as |
| // this would create a bias). |
| size_t code_point_count = 0; |
| uint16_t rand_code_points[kRandCodePointsCount]; |
| for (;;) { |
| // g_rand_bytes is only set for testing. |
| if (g_rand_bytes) |
| g_rand_bytes(rand_code_points, sizeof(rand_code_points)); |
| else |
| crypto::RandBytes(rand_code_points, sizeof(rand_code_points)); |
| |
| for (uint16_t code_point : rand_code_points) { |
| // Discard bad code points. |
| if (code_point == 0 || code_point == '\n' || |
| !base::IsValidCodepoint(code_point)) |
| continue; |
| |
| base::WriteUnicodeCharacter(code_point, &random_password); |
| if (++code_point_count >= kMachinePasswordCodePoints) |
| return random_password; |
| } |
| } |
| } |
| |
| void SetRandomNumberGeneratorForTesting(RandomBytesGenerator* rand_bytes) { |
| g_rand_bytes = rand_bytes; |
| } |
| |
| std::string GetOsName() { |
| std::string os_name; |
| if (!base::SysInfo::GetLsbReleaseValue(kLsbReleaseName, &os_name)) { |
| LOG(ERROR) << "Cannot determine OS name: Field '" << kLsbReleaseName |
| << "' missing from /etc/lsb-release"; |
| return std::string(); |
| } |
| return os_name; |
| } |
| |
| std::string GetOsVersion() { |
| std::string milestone, build, branch, patch; |
| if (!base::SysInfo::GetLsbReleaseValue(kLsbReleaseMilestone, &milestone) || |
| !base::SysInfo::GetLsbReleaseValue(kLsbReleaseBuild, &build) || |
| !base::SysInfo::GetLsbReleaseValue(kLsbReleaseBranch, &branch) || |
| !base::SysInfo::GetLsbReleaseValue(kLsbReleasePatch, &patch)) { |
| LOG(ERROR) |
| << "Cannot determine OS version: Field missing from /etc/lsb-release"; |
| return std::string(); |
| } |
| |
| const std::vector<std::string> parts = {milestone, build, branch, patch}; |
| return base::JoinString(parts, "."); |
| } |
| |
| } // namespace authpolicy |