Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1610)

Unified Diff: chrome/browser/chromeos/platform_keys/platform_keys_service.cc

Issue 905523002: platformKeys: Add per-extension sign permissions. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@pks_sign_task
Patch Set: Addressed comment. Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/chromeos/platform_keys/platform_keys_service.cc
diff --git a/chrome/browser/chromeos/platform_keys/platform_keys_service.cc b/chrome/browser/chromeos/platform_keys/platform_keys_service.cc
index 4f9eb1761d888a5fcf95cfc08cecab7a476c01ba..01abc1ae0661865d7d969651ad9cd91812623cc2 100644
--- a/chrome/browser/chromeos/platform_keys/platform_keys_service.cc
+++ b/chrome/browser/chromeos/platform_keys/platform_keys_service.cc
@@ -17,18 +17,125 @@ using content::BrowserThread;
namespace chromeos {
+struct PlatformKeysService::KeyEntry {
+ // 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 or by policy 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;
+};
+
namespace {
const char kErrorKeyNotAllowedForSigning[] =
"This key is not allowed for signing. Either it was used for signing "
"before or it was not correctly generated.";
+
+// 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
+// and removed from that list afterwards.
+//
+// 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";
+
+scoped_ptr<PlatformKeysService::KeyEntries> KeyEntriesFromState(
+ const base::Value& state) {
+ scoped_ptr<PlatformKeysService::KeyEntries> new_entries(
+ new PlatformKeysService::KeyEntries);
+
+ const base::ListValue* entries = nullptr;
+ if (!state.GetAsList(&entries)) {
+ LOG(ERROR) << "Found a state store of wrong type.";
+ return new_entries.Pass();
+ }
+ for (const base::Value* entry : *entries) {
+ if (!entry) {
+ LOG(ERROR) << "Found invalid NULL entry in PlatformKeys state store.";
+ continue;
+ }
+
+ PlatformKeysService::KeyEntry new_entry;
+ const base::DictionaryValue* dict_entry = nullptr;
+ if (entry->GetAsString(&new_entry.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.
+ new_entry.sign_once = true;
+ } else if (entry->GetAsDictionary(&dict_entry)) {
+ dict_entry->GetStringWithoutPathExpansion(kStateStoreSPKI,
+ &new_entry.spki_b64);
+ dict_entry->GetBooleanWithoutPathExpansion(kStateStoreSignOnce,
+ &new_entry.sign_once);
+ dict_entry->GetBooleanWithoutPathExpansion(kStateStoreSignUnlimited,
+ &new_entry.sign_unlimited);
+ } else {
+ LOG(ERROR) << "Found invalid entry of type " << entry->GetType()
+ << " in PlatformKeys state store.";
+ continue;
+ }
+ new_entries->push_back(new_entry);
+ }
+ return new_entries.Pass();
+}
+
+scoped_ptr<base::ListValue> KeyEntriesToState(
+ const PlatformKeysService::KeyEntries& entries) {
+ scoped_ptr<base::ListValue> new_state(new base::ListValue);
+ for (const PlatformKeysService::KeyEntry& entry : entries) {
+ // Drop entries that the extension doesn't have any permissions for anymore.
+ if (!entry.sign_once && !entry.sign_unlimited)
+ continue;
+
+ scoped_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 new_state.Pass();
+}
-scoped_ptr<base::StringValue> GetPublicKeyValue(
- 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);
- return make_scoped_ptr(new base::StringValue(public_key_spki_der_b64));
+// Searches |platform_keys| for an entry for |public_key_spki_der_b64|. If found
+// returns a pointer to it, otherwise returns null.
+PlatformKeysService::KeyEntry* GetMatchingEntry(
+ const std::string& public_key_spki_der_b64,
+ PlatformKeysService::KeyEntries* platform_keys) {
+ for (PlatformKeysService::KeyEntry& entry : *platform_keys) {
+ // 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;
+ }
+ return nullptr;
}
} // namespace
@@ -55,14 +162,16 @@ class PlatformKeysService::PermissionUpdateTask : public Task {
// Creates a task that reads the current permission for an extension to access
// a certain key. Afterwards it updates and persists the permission to the new
// value |new_permission_value|. |callback| will be run after the permission
- // was persisted. The old permission value is then accessible through
- // old_permission_value().
- PermissionUpdateTask(const bool new_permission_value,
+ // was persisted. The old permission values are then available through
+ // old_key_entry().
+ PermissionUpdateTask(const SignPermission permission,
+ const bool new_permission_value,
const std::string& public_key_spki_der,
const std::string& extension_id,
base::Callback<void(Task*)> callback,
PlatformKeysService* service)
- : new_permission_value_(new_permission_value),
+ : permission_(permission),
+ new_permission_value_(new_permission_value),
public_key_spki_der_(public_key_spki_der),
extension_id_(extension_id),
callback_(callback),
@@ -78,9 +187,9 @@ class PlatformKeysService::PermissionUpdateTask : public Task {
bool IsDone() override { return next_step_ == Step::DONE; }
- // The original permission value before setting the new value
- // |new_permission_value|.
- bool old_permission_value() { return old_permission_value_; }
+ // The old key entry with the old permissions before setting |permission| to
+ // the new value |new_permission_value|.
+ const KeyEntry& old_key_entry() { return old_key_entry_; }
private:
void DoStep() {
@@ -113,38 +222,59 @@ class PlatformKeysService::PermissionUpdateTask : public Task {
weak_factory_.GetWeakPtr()));
}
- void GotPlatformKeys(scoped_ptr<base::ListValue> platform_keys) {
+ void GotPlatformKeys(scoped_ptr<KeyEntries> platform_keys) {
platform_keys_ = platform_keys.Pass();
DoStep();
}
- // Returns whether the extension has permission to use the key for signing
- // according to the PlatformKeys value read from the extensions state store.
- // Invalidates the key if it was found to be valid.
+ // Persists the existing KeyEntry in |old_key_entry_|, updates the entry with
+ // the new permission and persists it to the extension's state store if it was
+ // changed.
void WriteUpdate() {
- scoped_ptr<base::StringValue> key_value(
- GetPublicKeyValue(public_key_spki_der_));
-
- base::ListValue::const_iterator it = platform_keys_->Find(*key_value);
- old_permission_value_ = it != platform_keys_->end();
- if (old_permission_value_ == new_permission_value_)
- return;
-
- if (new_permission_value_)
- platform_keys_->Append(key_value.release());
- else
- platform_keys_->Remove(*key_value, nullptr);
+ DCHECK(platform_keys_);
+
+ std::string public_key_spki_der_b64;
+ base::Base64Encode(public_key_spki_der_, &public_key_spki_der_b64);
+
+ KeyEntry* matching_entry =
+ GetMatchingEntry(public_key_spki_der_b64, platform_keys_.get());
+
+ if (!matching_entry) {
+ platform_keys_->push_back(KeyEntry());
+ matching_entry = &platform_keys_->back();
+ matching_entry->spki_b64 = public_key_spki_der_b64;
+ } else if (permission_ == SignPermission::ONCE && new_permission_value_) {
+ // The one-time sign permission is supposed to be granted once per key
+ // during generation. Generated keys should be unique and thus this case
+ // should never occur.
+ NOTREACHED() << "Requested one-time sign permission on existing key.";
+ }
+ old_key_entry_ = *matching_entry;
+
+ bool* permission_value = nullptr;
+ switch (permission_) {
+ case SignPermission::ONCE:
+ permission_value = &matching_entry->sign_once;
+ break;
+ case SignPermission::UNLIMITED:
+ permission_value = &matching_entry->sign_unlimited;
+ break;
+ }
- service_->SetPlatformKeysOfExtension(extension_id_, platform_keys_.Pass());
+ if (*permission_value != new_permission_value_) {
+ *permission_value = new_permission_value_;
+ service_->SetPlatformKeysOfExtension(extension_id_, *platform_keys_);
+ }
}
Step next_step_ = Step::READ_PLATFORM_KEYS;
- scoped_ptr<base::ListValue> platform_keys_;
- bool old_permission_value_ = false;
+ KeyEntry old_key_entry_;
+ const SignPermission permission_;
const bool new_permission_value_;
const std::string public_key_spki_der_;
const std::string extension_id_;
+ scoped_ptr<KeyEntries> platform_keys_;
base::Callback<void(Task*)> callback_;
PlatformKeysService* const service_;
base::WeakPtrFactory<PermissionUpdateTask> weak_factory_;
@@ -198,17 +328,21 @@ class PlatformKeysService::SignTask : public Task {
next_step_ = Step::SIGN_OR_ABORT;
UpdatePermission();
return;
- case Step::SIGN_OR_ABORT:
+ case Step::SIGN_OR_ABORT: {
next_step_ = Step::DONE;
- if (!service_->permission_check_enabled_ ||
- permission_update_->old_permission_value()) {
+ bool sign_granted = permission_update_->old_key_entry().sign_once ||
+ permission_update_->old_key_entry().sign_unlimited;
+ if (sign_granted) {
Sign();
} else {
- callback_.Run(std::string() /* no signature */,
- kErrorKeyNotAllowedForSigning);
+ if (!callback_.is_null()) {
+ callback_.Run(std::string() /* no signature */,
+ kErrorKeyNotAllowedForSigning);
+ }
DoStep();
}
return;
+ }
case Step::DONE:
service_->TaskFinished(this);
// |this| might be invalid now.
@@ -221,7 +355,8 @@ class PlatformKeysService::SignTask : public Task {
// signing operations with that key.
void UpdatePermission() {
permission_update_.reset(new PermissionUpdateTask(
- false /* new permission value */, public_key_, extension_id_,
+ SignPermission::ONCE, false /* new permission value */, public_key_,
+ extension_id_,
base::Bind(&SignTask::DidUpdatePermission, weak_factory_.GetWeakPtr()),
service_));
permission_update_->Start();
@@ -251,7 +386,7 @@ class PlatformKeysService::SignTask : public Task {
}
Step next_step_ = Step::UPDATE_PERMISSION;
- scoped_ptr<base::ListValue> platform_keys_;
+ scoped_ptr<KeyEntries> platform_keys_;
scoped_ptr<PermissionUpdateTask> permission_update_;
const std::string token_id_;
@@ -271,6 +406,213 @@ class PlatformKeysService::SignTask : public Task {
DISALLOW_COPY_AND_ASSIGN(SignTask);
};
+class PlatformKeysService::SelectTask : public Task {
+ public:
+ enum class Step {
+ GET_MATCHING_CERTS,
+ SELECT_CERTS,
+ READ_PLATFORM_KEYS,
+ UPDATE_PERMISSION,
+ FILTER_BY_PERMISSIONS,
+ DONE,
+ };
+
+ // This task determines all known client certs matching |request|. If
+ // |interactive| is true, calls |service->select_delegate_->Select()| to
+ // select a cert from all matches. The extension with |extension_id| will be
+ // granted unlimited sign permission for the selected cert.
+ // Finally, either the selection or, if |interactive| is false, matching certs
+ // that the extension has permission for are passed to |callback|.
+ SelectTask(const platform_keys::ClientCertificateRequest& request,
+ bool interactive,
+ const std::string& extension_id,
+ const SelectCertificatesCallback& callback,
+ PlatformKeysService* service)
+ : request_(request),
+ interactive_(interactive),
+ extension_id_(extension_id),
+ callback_(callback),
+ service_(service),
+ weak_factory_(this) {}
+ ~SelectTask() override {}
+
+ void Start() override {
+ CHECK(next_step_ == Step::GET_MATCHING_CERTS);
+ DoStep();
+ }
+ bool IsDone() override { return next_step_ == Step::DONE; }
+
+ private:
+ void DoStep() {
+ switch (next_step_) {
+ case Step::GET_MATCHING_CERTS:
+ if (interactive_)
+ next_step_ = Step::SELECT_CERTS;
+ else // Skip SelectCerts and UpdatePermission if not interactive.
+ next_step_ = Step::READ_PLATFORM_KEYS;
+ GetMatchingCerts();
+ return;
+ case Step::SELECT_CERTS:
+ next_step_ = Step::UPDATE_PERMISSION;
+ SelectCerts();
+ return;
+ case Step::UPDATE_PERMISSION:
+ next_step_ = Step::READ_PLATFORM_KEYS;
+ UpdatePermission();
+ return;
+ case Step::READ_PLATFORM_KEYS:
+ next_step_ = Step::FILTER_BY_PERMISSIONS;
+ ReadPlatformKeys();
+ return;
+ case Step::FILTER_BY_PERMISSIONS:
+ next_step_ = Step::DONE;
+ FilterSelectionByPermission();
+ return;
+ case Step::DONE:
+ service_->TaskFinished(this);
+ // |this| might be invalid now.
+ return;
+ }
+ }
+
+ // Retrieves all certificates matching |request_|. Will call back to
+ // |GotMatchingCerts()|.
+ void GetMatchingCerts() {
+ platform_keys::subtle::SelectClientCertificates(
+ request_,
+ base::Bind(&SelectTask::GotMatchingCerts, weak_factory_.GetWeakPtr()),
+ service_->browser_context_);
+ }
+
+ // If the certificate request could be processed successfully, |matches| will
+ // contain the list of matching certificates (maybe empty) and |error_message|
+ // will be empty. If an error occurred, |matches| will be null and
+ // |error_message| contain an error message.
+ void GotMatchingCerts(scoped_ptr<net::CertificateList> matches,
+ const std::string& error_message) {
+ if (!error_message.empty()) {
+ next_step_ = Step::DONE;
+ callback_.Run(nullptr /* no certificates */, error_message);
+ DoStep();
+ return;
+ }
+ matches_.swap(*matches);
+ DoStep();
+ }
+
+ // Calls |service_->select_delegate_->Select()| to select a cert from
+ // |matches_|, which will be stored in |selected_cert_|.
+ // Will call back to |GotSelection()|.
+ void SelectCerts() {
+ CHECK(interactive_);
+ if (matches_.empty()) {
+ // Don't show a select dialog if no certificate is matching.
+ DoStep();
+ return;
+ }
+ service_->select_delegate_->Select(
+ extension_id_, matches_,
+ base::Bind(&SelectTask::GotSelection, base::Unretained(this)));
+ }
+
+ // Will be called by |SelectCerts()| with the selected cert or null if no cert
+ // was selected.
+ void GotSelection(scoped_refptr<net::X509Certificate> selected_cert) {
+ selected_cert_ = selected_cert;
+ DoStep();
+ }
+
+ // Updates the extension's state store about unlimited sign permission for the
+ // selected cert. Does nothing if no cert was selected.
+ // Will call back to |DidUpdatePermission()|.
+ void UpdatePermission() {
+ CHECK(interactive_);
+ if (!selected_cert_) {
+ DoStep();
+ return;
+ }
+ const std::string public_key_spki_der(
+ platform_keys::GetSubjectPublicKeyInfo(selected_cert_));
+ permission_update_.reset(new PermissionUpdateTask(
+ SignPermission::UNLIMITED, true /* new permission value */,
+ public_key_spki_der, extension_id_,
+ base::Bind(&SelectTask::DidUpdatePermission, base::Unretained(this)),
+ service_));
+ permission_update_->Start();
+ }
+
+ void DidUpdatePermission(Task* /* task */) { DoStep(); }
+
+ // Reads the PlatformKeys value from the extension's state store and calls
+ // back to GotPlatformKeys().
+ void ReadPlatformKeys() {
+ service_->GetPlatformKeysOfExtension(
+ extension_id_,
+ base::Bind(&SelectTask::GotPlatformKeys, weak_factory_.GetWeakPtr()));
+ }
+
+ void GotPlatformKeys(scoped_ptr<KeyEntries> platform_keys) {
+ platform_keys_ = platform_keys.Pass();
+ DoStep();
+ }
+
+ // Filters from all matches (if not interactive) or from the selection (if
+ // interactive), the certificates that the extension has unlimited sign
+ // permission for. Passes the filtered certs to |callback_|.
+ void FilterSelectionByPermission() {
+ scoped_ptr<net::CertificateList> selection(new net::CertificateList);
+ if (interactive_) {
+ if (selected_cert_)
+ selection->push_back(selected_cert_);
+ } else {
+ selection->assign(matches_.begin(), matches_.end());
+ }
+
+ scoped_ptr<net::CertificateList> filtered_certs(new net::CertificateList);
+ for (scoped_refptr<net::X509Certificate> selected_cert : *selection) {
+ const std::string public_key_spki_der(
+ platform_keys::GetSubjectPublicKeyInfo(selected_cert));
+ std::string public_key_spki_der_b64;
+ base::Base64Encode(public_key_spki_der, &public_key_spki_der_b64);
+
+ KeyEntry* matching_entry =
+ GetMatchingEntry(public_key_spki_der_b64, platform_keys_.get());
+ if (!matching_entry || !matching_entry->sign_unlimited)
+ continue;
+
+ filtered_certs->push_back(selected_cert);
+ }
+ // Note: In the interactive case this should have filtered exactly the
+ // one selected cert. Checking the permissions again is not striclty
+ // necessary but this ensures that the permissions were updated correctly.
+ CHECK(!selected_cert_ || (filtered_certs->size() == 1 &&
+ filtered_certs->front() == selected_cert_));
+ callback_.Run(filtered_certs.Pass(), std::string() /* no error */);
+ DoStep();
+ }
+
+ Step next_step_ = Step::GET_MATCHING_CERTS;
+ scoped_ptr<KeyEntries> platform_keys_;
+ scoped_ptr<PermissionUpdateTask> permission_update_;
+
+ net::CertificateList matches_;
+ scoped_refptr<net::X509Certificate> selected_cert_;
+ platform_keys::ClientCertificateRequest request_;
+ const bool interactive_;
+ const std::string extension_id_;
+ const SelectCertificatesCallback callback_;
+ PlatformKeysService* const service_;
+ base::WeakPtrFactory<SelectTask> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelectTask);
+};
+
+PlatformKeysService::SelectDelegate::SelectDelegate() {
+}
+
+PlatformKeysService::SelectDelegate::~SelectDelegate() {
+}
+
PlatformKeysService::PlatformKeysService(
content::BrowserContext* browser_context,
extensions::StateStore* state_store)
@@ -283,8 +625,22 @@ PlatformKeysService::PlatformKeysService(
PlatformKeysService::~PlatformKeysService() {
}
-void PlatformKeysService::DisablePermissionCheckForTesting() {
- permission_check_enabled_ = false;
+void PlatformKeysService::SetSelectDelegate(
+ scoped_ptr<SelectDelegate> delegate) {
+ select_delegate_ = delegate.Pass();
+}
+
+void PlatformKeysService::GrantUnlimitedSignPermission(
+ const std::string& extension_id,
+ scoped_refptr<net::X509Certificate> cert) {
+ const std::string public_key_spki_der(
+ platform_keys::GetSubjectPublicKeyInfo(cert));
+
+ StartOrQueueTask(make_scoped_ptr(new PermissionUpdateTask(
+ SignPermission::UNLIMITED, true /* new permission value */,
+ public_key_spki_der, extension_id,
+ base::Bind(&PlatformKeysService::TaskFinished, base::Unretained(this)),
+ this)));
}
void PlatformKeysService::GenerateRSAKey(const std::string& token_id,
@@ -326,15 +682,12 @@ void PlatformKeysService::SignRSAPKCS1Raw(const std::string& token_id,
void PlatformKeysService::SelectClientCertificates(
const platform_keys::ClientCertificateRequest& request,
+ bool interactive,
const std::string& extension_id,
const SelectCertificatesCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-
- platform_keys::subtle::SelectClientCertificates(
- request,
- base::Bind(&PlatformKeysService::SelectClientCertificatesCallback,
- weak_factory_.GetWeakPtr(), extension_id, callback),
- browser_context_);
+ StartOrQueueTask(make_scoped_ptr(
+ new SelectTask(request, interactive, extension_id, callback, this)));
}
void PlatformKeysService::StartOrQueueTask(scoped_ptr<Task> task) {
@@ -367,9 +720,9 @@ void PlatformKeysService::GetPlatformKeysOfExtension(
void PlatformKeysService::SetPlatformKeysOfExtension(
const std::string& extension_id,
- scoped_ptr<base::ListValue> platform_keys) {
+ const KeyEntries& platform_keys) {
state_store_->SetExtensionValue(extension_id, kStateStorePlatformKeys,
- platform_keys.Pass());
+ KeyEntriesToState(platform_keys));
}
void PlatformKeysService::GeneratedKey(const std::string& extension_id,
@@ -382,7 +735,8 @@ void PlatformKeysService::GeneratedKey(const std::string& extension_id,
}
StartOrQueueTask(make_scoped_ptr(new PermissionUpdateTask(
- true /* new permission value */, public_key_spki_der, extension_id,
+ SignPermission::ONCE, true /* new permission value */,
+ public_key_spki_der, extension_id,
base::Bind(&PlatformKeysService::RegisteredGeneratedKey,
base::Unretained(this), callback, public_key_spki_der),
this)));
@@ -396,35 +750,16 @@ void PlatformKeysService::RegisteredGeneratedKey(
TaskFinished(task);
}
-void PlatformKeysService::SelectClientCertificatesCallback(
- const std::string& extension_id,
- const SelectCertificatesCallback& callback,
- scoped_ptr<net::CertificateList> matches,
- const std::string& error_message) {
- if (permission_check_enabled_)
- matches->clear();
-
- // TODO(pneubeck): Remove all certs that the extension doesn't have access to.
- callback.Run(matches.Pass(), error_message);
-}
void PlatformKeysService::GotPlatformKeysOfExtension(
const std::string& extension_id,
const GetPlatformKeysCallback& callback,
scoped_ptr<base::Value> value) {
- if (!value)
- value.reset(new base::ListValue);
-
- base::ListValue* keys = NULL;
- if (!value->GetAsList(&keys)) {
- LOG(ERROR) << "Found a value of wrong type.";
-
- keys = new base::ListValue;
- value.reset(keys);
- }
+ scoped_ptr<KeyEntries> key_entries(new KeyEntries);
+ if (value)
+ key_entries = KeyEntriesFromState(*value);
- ignore_result(value.release());
- callback.Run(make_scoped_ptr(keys));
+ callback.Run(key_entries.Pass());
}
} // namespace chromeos

Powered by Google App Engine
This is Rietveld 408576698