tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Windows11LimitedAccessFeatures.cpp (12838B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 /**
      7 * This file exists so that LaunchModernSettingsDialogDefaultApps can be called
      8 * without linking to libxul.
      9 */
     10 #include "Windows11LimitedAccessFeatures.h"
     11 
     12 #include "mozilla/Logging.h"
     13 
     14 static mozilla::LazyLogModule sLog("Windows11LimitedAccessFeatures");
     15 
     16 #define LAF_LOG(level, msg, ...) MOZ_LOG(sLog, level, (msg, ##__VA_ARGS__))
     17 
     18 // MINGW32 is not supported for these features
     19 // Fall back function defined in the #else
     20 #ifndef __MINGW32__
     21 
     22 #  include "mozilla/ErrorResult.h"
     23 #  include "nsString.h"
     24 #  include "nsCOMPtr.h"
     25 #  include "nsComponentManagerUtils.h"
     26 #  include "nsIWindowsRegKey.h"
     27 #  include "nsWindowsHelpers.h"
     28 #  include "nsICryptoHash.h"
     29 
     30 #  include "mozilla/Atomics.h"
     31 #  include "mozilla/Base64.h"
     32 #  include "mozilla/WinHeaderOnlyUtils.h"
     33 #  include "WinUtils.h"
     34 
     35 #  include <wrl.h>
     36 #  include <inspectable.h>
     37 #  include <roapi.h>
     38 #  include <windows.services.store.h>
     39 #  include <windows.foundation.h>
     40 
     41 using namespace Microsoft::WRL;
     42 using namespace Microsoft::WRL::Wrappers;
     43 using namespace ABI::Windows;
     44 using namespace ABI::Windows::Foundation;
     45 using namespace ABI::Windows::ApplicationModel;
     46 
     47 using namespace mozilla;
     48 
     49 /**
     50 * To unlock features, we need:
     51 * a feature identifier
     52 * a token,
     53 * an attestation string
     54 * a token
     55 *
     56 * The token is generated by Microsoft and must
     57 * match the publisher id Microsoft thinks we have, for a particular
     58 * feature.
     59 *
     60 * To get a token, find the right microsoft email address by doing
     61 * a search on the web for the feature you want unlocked and reach
     62 * out to the right people at Microsoft.
     63 *
     64 * The token is generated from Microsoft.
     65 * The jumbled code in the attestation string is a publisher id and
     66 * must match the code in the resources / .rc file for the identity,
     67 * looking like this for non-MSIX builds:
     68 *
     69 * Identity LimitedAccessFeature {{ L"MozillaFirefox_pcsmm0jrprpb2" }}
     70 *
     71 * Broken down:
     72 * Identity LimitedAccessFeature {{ L"PRODUCTNAME_PUBLISHERID" }}
     73 *
     74 * That is injected into our build in create_rc.py and is necessary
     75 * to unlock the taskbar pinning feature / APIs from an unpackaged
     76 * build.
     77 *
     78 * In the above, the token is generated from the publisher id (pcsmm0jrprpb2)
     79 * and the product name (MozillaFirefox)
     80 *
     81 * All tokens listed here were provided to us by Microsoft.
     82 *
     83 * Below and in create_rc.py, we used this set:
     84 *
     85 * Token: "kRFiWpEK5uS6PMJZKmR7MQ=="
     86 * Product Name: "MozillaFirefox"
     87 * Publisher ID: "pcsmm0jrprpb2"
     88 *
     89 * Microsoft also provided these other tokens, which will will
     90 * work if accompanied by the matching changes to create_rc.py:
     91 
     92 * -----
     93 * Token: "RGEhsYgKhmPLKyzkEHnMhQ=="
     94 * Product Name: "FirefoxBeta"
     95 * Publisher ID: "pcsmm0jrprpb2"
     96 *
     97 * -----
     98 *
     99 * Token: "qbVzns/9kT+t15YbIwT4Jw=="
    100 * Product Name: "FirefoxNightly"
    101 * Publisher ID: "pcsmm0jrprpb2"
    102 *
    103 * To use those instead, you have to ensure that the LimitedAccessFeature
    104 * generated in create_rc.py has the product name and publisher id
    105 * matching the token used in this file.
    106 *
    107 * For non-packaged (non-MSIX) builds, any of the above sets will work.
    108 * Just make sure the right (ProductName_PublisherID) value is in the
    109 * generated resource data for the executable, and the matching
    110 * (Token) and attestation string
    111 *
    112 * To get MSIX/packaged builds to work, the product name and publisher in
    113 * the final manifest (searchfox.org/mozilla-central/search?q=APPX_PUBLISHER)
    114 * should match the token in this file. For that case, the identity value
    115 * in the resources does not matter.
    116 *
    117 * See here for Microsoft examples:
    118 https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/TaskbarManager/CppUnpackagedDesktopTaskbarPin
    119 */
    120 
    121 /**
    122 * Unlocks a Windows Limited Access Feature (LAF) by generating a token and
    123 * attestation.
    124 *
    125 * This function first retrieves the LAF key from the registry using
    126 * the LAF identifier and then combines the lafId, lafKey, and PFN
    127 * into a token.
    128 *
    129 * Applying Base64(SHA256Encode("<lafId>!<lafKey>!<PFN>")[0..16]) yields the
    130 * complete LAF token for unlocking.
    131 *
    132 * Taking the last 13 characters of the PFN yields the publisher identifier
    133 * which is used in the following boilerplate:
    134 * "<PFN[-13]> has registered their use of <lafId> with Microsoft and
    135 * agrees to the terms of use."
    136 *
    137 * @return {LimitedAccessFeatureInfo} containing the generated
    138 *   token and attestation upon success. Contains empty strings for
    139 *   these fields upon failure.
    140 */
    141 static mozilla::Result<LimitedAccessFeatureInfo, nsresult>
    142 GenerateLimitedAccessFeatureInfo(const nsCString& debugName,
    143                                 const nsString& lafId) {
    144  nsresult rv;
    145  // Read registry key for a given Limited Access Feature with ID lafId.
    146  nsAutoString keyData;
    147  nsCOMPtr<nsIWindowsRegKey> regKey =
    148      do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
    149  NS_ENSURE_SUCCESS(rv, Err(rv));
    150  const nsAutoString regPath =
    151      u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModel\\LimitedAccessFeatures\\"_ns +
    152      lafId;
    153  rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, regPath,
    154                    nsIWindowsRegKey::ACCESS_READ);
    155  NS_ENSURE_SUCCESS(rv, Err(rv));
    156  rv = regKey->ReadStringValue(u""_ns, keyData);
    157  NS_ENSURE_SUCCESS(rv, Err(rv));
    158 
    159  // Get Package Family Name (PFN) and assemble a string to hash.
    160  // First convert this string to SHA256, take the first 16 bytes
    161  // only, and then read as base 64.
    162  nsAutoCString hashStringResult;
    163  nsAutoString encodedToken;
    164  // The non-MSIX family name must match whatever value is in create_rc.py
    165  // Currently this is MozillaFirefox_pcsmm0jrprpb2
    166  nsAutoString familyName;
    167  if (widget::WinUtils::HasPackageIdentity()) {
    168    familyName = nsDependentString(mozilla::GetPackageFamilyName().get());
    169  } else {
    170    familyName = u"MozillaFirefox_pcsmm0jrprpb2"_ns;
    171  }
    172  const nsAutoCString hashString =
    173      NS_ConvertUTF16toUTF8(lafId + u"!"_ns + keyData + u"!"_ns + familyName);
    174 
    175  nsCOMPtr<nsICryptoHash> cryptoHash =
    176      do_CreateInstance("@mozilla.org/security/hash;1", &rv);
    177  NS_ENSURE_SUCCESS(rv, Err(rv));
    178  rv = cryptoHash->Init(nsICryptoHash::SHA256);
    179  NS_ENSURE_SUCCESS(rv, Err(rv));
    180  rv = cryptoHash->Update(reinterpret_cast<const uint8_t*>(hashString.get()),
    181                          hashString.Length());
    182  NS_ENSURE_SUCCESS(rv, Err(rv));
    183  rv = cryptoHash->Finish(false, hashStringResult);
    184  NS_ENSURE_SUCCESS(rv, Err(rv));
    185 
    186  // Keep only first 16 bytes and encode
    187  hashStringResult.Truncate(hashStringResult.Length() - 16);
    188  rv = Base64Encode(hashStringResult, encodedToken);
    189  NS_ENSURE_SUCCESS(rv, Err(rv));
    190 
    191  // The PFN contains a package ID in the last 13 characters.
    192  // This ID is based on the value in the publisher field of the
    193  // AppManifest. This ID is used to assemble the attestation.
    194  familyName.Cut(0, familyName.Length() - 13);
    195  nsAutoString attestation =
    196      familyName + u" has registered their use of "_ns + lafId +
    197      u" with Microsoft and agrees to the terms of use."_ns;
    198  LimitedAccessFeatureInfo result = {debugName, lafId, encodedToken,
    199                                     attestation};
    200  return result;
    201 }
    202 
    203 /**
    204 Implementation of the Win11LimitedAccessFeaturesInterface.
    205 */
    206 class Win11LimitedAccessFeatures : public Win11LimitedAccessFeaturesInterface {
    207 public:
    208  using AtomicState = Atomic<int, SequentiallyConsistent>;
    209 
    210  Result<bool, HRESULT> Unlock(Win11LimitedAccessFeatureType feature) override;
    211 
    212 private:
    213  AtomicState& GetState(Win11LimitedAccessFeatureType feature);
    214  Result<bool, HRESULT> UnlockImplementation(
    215      const LimitedAccessFeatureInfo& lafInfo);
    216 
    217  /**
    218   * Store the state as an atomic so that it can be safely accessed from
    219   * different threads.
    220   */
    221  static AtomicState mTaskbarState;
    222  static AtomicState mDefaultState;
    223 
    224  enum State {
    225    Uninitialized,
    226    Locked,
    227    Unlocked,
    228  };
    229 };
    230 
    231 Win11LimitedAccessFeatures::AtomicState
    232    Win11LimitedAccessFeatures::mTaskbarState(
    233        Win11LimitedAccessFeatures::Uninitialized);
    234 Win11LimitedAccessFeatures::AtomicState
    235    Win11LimitedAccessFeatures::mDefaultState(
    236        Win11LimitedAccessFeatures::Uninitialized);
    237 
    238 RefPtr<Win11LimitedAccessFeaturesInterface>
    239 CreateWin11LimitedAccessFeaturesInterface() {
    240  RefPtr<Win11LimitedAccessFeaturesInterface> result(
    241      new Win11LimitedAccessFeatures());
    242  return result;
    243 }
    244 
    245 Result<bool, HRESULT> Win11LimitedAccessFeatures::Unlock(
    246    Win11LimitedAccessFeatureType feature) {
    247  AtomicState& atomicState = GetState(feature);
    248 
    249  // Win11LimitedAccessFeatureType::Taskbar
    250  auto taskbarLafInfo = GenerateLimitedAccessFeatureInfo(
    251      "Win11LimitedAccessFeatureType::Taskbar"_ns,
    252      u"com.microsoft.windows.taskbar.pin"_ns);
    253  if (taskbarLafInfo.isErr()) {
    254    LAF_LOG(LogLevel::Debug, "Unlocking taskbar failed with error %d",
    255            NS_ERROR_GET_CODE(taskbarLafInfo.unwrapErr()));
    256    return Err(E_FAIL);
    257  }
    258 
    259  LimitedAccessFeatureInfo limitedAccessFeatureInfo[] = {
    260      taskbarLafInfo.unwrap()};
    261  const auto& lafInfo = limitedAccessFeatureInfo[static_cast<int>(feature)];
    262 
    263  LAF_LOG(LogLevel::Debug,
    264          "Limited Access Feature Info for %s. Feature %ls, %ls, %ls",
    265          lafInfo.debugName.get(), lafInfo.token.getW(), lafInfo.token.getW(),
    266          lafInfo.attestation.getW());
    267 
    268  int state = atomicState;
    269  if (state != Uninitialized) {
    270    LAF_LOG(LogLevel::Debug, "%s already initialized! State = %s",
    271            lafInfo.debugName.get(), (state == Unlocked) ? "true" : "false");
    272    return (state == Unlocked);
    273  }
    274 
    275  // If multiple threads read the state at the same time, and it's unitialized,
    276  // both threads will unlock the feature. This situation is unlikely, but even
    277  // if it happens, it's not a problem.
    278 
    279  auto result = UnlockImplementation(lafInfo);
    280 
    281  int newState = Locked;
    282  if (!result.isErr() && result.unwrap()) {
    283    newState = Unlocked;
    284  }
    285 
    286  atomicState = newState;
    287 
    288  return result;
    289 }
    290 
    291 Win11LimitedAccessFeatures::AtomicState& Win11LimitedAccessFeatures::GetState(
    292    Win11LimitedAccessFeatureType feature) {
    293  switch (feature) {
    294    case Win11LimitedAccessFeatureType::Taskbar:
    295      return mTaskbarState;
    296 
    297    default:
    298      LAF_LOG(LogLevel::Debug, "Missing feature type for %d",
    299              static_cast<int>(feature));
    300      MOZ_ASSERT(false,
    301                 "Unhandled feature type! Add a new atomic state variable, add "
    302                 "that entry to the switch statement above, and add the proper "
    303                 "entries for the feature and the token.");
    304      return mDefaultState;
    305  }
    306 }
    307 
    308 Result<bool, HRESULT> Win11LimitedAccessFeatures::UnlockImplementation(
    309    const LimitedAccessFeatureInfo& lafInfo) {
    310  ComPtr<ILimitedAccessFeaturesStatics> limitedAccessFeatures;
    311  ComPtr<ILimitedAccessFeatureRequestResult> limitedAccessFeaturesResult;
    312 
    313  HRESULT hr = RoGetActivationFactory(
    314      HStringReference(
    315          RuntimeClass_Windows_ApplicationModel_LimitedAccessFeatures)
    316          .Get(),
    317      IID_ILimitedAccessFeaturesStatics, &limitedAccessFeatures);
    318 
    319  if (!SUCCEEDED(hr)) {
    320    LAF_LOG(LogLevel::Debug, "%s activation error. HRESULT = 0x%lx",
    321            lafInfo.debugName.get(), hr);
    322    return Err(hr);
    323  }
    324 
    325  hr = limitedAccessFeatures->TryUnlockFeature(
    326      HStringReference(lafInfo.feature.get()).Get(),
    327      HStringReference(lafInfo.token.get()).Get(),
    328      HStringReference(lafInfo.attestation.get()).Get(),
    329      &limitedAccessFeaturesResult);
    330  if (!SUCCEEDED(hr)) {
    331    LAF_LOG(LogLevel::Debug, "%s unlock error. HRESULT = 0x%lx",
    332            lafInfo.debugName.get(), hr);
    333    return Err(hr);
    334  }
    335 
    336  LimitedAccessFeatureStatus status;
    337  hr = limitedAccessFeaturesResult->get_Status(&status);
    338  if (!SUCCEEDED(hr)) {
    339    LAF_LOG(LogLevel::Debug, "%s get status error. HRESULT = 0x%lx",
    340            lafInfo.debugName.get(), hr);
    341    return Err(hr);
    342  }
    343 
    344  int state = Unlocked;
    345  if ((status != LimitedAccessFeatureStatus_Available) &&
    346      (status != LimitedAccessFeatureStatus_AvailableWithoutToken)) {
    347    LAF_LOG(LogLevel::Debug, "%s not available. HRESULT = 0x%lx",
    348            lafInfo.debugName.get(), hr);
    349    state = Locked;
    350  }
    351 
    352  return (state == Unlocked);
    353 }
    354 
    355 #else  // MINGW32 implementation
    356 
    357 RefPtr<Win11LimitedAccessFeaturesInterface>
    358 CreateWin11LimitedAccessFeaturesInterface() {
    359  RefPtr<Win11LimitedAccessFeaturesInterface> result;
    360  return result;
    361 }
    362 
    363 static mozilla::Result<LimitedAccessFeatureInfo, nsresult>
    364 GenerateLimitedAccessFeatureInfo(const nsCString& debugName,
    365                                 const nsString& lafId) {
    366  return mozilla::Err(NS_ERROR_NOT_IMPLEMENTED);
    367 }
    368 
    369 #endif