Verified Commit aab1a725 authored by Nicholas Rishel's avatar Nicholas Rishel Committed by ma1
Browse files

Bug 2007035 - Migrate Windows Limited Access Feature code to Rust. a=RyanVM

parent 06edc5db
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -2629,6 +2629,7 @@ dependencies = [
 "rure",
 "rusqlite 0.33.0",
 "rust_minidump_writer_linux",
 "shell_windows",
 "signature_cache",
 "static_prefs",
 "storage",
@@ -6152,6 +6153,19 @@ dependencies = [
 "keccak",
]

[[package]]
name = "shell_windows"
version = "0.1.0"
dependencies = [
 "base64 0.22.1",
 "log",
 "nserror",
 "nsstring",
 "sha2",
 "windows",
 "xpcom",
]

[[package]]
name = "shlex"
version = "1.3.0"
+6 −0
Original line number Diff line number Diff line
@@ -46,4 +46,10 @@ elif buildconfig.substs['OS_ARCH'] == 'WINNT':
            'type': 'nsIEHistoryEnumerator',
            'headers': ['/browser/components/migration/nsIEHistoryEnumerator.h'],
        },
        {
            'cid': '{115f4862-f302-42a2-a58c-d09f636ab601}',
            'contract_ids': ['@mozilla.org/limited-access-feature-service;1'],
            'headers': ['/browser/components/shell/rust/shell_windows/src/LimitedAccessFeatureService.h'],
            'legacy_constructor': 'new_limited_access_feature_service',
        },
    ]
+0 −370
Original line number Diff line number Diff line
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file exists so that LaunchModernSettingsDialogDefaultApps can be called
 * without linking to libxul.
 */
#include "Windows11LimitedAccessFeatures.h"

#include "mozilla/Logging.h"

static mozilla::LazyLogModule sLog("Windows11LimitedAccessFeatures");

#define LAF_LOG(level, msg, ...) MOZ_LOG(sLog, level, (msg, ##__VA_ARGS__))

// MINGW32 is not supported for these features
// Fall back function defined in the #else
#ifndef __MINGW32__

#  include "mozilla/ErrorResult.h"
#  include "nsString.h"
#  include "nsCOMPtr.h"
#  include "nsComponentManagerUtils.h"
#  include "nsIWindowsRegKey.h"
#  include "nsWindowsHelpers.h"
#  include "nsICryptoHash.h"

#  include "mozilla/Atomics.h"
#  include "mozilla/Base64.h"
#  include "mozilla/Char16.h"
#  include "mozilla/WinHeaderOnlyUtils.h"
#  include "WinUtils.h"

#  include <wrl.h>
#  include <inspectable.h>
#  include <roapi.h>
#  include <windows.services.store.h>
#  include <windows.foundation.h>

using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::ApplicationModel;

using namespace mozilla;

/**
 * To unlock features, we need:
 * a feature identifier
 * a token,
 * an attestation string
 * a token
 *
 * The token is generated by Microsoft and must
 * match the publisher id Microsoft thinks we have, for a particular
 * feature.
 *
 * To get a token, find the right microsoft email address by doing
 * a search on the web for the feature you want unlocked and reach
 * out to the right people at Microsoft.
 *
 * The token is generated from Microsoft.
 * The jumbled code in the attestation string is a publisher id and
 * must match the code in the resources / .rc file for the identity,
 * looking like this for non-MSIX builds:
 *
 * Identity LimitedAccessFeature {{ L"MozillaFirefox_pcsmm0jrprpb2" }}
 *
 * Broken down:
 * Identity LimitedAccessFeature {{ L"PRODUCTNAME_PUBLISHERID" }}
 *
 * That is injected into our build in create_rc.py and is necessary
 * to unlock the taskbar pinning feature / APIs from an unpackaged
 * build.
 *
 * In the above, the token is generated from the publisher id (pcsmm0jrprpb2)
 * and the product name (MozillaFirefox)
 *
 * All tokens listed here were provided to us by Microsoft.
 *
 * Below and in create_rc.py, we used this set:
 *
 * Token: "kRFiWpEK5uS6PMJZKmR7MQ=="
 * Product Name: "MozillaFirefox"
 * Publisher ID: "pcsmm0jrprpb2"
 *
 * Microsoft also provided these other tokens, which will will
 * work if accompanied by the matching changes to create_rc.py:

 * -----
 * Token: "RGEhsYgKhmPLKyzkEHnMhQ=="
 * Product Name: "FirefoxBeta"
 * Publisher ID: "pcsmm0jrprpb2"
 *
 * -----
 *
 * Token: "qbVzns/9kT+t15YbIwT4Jw=="
 * Product Name: "FirefoxNightly"
 * Publisher ID: "pcsmm0jrprpb2"
 *
 * To use those instead, you have to ensure that the LimitedAccessFeature
 * generated in create_rc.py has the product name and publisher id
 * matching the token used in this file.
 *
 * For non-packaged (non-MSIX) builds, any of the above sets will work.
 * Just make sure the right (ProductName_PublisherID) value is in the
 * generated resource data for the executable, and the matching
* (Token) and attestation string
 *
 * To get MSIX/packaged builds to work, the product name and publisher in
 * the final manifest (searchfox.org/mozilla-central/search?q=APPX_PUBLISHER)
 * should match the token in this file. For that case, the identity value
 * in the resources does not matter.
 *
 * See here for Microsoft examples:
https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/TaskbarManager/CppUnpackagedDesktopTaskbarPin
 */

/**
 * Unlocks a Windows Limited Access Feature (LAF) by generating a token and
 * attestation.
 *
 * This function first retrieves the LAF key from the registry using
 * the LAF identifier and then combines the lafId, lafKey, and PFN
 * into a token.
 *
 * Applying Base64(SHA256Encode("<lafId>!<lafKey>!<PFN>")[0..16]) yields the
 * complete LAF token for unlocking.
 *
 * Taking the last 13 characters of the PFN yields the publisher identifier
 * which is used in the following boilerplate:
 * "<PFN[-13]> has registered their use of <lafId> with Microsoft and
 * agrees to the terms of use."
 *
 * @return {LimitedAccessFeatureInfo} containing the generated
 *   token and attestation upon success. Contains empty strings for
 *   these fields upon failure.
 */
static mozilla::Result<LimitedAccessFeatureInfo, nsresult>
GenerateLimitedAccessFeatureInfo(const nsCString& debugName,
                                 const nsString& lafId) {
  nsresult rv;
  // Read registry key for a given Limited Access Feature with ID lafId.
  nsAutoString keyData;
  nsCOMPtr<nsIWindowsRegKey> regKey =
      do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
  NS_ENSURE_SUCCESS(rv, Err(rv));
  const nsAutoString regPath =
      u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModel\\LimitedAccessFeatures\\"_ns +
      lafId;
  rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, regPath,
                    nsIWindowsRegKey::ACCESS_READ);
  NS_ENSURE_SUCCESS(rv, Err(rv));
  rv = regKey->ReadStringValue(u""_ns, keyData);
  NS_ENSURE_SUCCESS(rv, Err(rv));

  // Get Package Family Name (PFN) and assemble a string to hash.
  // First convert this string to SHA256, take the first 16 bytes
  // only, and then read as base 64.
  nsAutoCString hashStringResult;
  nsAutoString encodedToken;
  // The non-MSIX family name must match whatever value is in create_rc.py
  // Currently this is MozillaFirefox_pcsmm0jrprpb2
  nsAutoString familyName;
  if (widget::WinUtils::HasPackageIdentity()) {
    familyName = nsDependentString(mozilla::GetPackageFamilyName().get());
  } else {
    familyName = u"MozillaFirefox_pcsmm0jrprpb2"_ns;
  }
  const nsAutoCString hashString =
      NS_ConvertUTF16toUTF8(lafId + u"!"_ns + keyData + u"!"_ns + familyName);

  nsCOMPtr<nsICryptoHash> cryptoHash =
      do_CreateInstance("@mozilla.org/security/hash;1", &rv);
  NS_ENSURE_SUCCESS(rv, Err(rv));
  rv = cryptoHash->Init(nsICryptoHash::SHA256);
  NS_ENSURE_SUCCESS(rv, Err(rv));
  rv = cryptoHash->Update(reinterpret_cast<const uint8_t*>(hashString.get()),
                          hashString.Length());
  NS_ENSURE_SUCCESS(rv, Err(rv));
  rv = cryptoHash->Finish(false, hashStringResult);
  NS_ENSURE_SUCCESS(rv, Err(rv));

  // Keep only first 16 bytes and encode
  hashStringResult.Truncate(hashStringResult.Length() - 16);
  rv = Base64Encode(hashStringResult, encodedToken);
  NS_ENSURE_SUCCESS(rv, Err(rv));

  // The PFN contains a package ID in the last 13 characters.
  // This ID is based on the value in the publisher field of the
  // AppManifest. This ID is used to assemble the attestation.
  familyName.Cut(0, familyName.Length() - 13);
  nsAutoString attestation =
      familyName + u" has registered their use of "_ns + lafId +
      u" with Microsoft and agrees to the terms of use."_ns;
  LimitedAccessFeatureInfo result = {debugName, lafId, encodedToken,
                                     attestation};
  return result;
}

/**
 Implementation of the Win11LimitedAccessFeaturesInterface.
 */
class Win11LimitedAccessFeatures : public Win11LimitedAccessFeaturesInterface {
 public:
  using AtomicState = Atomic<int, SequentiallyConsistent>;

  Result<bool, HRESULT> Unlock(Win11LimitedAccessFeatureType feature) override;

 private:
  AtomicState& GetState(Win11LimitedAccessFeatureType feature);
  Result<bool, HRESULT> UnlockImplementation(
      const LimitedAccessFeatureInfo& lafInfo);

  /**
   * Store the state as an atomic so that it can be safely accessed from
   * different threads.
   */
  static AtomicState mTaskbarState;
  static AtomicState mDefaultState;

  enum State {
    Uninitialized,
    Locked,
    Unlocked,
  };
};

Win11LimitedAccessFeatures::AtomicState
    Win11LimitedAccessFeatures::mTaskbarState(
        Win11LimitedAccessFeatures::Uninitialized);
Win11LimitedAccessFeatures::AtomicState
    Win11LimitedAccessFeatures::mDefaultState(
        Win11LimitedAccessFeatures::Uninitialized);

RefPtr<Win11LimitedAccessFeaturesInterface>
CreateWin11LimitedAccessFeaturesInterface() {
  RefPtr<Win11LimitedAccessFeaturesInterface> result(
      new Win11LimitedAccessFeatures());
  return result;
}

Result<bool, HRESULT> Win11LimitedAccessFeatures::Unlock(
    Win11LimitedAccessFeatureType feature) {
  AtomicState& atomicState = GetState(feature);

  // Win11LimitedAccessFeatureType::Taskbar
  auto taskbarLafInfo = GenerateLimitedAccessFeatureInfo(
      "Win11LimitedAccessFeatureType::Taskbar"_ns,
      u"com.microsoft.windows.taskbar.pin"_ns);
  if (taskbarLafInfo.isErr()) {
    LAF_LOG(LogLevel::Debug, "Unlocking taskbar failed with error %d",
            NS_ERROR_GET_CODE(taskbarLafInfo.unwrapErr()));
    return Err(E_FAIL);
  }

  LimitedAccessFeatureInfo limitedAccessFeatureInfo[] = {
      taskbarLafInfo.unwrap()};
  const auto& lafInfo = limitedAccessFeatureInfo[static_cast<int>(feature)];

  LAF_LOG(LogLevel::Debug,
          "Limited Access Feature Info for %s. Feature %ls, %ls, %ls",
          lafInfo.debugName.get(), lafInfo.token.getW(), lafInfo.token.getW(),
          lafInfo.attestation.getW());

  int state = atomicState;
  if (state != Uninitialized) {
    LAF_LOG(LogLevel::Debug, "%s already initialized! State = %s",
            lafInfo.debugName.get(), (state == Unlocked) ? "true" : "false");
    return (state == Unlocked);
  }

  // If multiple threads read the state at the same time, and it's unitialized,
  // both threads will unlock the feature. This situation is unlikely, but even
  // if it happens, it's not a problem.

  auto result = UnlockImplementation(lafInfo);

  int newState = Locked;
  if (!result.isErr() && result.unwrap()) {
    newState = Unlocked;
  }

  atomicState = newState;

  return result;
}

Win11LimitedAccessFeatures::AtomicState& Win11LimitedAccessFeatures::GetState(
    Win11LimitedAccessFeatureType feature) {
  switch (feature) {
    case Win11LimitedAccessFeatureType::Taskbar:
      return mTaskbarState;

    default:
      LAF_LOG(LogLevel::Debug, "Missing feature type for %d",
              static_cast<int>(feature));
      MOZ_ASSERT(false,
                 "Unhandled feature type! Add a new atomic state variable, add "
                 "that entry to the switch statement above, and add the proper "
                 "entries for the feature and the token.");
      return mDefaultState;
  }
}

Result<bool, HRESULT> Win11LimitedAccessFeatures::UnlockImplementation(
    const LimitedAccessFeatureInfo& lafInfo) {
  ComPtr<ILimitedAccessFeaturesStatics> limitedAccessFeatures;
  ComPtr<ILimitedAccessFeatureRequestResult> limitedAccessFeaturesResult;

  HRESULT hr = RoGetActivationFactory(
      HStringReference(
          RuntimeClass_Windows_ApplicationModel_LimitedAccessFeatures)
          .Get(),
      IID_ILimitedAccessFeaturesStatics, &limitedAccessFeatures);

  if (!SUCCEEDED(hr)) {
    LAF_LOG(LogLevel::Debug, "%s activation error. HRESULT = 0x%lx",
            lafInfo.debugName.get(), hr);
    return Err(hr);
  }

  hr = limitedAccessFeatures->TryUnlockFeature(
      HStringReference(lafInfo.feature.get()).Get(),
      HStringReference(lafInfo.token.get()).Get(),
      HStringReference(lafInfo.attestation.get()).Get(),
      &limitedAccessFeaturesResult);
  if (!SUCCEEDED(hr)) {
    LAF_LOG(LogLevel::Debug, "%s unlock error. HRESULT = 0x%lx",
            lafInfo.debugName.get(), hr);
    return Err(hr);
  }

  LimitedAccessFeatureStatus status;
  hr = limitedAccessFeaturesResult->get_Status(&status);
  if (!SUCCEEDED(hr)) {
    LAF_LOG(LogLevel::Debug, "%s get status error. HRESULT = 0x%lx",
            lafInfo.debugName.get(), hr);
    return Err(hr);
  }

  int state = Unlocked;
  if ((status != LimitedAccessFeatureStatus_Available) &&
      (status != LimitedAccessFeatureStatus_AvailableWithoutToken)) {
    LAF_LOG(LogLevel::Debug, "%s not available. HRESULT = 0x%lx",
            lafInfo.debugName.get(), hr);
    state = Locked;
  }

  return (state == Unlocked);
}

#else  // MINGW32 implementation

RefPtr<Win11LimitedAccessFeaturesInterface>
CreateWin11LimitedAccessFeaturesInterface() {
  RefPtr<Win11LimitedAccessFeaturesInterface> result;
  return result;
}

static mozilla::Result<LimitedAccessFeatureInfo, nsresult>
GenerateLimitedAccessFeatureInfo(const nsCString& debugName,
                                 const nsString& lafId) {
  return mozilla::Err(NS_ERROR_NOT_IMPLEMENTED);
}

#endif
+0 −65
Original line number Diff line number Diff line
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef SHELL_WINDOWS11LIMITEDACCESSFEATURES_H__
#define SHELL_WINDOWS11LIMITEDACCESSFEATURES_H__

#include "nsISupportsImpl.h"
#include "nsString.h"
#include "mozilla/Result.h"
#include "mozilla/ResultVariant.h"
#include <winerror.h>
#include <windows.h>  // for HRESULT
#include "mozilla/DefineEnum.h"
#include <winerror.h>

MOZ_DEFINE_ENUM_CLASS(Win11LimitedAccessFeatureType, (Taskbar));

/**
 * Class to manage unlocking limited access features on Windows 11.
 * Unless stubbing for testing purposes, create objects of this
 * class with CreateWin11LimitedAccessFeaturesInterface.
 *
 * Windows 11 requires certain features to be unlocked in order to work
 * (for instance, the Win11 Taskbar pinning APIs). Call Unlock()
 * to unlock them. Generally, results will be cached in atomic variables
 * and future calls to Unlock will be as long as it takes
 * to fetch an atomic variable.
 */
class Win11LimitedAccessFeaturesInterface {
 public:
  /**
   * Unlocks the limited access features, if possible.
   *
   * Returns an error code on error, true on successful unlock,
   * false on unlock failed (but with no error).
   */
  virtual mozilla::Result<bool, HRESULT> Unlock(
      Win11LimitedAccessFeatureType feature) = 0;

  /**
   * Reference counting and cycle collection.
   */
  NS_INLINE_DECL_REFCOUNTING(Win11LimitedAccessFeaturesInterface)

 protected:
  virtual ~Win11LimitedAccessFeaturesInterface() {}
};

struct LimitedAccessFeatureInfo {
  const nsCString debugName;
  const nsString feature;
  const nsString token;
  const nsString attestation;
};

RefPtr<Win11LimitedAccessFeaturesInterface>
CreateWin11LimitedAccessFeaturesInterface();

mozilla::Result<LimitedAccessFeatureInfo, nsresult>
GenerateLimitedAccessFeatureInfo(const nsCString& debugName,
                                 const nsString& lafId);

#endif  // SHELL_WINDOWS11LIMITEDACCESSFEATURES_H__
+29 −16
Original line number Diff line number Diff line
@@ -4,8 +4,8 @@
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "Windows11TaskbarPinning.h"
#include "Windows11LimitedAccessFeatures.h"

#include "nsILimitedAccessFeature.h"
#include "nsWindowsHelpers.h"
#include "MainThreadUtils.h"
#include "nsThreadUtils.h"
@@ -70,24 +70,39 @@ using namespace ABI::Windows::UI::Shell;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::ApplicationModel;

static Win11PinToTaskBarResult UnlockLimitedAccessFeature(
    Win11LimitedAccessFeatureType featureType) {
  RefPtr<Win11LimitedAccessFeaturesInterface> limitedAccessFeatures =
      CreateWin11LimitedAccessFeaturesInterface();
  auto result = limitedAccessFeatures->Unlock(featureType);
  if (result.isErr()) {
    auto hr = result.unwrapErr();
    TASKBAR_PINNING_LOG(LogLevel::Debug,
                        "Taskbar unlock: Error. HRESULT = 0x%lx", hr);
    return {hr, Win11PinToTaskBarResultStatus::NotSupported};
static Win11PinToTaskBarResult UnlockTaskbarPinFeature() {
  nsCOMPtr<nsILimitedAccessFeatureService> lafService =
      do_GetService("@mozilla.org/limited-access-feature-service;1");

  nsAutoCString pinFeatureId;
  nsresult rv = lafService->GetTaskbarPinFeatureId(pinFeatureId);

  if (NS_WARN_IF(NS_FAILED(rv))) {
    return {E_FAIL, Win11PinToTaskBarResultStatus::NotSupported};
  }

  if (result.unwrap() == false) {
  nsCOMPtr<nsILimitedAccessFeature> feature;
  rv = lafService->GenerateLimitedAccessFeature(pinFeatureId,
                                                getter_AddRefs(feature));

  if (NS_WARN_IF(NS_FAILED(rv))) {
    return {E_FAIL, Win11PinToTaskBarResultStatus::NotSupported};
  }

  bool unlocked = false;
  rv = feature->Unlock(&unlocked);

  if (NS_WARN_IF(NS_FAILED(rv))) {
    return {E_FAIL, Win11PinToTaskBarResultStatus::NotSupported};
  }

  if (!unlocked) {
    TASKBAR_PINNING_LOG(
        LogLevel::Debug,
        "Taskbar unlock: failed. Not supported on this version of Windows.");
    return {S_OK, Win11PinToTaskBarResultStatus::NotSupported};
  }

  return {S_OK, Win11PinToTaskBarResultStatus::Success};
}

@@ -184,8 +199,7 @@ Win11PinToTaskBarResult PinCurrentAppToTaskbarWin11(
                        "thread only. It blocks, waiting on things to execute "
                        "asynchronously on the main thread.");

  Win11PinToTaskBarResult unlockStatus =
      UnlockLimitedAccessFeature(Win11LimitedAccessFeatureType::Taskbar);
  Win11PinToTaskBarResult unlockStatus = UnlockTaskbarPinFeature();
  if (unlockStatus.result != Win11PinToTaskBarResultStatus::Success) {
    // Limited Access Feature no longer necessary for Windows 11 26200 Build
    // 7840, and possibly other channels.
@@ -414,8 +428,7 @@ Win11PinToTaskBarResult IsCurrentAppPinnedToTaskbarWin11(bool aCheckOnly) {
      "thread only. It blocks, waiting on things to execute "
      "asynchronously on the main thread.");

  Win11PinToTaskBarResult unlockStatus =
      UnlockLimitedAccessFeature(Win11LimitedAccessFeatureType::Taskbar);
  Win11PinToTaskBarResult unlockStatus = UnlockTaskbarPinFeature();
  if (unlockStatus.result != Win11PinToTaskBarResultStatus::Success) {
    // Limited Access Feature no longer necessary for Windows 11 26200 Build
    // 7840, and possibly other channels.
Loading