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