blob: fb260419341ded35e59af68f7289d441661f6f1d [file] [log] [blame]
// Copyright 2015 The Chromium 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 "chrome/browser/chromeos/platform_keys/key_permissions.h"
#include <utility>
#include "base/base64.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/values.h"
#include "chrome/common/pref_names.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_namespace.h"
#include "components/policy/core/common/policy_service.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "extensions/browser/state_store.h"
#include "policy/policy_constants.h"
namespace chromeos {
namespace {
// The key at which platform key specific data is stored in each extension's
// state store.
//
// From older versions of ChromeOS, this key can hold a list of base64 and
// DER-encoded SPKIs. A key can be used for signing at most once if it is part
// of that list.
//
// The current format of data that is written to the PlatformKeys field is a
// list of serialized KeyEntry objects:
// { 'SPKI': string,
// 'signOnce': bool, // if not present, defaults to false
// 'signUnlimited': bool // if not present, defaults to false
// }
//
// Do not change this constant as clients will lose their existing state.
const char kStateStorePlatformKeys[] = "PlatformKeys";
const char kStateStoreSPKI[] = "SPKI";
const char kStateStoreSignOnce[] = "signOnce";
const char kStateStoreSignUnlimited[] = "signUnlimited";
// The profile pref prefs::kPlatformKeys stores a dictionary mapping from
// public key (base64 encoding of an DER-encoded SPKI) to key properties. The
// currently only key property is the key usage, which can either be undefined
// or "corporate". If a key is not present in the pref, the default for the key
// usage is undefined, which in particular means "not for corporate usage".
// E.g. the entry in the profile pref might look like:
// "platform_keys" : {
// "ABCDEF123" : {
// "keyUsage" : "corporate"
// },
// "abcdef567" : {
// "keyUsage" : "corporate"
// }
// }
const char kPrefKeyUsage[] = "keyUsage";
const char kPrefKeyUsageCorporate[] = "corporate";
const char kPolicyAllowCorporateKeyUsage[] = "allowCorporateKeyUsage";
} // namespace
struct KeyPermissions::PermissionsForExtension::KeyEntry {
explicit KeyEntry(const std::string& public_key_spki_der_b64)
: spki_b64(public_key_spki_der_b64) {}
// The base64-encoded DER of a X.509 Subject Public Key Info.
std::string spki_b64;
// True if the key can be used once for singing.
// This permission is granted if an extension generated a key using the
// enterprise.platformKeys API, so that it can build a certification request.
// After the first signing operation this permission will be revoked.
bool sign_once = false;
// True if the key can be used for signing an unlimited number of times.
// This permission is granted by the user to allow the extension to use the
// key for signing through the enterprise.platformKeys or platformKeys API.
// This permission is granted until revoked by the user or the policy.
bool sign_unlimited = false;
};
KeyPermissions::PermissionsForExtension::PermissionsForExtension(
const std::string& extension_id,
std::unique_ptr<base::Value> state_store_value,
PrefService* profile_prefs,
policy::PolicyService* profile_policies,
KeyPermissions* key_permissions)
: extension_id_(extension_id),
profile_prefs_(profile_prefs),
profile_policies_(profile_policies),
key_permissions_(key_permissions) {
DCHECK(profile_prefs_);
DCHECK(profile_policies_);
DCHECK(key_permissions_);
if (state_store_value)
KeyEntriesFromState(*state_store_value);
}
KeyPermissions::PermissionsForExtension::~PermissionsForExtension() {
}
bool KeyPermissions::PermissionsForExtension::CanUseKeyForSigning(
const std::string& public_key_spki_der) {
std::string public_key_spki_der_b64;
base::Base64Encode(public_key_spki_der, &public_key_spki_der_b64);
KeyEntry* matching_entry = GetStateStoreEntry(public_key_spki_der_b64);
// In any case, we allow the generating extension to use the generated key a
// single time for signing arbitrary data. The reason is, that the extension
// usually has to sign a certification request containing the public key in
// order to obtain a certificate for the key.
// That means, once a certificate authority generated a certificate for the
// key, the generating extension doesn't have access to the key anymore,
// except if explicitly permitted by the administrator.
if (matching_entry->sign_once)
return true;
// Usage of corporate keys is solely determined by policy. The user must not
// circumvent this decision.
if (key_permissions_->IsCorporateKey(public_key_spki_der_b64))
return PolicyAllowsCorporateKeyUsage();
// Only permissions for keys that are not designated for corporate usage are
// determined by user decisions.
return matching_entry->sign_unlimited;
}
void KeyPermissions::PermissionsForExtension::SetKeyUsedForSigning(
const std::string& public_key_spki_der) {
std::string public_key_spki_der_b64;
base::Base64Encode(public_key_spki_der, &public_key_spki_der_b64);
KeyEntry* matching_entry = GetStateStoreEntry(public_key_spki_der_b64);
if (!matching_entry->sign_once) {
if (matching_entry->sign_unlimited)
VLOG(1) << "Key is already marked as not usable for signing, skipping.";
else
LOG(ERROR) << "Key was not allowed for signing.";
return;
}
matching_entry->sign_once = false;
WriteToStateStore();
}
void KeyPermissions::PermissionsForExtension::RegisterKeyForCorporateUsage(
const std::string& public_key_spki_der) {
std::string public_key_spki_der_b64;
base::Base64Encode(public_key_spki_der, &public_key_spki_der_b64);
KeyEntry* matching_entry = GetStateStoreEntry(public_key_spki_der_b64);
if (matching_entry->sign_once) {
VLOG(1) << "Key is already allowed for signing, skipping.";
return;
}
matching_entry->sign_once = true;
WriteToStateStore();
DictionaryPrefUpdate update(profile_prefs_, prefs::kPlatformKeys);
std::unique_ptr<base::DictionaryValue> new_pref_entry(
new base::DictionaryValue);
new_pref_entry->SetStringWithoutPathExpansion(kPrefKeyUsage,
kPrefKeyUsageCorporate);
update->SetWithoutPathExpansion(public_key_spki_der_b64,
new_pref_entry.release());
}
void KeyPermissions::PermissionsForExtension::SetUserGrantedPermission(
const std::string& public_key_spki_der) {
if (!key_permissions_->CanUserGrantPermissionFor(public_key_spki_der)) {
LOG(WARNING) << "Tried to grant permission for a key although prohibited "
"(either key is a corporate key or this account is "
"managed).";
return;
}
std::string public_key_spki_der_b64;
base::Base64Encode(public_key_spki_der, &public_key_spki_der_b64);
KeyEntry* matching_entry = GetStateStoreEntry(public_key_spki_der_b64);
if (matching_entry->sign_unlimited) {
VLOG(1) << "Key is already allowed for signing, skipping.";
return;
}
matching_entry->sign_unlimited = true;
WriteToStateStore();
}
bool KeyPermissions::PermissionsForExtension::PolicyAllowsCorporateKeyUsage()
const {
const policy::PolicyMap& policies = profile_policies_->GetPolicies(
policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string()));
const base::Value* policy_value =
policies.GetValue(policy::key::kKeyPermissions);
if (!policy_value)
return false;
const base::DictionaryValue* key_permissions_map = nullptr;
policy_value->GetAsDictionary(&key_permissions_map);
if (!key_permissions_map) {
LOG(ERROR) << "Expected policy to be a dictionary.";
return false;
}
const base::DictionaryValue* key_permissions_for_ext = nullptr;
key_permissions_map->GetDictionaryWithoutPathExpansion(
extension_id_, &key_permissions_for_ext);
if (!key_permissions_for_ext)
return false;
bool allow_corporate_key_usage = false;
key_permissions_for_ext->GetBooleanWithoutPathExpansion(
kPolicyAllowCorporateKeyUsage, &allow_corporate_key_usage);
VLOG_IF(allow_corporate_key_usage, 2)
<< "Policy allows usage of corporate keys by extension " << extension_id_;
return allow_corporate_key_usage;
}
void KeyPermissions::PermissionsForExtension::WriteToStateStore() {
key_permissions_->SetPlatformKeysOfExtension(extension_id_,
KeyEntriesToState());
}
void KeyPermissions::PermissionsForExtension::KeyEntriesFromState(
const base::Value& state) {
state_store_entries_.clear();
const base::ListValue* entries = nullptr;
if (!state.GetAsList(&entries)) {
LOG(ERROR) << "Found a state store of wrong type.";
return;
}
for (const auto& entry : *entries) {
if (!entry) {
LOG(ERROR) << "Found invalid NULL entry in PlatformKeys state store.";
continue;
}
std::string spki_b64;
const base::DictionaryValue* dict_entry = nullptr;
if (entry->GetAsString(&spki_b64)) {
// This handles the case that the store contained a plain list of base64
// and DER-encoded SPKIs from an older version of ChromeOS.
KeyEntry new_entry(spki_b64);
new_entry.sign_once = true;
state_store_entries_.push_back(new_entry);
} else if (entry->GetAsDictionary(&dict_entry)) {
dict_entry->GetStringWithoutPathExpansion(kStateStoreSPKI, &spki_b64);
KeyEntry new_entry(spki_b64);
dict_entry->GetBooleanWithoutPathExpansion(kStateStoreSignOnce,
&new_entry.sign_once);
dict_entry->GetBooleanWithoutPathExpansion(kStateStoreSignUnlimited,
&new_entry.sign_unlimited);
state_store_entries_.push_back(new_entry);
} else {
LOG(ERROR) << "Found invalid entry of type " << entry->GetType()
<< " in PlatformKeys state store.";
continue;
}
}
}
std::unique_ptr<base::Value>
KeyPermissions::PermissionsForExtension::KeyEntriesToState() {
std::unique_ptr<base::ListValue> new_state(new base::ListValue);
for (const KeyEntry& entry : state_store_entries_) {
// Drop entries that the extension doesn't have any permissions for anymore.
if (!entry.sign_once && !entry.sign_unlimited)
continue;
std::unique_ptr<base::DictionaryValue> new_entry(new base::DictionaryValue);
new_entry->SetStringWithoutPathExpansion(kStateStoreSPKI, entry.spki_b64);
// Omit writing default values, namely |false|.
if (entry.sign_once) {
new_entry->SetBooleanWithoutPathExpansion(kStateStoreSignOnce,
entry.sign_once);
}
if (entry.sign_unlimited) {
new_entry->SetBooleanWithoutPathExpansion(kStateStoreSignUnlimited,
entry.sign_unlimited);
}
new_state->Append(new_entry.release());
}
return std::move(new_state);
}
KeyPermissions::PermissionsForExtension::KeyEntry*
KeyPermissions::PermissionsForExtension::GetStateStoreEntry(
const std::string& public_key_spki_der_b64) {
for (KeyEntry& entry : state_store_entries_) {
// For every ASN.1 value there is exactly one DER encoding, so it is fine to
// compare the DER (or its base64 encoding).
if (entry.spki_b64 == public_key_spki_der_b64)
return &entry;
}
state_store_entries_.push_back(KeyEntry(public_key_spki_der_b64));
return &state_store_entries_.back();
}
KeyPermissions::KeyPermissions(bool profile_is_managed,
PrefService* profile_prefs,
policy::PolicyService* profile_policies,
extensions::StateStore* extensions_state_store)
: profile_is_managed_(profile_is_managed),
profile_prefs_(profile_prefs),
profile_policies_(profile_policies),
extensions_state_store_(extensions_state_store),
weak_factory_(this) {
DCHECK(profile_prefs_);
DCHECK(extensions_state_store_);
DCHECK(!profile_is_managed_ || profile_policies_);
}
KeyPermissions::~KeyPermissions() {
}
void KeyPermissions::GetPermissionsForExtension(
const std::string& extension_id,
const PermissionsCallback& callback) {
extensions_state_store_->GetExtensionValue(
extension_id, kStateStorePlatformKeys,
base::Bind(&KeyPermissions::CreatePermissionObjectAndPassToCallback,
weak_factory_.GetWeakPtr(), extension_id, callback));
}
bool KeyPermissions::CanUserGrantPermissionFor(
const std::string& public_key_spki_der) const {
// As keys cannot be tagged for non-corporate usage, the user can currently
// not grant any permissions if the profile is managed.
if (profile_is_managed_)
return false;
std::string public_key_spki_der_b64;
base::Base64Encode(public_key_spki_der, &public_key_spki_der_b64);
// If this profile is not managed but we find a corporate key, don't allow
// the user to grant permissions.
return !IsCorporateKey(public_key_spki_der_b64);
}
bool KeyPermissions::IsCorporateKey(
const std::string& public_key_spki_der_b64) const {
const base::DictionaryValue* prefs_entry =
GetPrefsEntry(public_key_spki_der_b64);
if (prefs_entry) {
std::string key_usage;
prefs_entry->GetStringWithoutPathExpansion(kPrefKeyUsage, &key_usage);
return key_usage == kPrefKeyUsageCorporate;
}
return false;
}
void KeyPermissions::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
// For the format of the dictionary see the documentation at kPrefKeyUsage.
registry->RegisterDictionaryPref(prefs::kPlatformKeys);
}
void KeyPermissions::CreatePermissionObjectAndPassToCallback(
const std::string& extension_id,
const PermissionsCallback& callback,
std::unique_ptr<base::Value> value) {
callback.Run(base::WrapUnique(
new PermissionsForExtension(extension_id, std::move(value),
profile_prefs_, profile_policies_, this)));
}
void KeyPermissions::SetPlatformKeysOfExtension(
const std::string& extension_id,
std::unique_ptr<base::Value> value) {
extensions_state_store_->SetExtensionValue(
extension_id, kStateStorePlatformKeys, std::move(value));
}
const base::DictionaryValue* KeyPermissions::GetPrefsEntry(
const std::string& public_key_spki_der_b64) const {
const base::DictionaryValue* platform_keys =
profile_prefs_->GetDictionary(prefs::kPlatformKeys);
const base::DictionaryValue* key_entry = nullptr;
platform_keys->GetDictionaryWithoutPathExpansion(public_key_spki_der_b64,
&key_entry);
return key_entry;
}
} // namespace chromeos