chromium/chrome/browser/chromeos/platform_keys/extension_key_permissions_service.h

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_EXTENSION_KEY_PERMISSIONS_SERVICE_H_
#define CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_EXTENSION_KEY_PERMISSIONS_SERVICE_H_

#include <stdint.h>

#include <memory>
#include <string>
#include <vector>

#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys.h"
#include "chromeos/crosapi/mojom/keystore_error.mojom.h"
#include "chromeos/crosapi/mojom/keystore_service.mojom.h"
#include "extensions/common/extension_id.h"

namespace extensions {
class StateStore;
}

namespace policy {
class PolicyService;
}

namespace content {
class BrowserContext;
}

namespace chromeos::platform_keys {

// PlatformKeys is a field stored in each extension's state store. It saves
// signing permissions of keys in the context of a (Profile, Extension) pair.
// 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";

using CanUseKeyForSigningCallback = base::OnceCallback<void(bool allowed)>;

using RegisterKeyForCorporateUsageCallback =
    base::OnceCallback<void(bool is_error,
                            crosapi::mojom::KeystoreError error)>;

using SetUserGrantedPermissionCallback =
    base::OnceCallback<void(bool is_error,
                            crosapi::mojom::KeystoreError error)>;

using SetKeyUsedForSigningCallback =
    base::OnceCallback<void(bool is_error,
                            crosapi::mojom::KeystoreError error)>;

// ** ExtensionKeyPermissionsService Responsibility **
// - Managing signing permissions for a (Profile, Extension) pair.
// - Answering signing permissions queries for a (Profile, Extension) pair.
//
// The permission model depends on whether the user account is managed or not.
//
// ** If the user account is not managed **
// The user is under full control of the keys that are generated or imported
// while the device is not managed. For that, a user can grant a specific
// extension the permission to sign arbitrary data with a specific key for an
// unlimited number of times.
//
// ** If the user account is managed **
// The administrator is in charge of granting access to keys that are meant for
// corporate usage.
// The KeyPermissions policy allows the administrator to list exactly the
// extensions that are allowed to use corporate keys. Non-corporate keys are not
// affected.
//
// ** One-off Permission for the Certification Requests **
// Independent of the above, the extension that generates a key using the
// chrome.enterprise.platformKeys API is allowed to sign arbitrary data with the
// private key for a single time in order to create a certification request.
// The assumption is that certification requests usually require a signature of
// data including the public key. So the one-off permission implies that once a
// certificate authority creates the certificate of the generated key, the
// generating extension isn't able to use the key anymore except if explicitly
// permitted by the administrator.
//
// ** IMPORTANT: Synchronization / Possible Race Conditions **
// This class reads from the extensions::StateStore only once on creation and
// caches the result. Further reads are done using the cached result. This can
// lead to race conditions once there can exist more than one instance of the
// service for the same (Profile, Extension) pair. Currently this will not
// happen as the only class owning ExtensionKeyPermissionsServices is
// ExtensionPlatformKeysService, which never owns two
// ExtensionKeyPermissionsServices at the same time.
class ExtensionKeyPermissionsService {
 public:
  // |key_permissions_service| must not be null and outlive this object.
  // Methods of this object refer implicitly to the extension with the id
  // |extension_id|. Don't use this constructor directly. Call
  // |ExtensionKeyPermissionsServiceFactory::GetForBrowserContextAndExtension|
  // instead.
  ExtensionKeyPermissionsService(const std::string& extension_id,
                                 extensions::StateStore* state_store,
                                 base::Value::List state_store_value,
                                 policy::PolicyService* profile_policies,
                                 content::BrowserContext* browser_context);

  ExtensionKeyPermissionsService(const ExtensionKeyPermissionsService&) =
      delete;
  ExtensionKeyPermissionsService& operator=(
      const ExtensionKeyPermissionsService&) = delete;
  ~ExtensionKeyPermissionsService();

  // Returns true if the private key matching |public_key_spki_der| can be
  // used for signing by the extension with id |extension_id_|.
  // |key_locations| must describe locations available to the user the private
  // key is stored on.
  void CanUseKeyForSigning(const std::vector<uint8_t>& public_key_spki_der,
                           CanUseKeyForSigningCallback callback);

  // Must be called when the extension with id |extension_id| used the private
  // key matching |public_key_spki_der| for signing. |key_locations| must
  // describe locations available to the user the private key is stored on.
  // Updates the permissions accordingly.  E.g. if this extension generated
  // the key and no other permission was granted then the permission to sign
  // with this key is removed.
  void SetKeyUsedForSigning(const std::vector<uint8_t>& public_key_spki_der,
                            SetKeyUsedForSigningCallback callback);

  // Registers the private key matching |public_key_spki_der| as being generated
  // by the extension with id |extension_id| and marks it for corporate usage.
  // |key_locations| must describe locations available to the user the private
  // key is stored on.
  void RegisterKeyForCorporateUsage(
      const std::vector<uint8_t>& public_key_spki_der,
      RegisterKeyForCorporateUsageCallback callback);

  // Sets the user granted permission that the extension with id
  // |extension_id| can use the private key matching |public_key_spki_der| for
  // signing. |key_locations| must describe locations available to the user
  // the private key is stored on.
  void SetUserGrantedPermission(const std::vector<uint8_t>& public_key_spki_der,
                                SetUserGrantedPermissionCallback callback);

  // Returns the list of apps and extensions ids allowed to use corporate usage
  // keys by policy in |profile_policies|.
  static std::vector<std::string> GetCorporateKeyUsageAllowedAppIds(
      policy::PolicyService* const profile_policies);

 private:
  struct 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;
  };

  void OnGotExtensionValue(std::optional<base::Value> value);

  // Writes the current |state_store_entries_| to the state store of
  // |extension_id_|.
  void WriteToStateStore();

  // Reads a KeyEntry list from |state| and stores them in
  // |state_store_entries_|.
  void KeyEntriesFromState(const base::Value::List& state);

  // Converts |state_store_entries_| to a base::Value for storing in the state
  // store.
  base::Value::List KeyEntriesToState();

  // Returns an existing entry for |public_key_spki_der_b64| from
  // |state_store_entries_|. If there is no existing entry, creates, adds and
  // returns a new entry.
  // |public_key_spki_der| must be the base64 encoding of the DER of a Subject
  // Public Key Info.
  KeyEntry* GetStateStoreEntry(const std::string& public_key_spki_der_b64);

  // Writes |value| to the state store of the extension.
  void SetPlatformKeysInStateStore(std::optional<base::Value> value);

  bool PolicyAllowsCorporateKeyUsage() const;

  void CanUseKeyForSigningWithFlags(
      CanUseKeyForSigningCallback callback,
      bool sign_unlimited_allowed,
      crosapi::mojom::GetKeyTagsResultPtr key_tags);

  void SetUserGrantedPermissionWithFlag(
      const std::vector<uint8_t>& public_key_spki_der,
      SetUserGrantedPermissionCallback callback,
      bool can_user_grant_permission);

  const extensions::ExtensionId extension_id_;
  raw_ptr<extensions::StateStore, FlakyDanglingUntriaged>
      extensions_state_store_ = nullptr;
  std::vector<KeyEntry> state_store_entries_;
  const raw_ptr<policy::PolicyService> profile_policies_;
  const raw_ptr<crosapi::mojom::KeystoreService> keystore_service_ = nullptr;
  base::WeakPtrFactory<ExtensionKeyPermissionsService> weak_factory_{this};
};

}  // namespace chromeos::platform_keys

#endif  // CHROME_BROWSER_CHROMEOS_PLATFORM_KEYS_EXTENSION_KEY_PERMISSIONS_SERVICE_H_