Skip to content

Commit

Permalink
chrome_extensions: Compute the identifier from the 'key' property (os…
Browse files Browse the repository at this point in the history
  • Loading branch information
alessandrogario authored May 27, 2021
1 parent 5abaa76 commit 2ac2be7
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 8 deletions.
11 changes: 9 additions & 2 deletions osquery/tables/applications/chrome/chrome_extensions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,15 @@ QueryData genChromeExtensions(QueryContext& context) {

row["install_timestamp"] = BIGINT(converted_timestamp);

row["identifier"] =
SQL_TEXT(getExtensionProfileSettingsValue(extension, "identifier"));
row["referenced_identifier"] = SQL_TEXT(
getExtensionProfileSettingsValue(extension, "referenced_identifier"));

if (extension.opt_computed_identifier.has_value()) {
const auto& computed_identifier = *extension.opt_computed_identifier;
row["identifier"] = SQL_TEXT(computed_identifier);
} else {
row["identifier"] = SQL_TEXT("");
}

// This column has been deprecated and is marked as hidden. It will
// be removed in a future version
Expand Down
59 changes: 55 additions & 4 deletions osquery/tables/applications/chrome/tests/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ constexpr auto kTestExtensionManifest = R"MANIFEST(
"default_locale": "en",
"current_locale": "en",
"description": "Description",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmJNzUNVjS6Q1qe0NRqpmfX/oSJdgauSZNdfeb5RV1Hji21vX0TivpP5gq0fadwmvmVCtUpOaNUopgejiUFm/iKHPs0o3x7hyKk/eX0t2QT3OZGdXkPiYpTEC0f0p86SQaLoA2eHaOG4uCGi7sxLJmAXc6IsxGKVklh7cCoLUgWEMnj8ZNG2Y8UKG3gBdrpES5hk7QyFDMraO79NmSlWRNgoJHX6XRoY66oYThFQad8KL8q3pf3Oe8uBLKywohU0ZrDPViWHIszXoE9HEvPTFAbHZ1umINni4W/YVs+fhqHtzRJcaKJtsTaYy+cholu5mAYeTZqtHf6bcwJ8t9i2afwIDAQAB",
"name": "Test extension",
"permissions": [ "1", "2" ],
"optional_permissions": [ "3", "4" ],
Expand Down Expand Up @@ -136,6 +137,14 @@ const std::vector<std::pair<std::string, std::string>>
const std::unordered_map<std::string, std::string>
kExpectedExtensionProperties = {
{"name", "Test extension"},
{"key",
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmJNzUNVjS6Q1qe0NRqpmfX/"
"oSJdgauSZNdfeb5RV1Hji21vX0TivpP5gq0fadwmvmVCtUpOaNUopgejiUFm/"
"iKHPs0o3x7hyKk/"
"eX0t2QT3OZGdXkPiYpTEC0f0p86SQaLoA2eHaOG4uCGi7sxLJmAXc6IsxGKVklh7cCoLU"
"gWEMnj8ZNG2Y8UKG3gBdrpES5hk7QyFDMraO79NmSlWRNgoJHX6XRoY66oYThFQad8KL8"
"q3pf3Oe8uBLKywohU0ZrDPViWHIszXoE9HEvPTFAbHZ1umINni4W/"
"YVs+fhqHtzRJcaKJtsTaYy+cholu5mAYeTZqtHf6bcwJ8t9i2afwIDAQAB"},
{"update_url", "https://clients2.google.com/service/update2/crx"},
{"version", "1.00.0"},
{"author", "Author"},
Expand All @@ -149,6 +158,9 @@ const std::unordered_map<std::string, std::string>
{"optional_permissions_json", "{\"\":\"3\",\"\":\"4\"}\n"},
};

const std::string kExpectedComputedExtensionIdentifier{
"cjpalhdlnbpafiamejdnhcphjbkeiagm"};

} // namespace

class ChromeUtilsTests : public ::testing::Test {};
Expand Down Expand Up @@ -290,14 +302,17 @@ TEST_F(ChromeUtilsTests, getExtensionProfileSettings) {
auto state = getExtensionProfileSettingsValue(extension, "state");
auto from_webstore =
getExtensionProfileSettingsValue(extension, "from_webstore");

auto install_time =
getExtensionProfileSettingsValue(extension, "install_time");
auto identifier = getExtensionProfileSettingsValue(extension, "identifier");

auto ref_identifier =
getExtensionProfileSettingsValue(extension, "referenced_identifier");

EXPECT_EQ(state, "1");
EXPECT_EQ(from_webstore, "true");
EXPECT_EQ(install_time, "13251308956895241");
EXPECT_EQ(identifier, "extension_identifier1");
EXPECT_EQ(ref_identifier, "extension_identifier1");

status = getExtensionProfileSettings(extension.profile_settings,
parsed_preferences,
Expand All @@ -307,12 +322,13 @@ TEST_F(ChromeUtilsTests, getExtensionProfileSettings) {
state = getExtensionProfileSettingsValue(extension, "state");
from_webstore = getExtensionProfileSettingsValue(extension, "from_webstore");
install_time = getExtensionProfileSettingsValue(extension, "install_time");
identifier = getExtensionProfileSettingsValue(extension, "identifier");
ref_identifier =
getExtensionProfileSettingsValue(extension, "referenced_identifier");

EXPECT_EQ(state, "0");
EXPECT_EQ(from_webstore, "false");
EXPECT_EQ(install_time, "13251308956895242");
EXPECT_EQ(identifier, "extension_identifier2");
EXPECT_EQ(ref_identifier, "extension_identifier2");
}

TEST_F(ChromeUtilsTests, getExtensionFromSnapshot) {
Expand Down Expand Up @@ -343,6 +359,12 @@ TEST_F(ChromeUtilsTests, getExtensionFromSnapshot) {

EXPECT_EQ(extension.content_scripts_matches.size(),
kExpectedContentScriptsMatches.size());

// Also make sure that the computed identifier is correct
ASSERT_TRUE(extension.opt_computed_identifier.has_value());

const auto& computed_identifier = *extension.opt_computed_identifier;
EXPECT_EQ(computed_identifier, kExpectedComputedExtensionIdentifier);
}

TEST_F(ChromeUtilsTests, getChromeProfilesFromSnapshotList) {
Expand Down Expand Up @@ -399,6 +421,35 @@ TEST_F(ChromeUtilsTests, webkitTimeToUnixTimestamp) {
ASSERT_EQ(timestamp_exp.getErrorCode(), ConversionError::InvalidArgument);
}

TEST_F(ChromeUtilsTests, computeExtensionIdentifier) {
ChromeProfile::Extension extension;
auto identifier_exp = computeExtensionIdentifier(extension);
ASSERT_TRUE(identifier_exp.isError());
EXPECT_EQ(identifier_exp.getErrorCode(), ExtensionKeyError::MissingProperty);

extension.properties.insert({"key", "hello, world!"});

identifier_exp = computeExtensionIdentifier(extension);
ASSERT_TRUE(identifier_exp.isError());
EXPECT_EQ(identifier_exp.getErrorCode(), ExtensionKeyError::InvalidValue);

extension.properties.clear();
extension.properties.insert(
{"key",
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmJNzUNVjS6Q1qe0NRqpmfX/"
"oSJdgauSZNdfeb5RV1Hji21vX0TivpP5gq0fadwmvmVCtUpOaNUopgejiUFm/"
"iKHPs0o3x7hyKk/"
"eX0t2QT3OZGdXkPiYpTEC0f0p86SQaLoA2eHaOG4uCGi7sxLJmAXc6IsxGKVklh7cCoLUgW"
"EMnj8ZNG2Y8UKG3gBdrpES5hk7QyFDMraO79NmSlWRNgoJHX6XRoY66oYThFQad8KL8q3pf"
"3Oe8uBLKywohU0ZrDPViWHIszXoE9HEvPTFAbHZ1umINni4W/"
"YVs+fhqHtzRJcaKJtsTaYy+cholu5mAYeTZqtHf6bcwJ8t9i2afwIDAQAB"});
identifier_exp = computeExtensionIdentifier(extension);
ASSERT_FALSE(identifier_exp.isError());

auto identifier = identifier_exp.get();
EXPECT_EQ(identifier, kExpectedComputedExtensionIdentifier);
}

} // namespace tables

} // namespace osquery
75 changes: 74 additions & 1 deletion osquery/tables/applications/chrome/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include <osquery/logger/logger.h>
#include <osquery/tables/applications/chrome/utils.h>
#include <osquery/tables/system/system_utils.h>
#include <osquery/utils/base64.h>
#include <osquery/utils/conversions/tryto.h>
#include <osquery/utils/info/platform_type.h>

namespace osquery {
Expand Down Expand Up @@ -137,6 +139,8 @@ const ExtensionPropertyMap kExtensionPropertyList = {
{ExtensionProperty::Type::StringArray,
"optional_permissions",
"optional_permissions"},

{ExtensionProperty::Type::String, "key", "key"},
};

/// Active extension profile settings
Expand Down Expand Up @@ -869,7 +873,7 @@ Status getExtensionProfileSettings(
extension_path);
}

profile_settings["identifier"] = extension_id;
profile_settings["referenced_identifier"] = extension_id;

for (const auto& property_name : kExtensionProfileSettingsList) {
const auto& opt_property = extension_obj.get_child_optional(property_name);
Expand Down Expand Up @@ -947,6 +951,15 @@ Status getExtensionFromSnapshot(
pt::write_json(stream, parsed_manifest, false);
output.manifest_json = stream.str();

// Attempt to compute the real extension identifier
auto identifier_exp = computeExtensionIdentifier(output);
if (identifier_exp.isError()) {
LOG(ERROR) << identifier_exp.getError().getMessage();

} else {
output.opt_computed_identifier = identifier_exp.take();
}

extension = std::move(output);
return Status::success();
}
Expand Down Expand Up @@ -1165,6 +1178,66 @@ std::string getExtensionProfileSettingsValue(
return value;
}

ExpectedExtensionKey computeExtensionIdentifier(
const ChromeProfile::Extension& extension) {
auto extension_key_it = extension.properties.find("key");
if (extension_key_it == extension.properties.end()) {
return ExpectedExtensionKey::failure(
ExtensionKeyError::MissingProperty,
"The 'key' property is missing from the extension manifest");
}

const auto& encoded_key = extension_key_it->second;

auto decoded_key = base64::decode(encoded_key);
if (decoded_key.empty()) {
return ExpectedExtensionKey::failure(
ExtensionKeyError::InvalidValue,
"The 'key' property of the extension manifest could not be properly "
"base64 decoded");
}

auto decoded_key_hash =
hashFromBuffer(HASH_TYPE_SHA256, decoded_key.data(), decoded_key.size());
if (decoded_key_hash.size() != 64) {
return ExpectedExtensionKey::failure(
ExtensionKeyError::HashingError,
"The 'key' property of the extension manifest could not be properly "
"sha256 hashed");
}

auto hash_prefix = decoded_key_hash.substr(0, 32U);

std::string identifier;
identifier.reserve(hash_prefix.size());

std::string buffer(1, '\x00');

for (auto c : hash_prefix) {
buffer[0] = c;

auto as_int_exp = tryTo<int>(buffer, 16);
if (as_int_exp.isError()) {
return ExpectedExtensionKey::failure(
ExtensionKeyError::TransformationError,
"Failed to transform the 'key' property of the extension manifest");
}

auto as_int = as_int_exp.take();

auto ascii = 'a' + as_int;
if (ascii < 0x61 || ascii > 0x122) {
return ExpectedExtensionKey::failure(
ExtensionKeyError::TransformationError,
"Failed to transform the 'key' property of the extension manifest");
}

identifier.push_back(static_cast<char>(ascii));
}

return ExpectedExtensionKey::success(identifier);
}

} // namespace tables

} // namespace osquery
17 changes: 17 additions & 0 deletions osquery/tables/applications/chrome/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#pragma once

#include <boost/optional.hpp>
#include <boost/property_tree/ptree.hpp>

#include <osquery/core/tables.h>
Expand Down Expand Up @@ -118,6 +119,9 @@ struct ChromeProfile final {

/// The 'matches' entries inside 'content_scripts'
ContentScriptsEntryList content_scripts_matches;

/// The extension id, computed from the 'key' property
boost::optional<std::string> opt_computed_identifier;
};

/// A list of extensions
Expand Down Expand Up @@ -195,6 +199,19 @@ using ExpectedUnixTimestamp = Expected<std::int64_t, ConversionError>;
/// Converts a timestamp from Webkit to Unix format
ExpectedUnixTimestamp webkitTimeToUnixTimestamp(const std::string& timestamp);

enum class ExtensionKeyError {
MissingProperty,
InvalidValue,
HashingError,
TransformationError,
};

using ExpectedExtensionKey = Expected<std::string, ExtensionKeyError>;

/// Computes the extension id based on the given key
ExpectedExtensionKey computeExtensionIdentifier(
const ChromeProfile::Extension& extension);

} // namespace tables

} // namespace osquery
4 changes: 3 additions & 1 deletion specs/chrome_extensions.table
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ schema([
Column("name", TEXT, "Extension display name"),
Column("profile", TEXT, "The name of the Chrome profile that contains this extension"),
Column("profile_path", TEXT, "The profile path"),
Column("identifier", TEXT, "Extension identifier (folder name)"),
Column("referenced_identifier", TEXT, "Extension identifier, as specified by the preferences file. Empty if the extension is not in the profile."),
Column("identifier", TEXT, "Extension identifier, computed from its manifest. Empty in case of error."),
Column("version", TEXT, "Extension-supplied version"),
Column("description", TEXT, "Extension-optional description"),
Column("default_locale", TEXT, "Default locale supported by extension", aliases=["locale"]),
Expand All @@ -26,6 +27,7 @@ schema([
Column("install_time", TEXT, "Extension install time, in its original Webkit format"),
Column("install_timestamp", BIGINT, "Extension install time, converted to unix time"),
Column("manifest_json", TEXT, "The manifest file of the extension", hidden=True),
Column("key", TEXT, "The extension key, from the manifest file", hidden=True),
ForeignKey(column="uid", table="users"),
])
attributes(user_data=True)
Expand Down
1 change: 1 addition & 0 deletions tests/integration/tables/chrome_extensions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ TEST_F(chromeExtensions, test_sanity) {
{"profile", NormalType},
{"profile_path", NormalType},
{"identifier", NormalType},
{"referenced_identifier", NormalType},
{"version", NormalType},
{"description", NormalType},
{"default_locale", NormalType},
Expand Down

0 comments on commit 2ac2be7

Please sign in to comment.