Bug 1794159 - Part 2: Add LAF unlocking and check for pinning status using new APIs r=nalexander,nrishel

Differential Revision: https://phabricator.services.mozilla.com/D213331
This commit is contained in:
Nipun Shukla 2024-08-12 17:05:47 +00:00
parent 20ce9b413f
commit 984598d14d
6 changed files with 221 additions and 21 deletions

View File

@ -19,10 +19,19 @@ static mozilla::LazyLogModule sLog("Windows11LimitedAccessFeatures");
// 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>
@ -110,24 +119,87 @@ using namespace mozilla;
https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/TaskbarManager/CppUnpackagedDesktopTaskbarPin
*/
struct LimitedAccessFeatureInfo {
const nsCString debugName;
const nsString feature;
const nsString token;
const nsString attestation;
};
/**
* 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));
static LimitedAccessFeatureInfo limitedAccessFeatureInfo[] = {
{// Win11LimitedAccessFeatureType::Taskbar
"Win11LimitedAccessFeatureType::Taskbar"_ns,
u"com.microsoft.windows.taskbar.pin"_ns, u"kRFiWpEK5uS6PMJZKmR7MQ=="_ns,
u"pcsmm0jrprpb2 has registered their use of "_ns
u"com.microsoft.windows.taskbar.pin with Microsoft and agrees to the "_ns
u"terms "_ns
u"of use."_ns}};
// 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);
static_assert(mozilla::ArrayLength(limitedAccessFeatureInfo) ==
kWin11LimitedAccessFeatureTypeCount);
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.
@ -141,7 +213,7 @@ class Win11LimitedAccessFeatures : public Win11LimitedAccessFeaturesInterface {
private:
AtomicState& GetState(Win11LimitedAccessFeatureType feature);
Result<bool, HRESULT> UnlockImplementation(
Win11LimitedAccessFeatureType feature);
const LimitedAccessFeatureInfo& lafInfo);
/**
* Store the state as an atomic so that it can be safely accessed from
@ -175,6 +247,18 @@ 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,
@ -193,7 +277,7 @@ Result<bool, HRESULT> Win11LimitedAccessFeatures::Unlock(
// both threads will unlock the feature. This situation is unlikely, but even
// if it happens, it's not a problem.
auto result = UnlockImplementation(feature);
auto result = UnlockImplementation(lafInfo);
int newState = Locked;
if (!result.isErr() && result.unwrap()) {
@ -223,12 +307,10 @@ Win11LimitedAccessFeatures::AtomicState& Win11LimitedAccessFeatures::GetState(
}
Result<bool, HRESULT> Win11LimitedAccessFeatures::UnlockImplementation(
Win11LimitedAccessFeatureType feature) {
const LimitedAccessFeatureInfo& lafInfo) {
ComPtr<ILimitedAccessFeaturesStatics> limitedAccessFeatures;
ComPtr<ILimitedAccessFeatureRequestResult> limitedAccessFeaturesResult;
const auto& lafInfo = limitedAccessFeatureInfo[static_cast<int>(feature)];
HRESULT hr = RoGetActivationFactory(
HStringReference(
RuntimeClass_Windows_ApplicationModel_LimitedAccessFeatures)
@ -279,4 +361,10 @@ CreateWin11LimitedAccessFeaturesInterface() {
return result;
}
static mozilla::Result<LimitedAccessFeatureInfo, nsresult>
GenerateLimitedAccessFeatureInfo(const nsCString& debugName,
const nsString& lafId) {
return mozilla::Err(NS_ERROR_NOT_IMPLEMENTED);
}
#endif

View File

@ -7,6 +7,7 @@
#define SHELL_WINDOWS11LIMITEDACCESSFEATURES_H__
#include "nsISupportsImpl.h"
#include "nsString.h"
#include "mozilla/Result.h"
#include "mozilla/ResultVariant.h"
#include <winerror.h>
@ -47,7 +48,18 @@ class Win11LimitedAccessFeaturesInterface {
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__

View File

@ -0,0 +1,40 @@
/* -*- Mode: C++; tab-width: 8; 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/. */
#include "gtest/gtest.h"
#include "Windows11LimitedAccessFeatures.h"
#include "WinUtils.h"
TEST(LimitedAccessFeature, VerifyGeneratedInfo)
{
// If running on MSIX we have no guarantee that the
// generated LAF info will match the known values.
if (mozilla::widget::WinUtils::HasPackageIdentity()) {
return;
}
LimitedAccessFeatureInfo knownLafInfo = {
// Win11LimitedAccessFeatureType::Taskbar
"Win11LimitedAccessFeatureType::Taskbar"_ns, // debugName
u"com.microsoft.windows.taskbar.pin"_ns, // feature
u"kRFiWpEK5uS6PMJZKmR7MQ=="_ns, // token
u"pcsmm0jrprpb2 has registered their use of "_ns // attestation
u"com.microsoft.windows.taskbar.pin with Microsoft and agrees to the "_ns
u"terms "_ns
u"of use."_ns};
auto generatedLafInfoResult = GenerateLimitedAccessFeatureInfo(
"Win11LimitedAccessFeatureType::Taskbar"_ns,
u"com.microsoft.windows.taskbar.pin"_ns);
ASSERT_TRUE(generatedLafInfoResult.isOk());
LimitedAccessFeatureInfo generatedLafInfo = generatedLafInfoResult.unwrap();
// Check for equality between generated values and known good values
ASSERT_TRUE(knownLafInfo.debugName.Equals(generatedLafInfo.debugName));
ASSERT_TRUE(knownLafInfo.feature.Equals(generatedLafInfo.feature));
ASSERT_TRUE(knownLafInfo.token.Equals(generatedLafInfo.token));
ASSERT_TRUE(knownLafInfo.attestation.Equals(generatedLafInfo.attestation));
}

View File

@ -8,6 +8,7 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
LOCAL_INCLUDES += ["/browser/components/shell"]
UNIFIED_SOURCES += [
"LimitedAccessFeatureTests.cpp",
"ShellLinkTests.cpp",
]

View File

@ -0,0 +1,58 @@
===============================
Windows Limited Access Features
===============================
--------
Overview
--------
`Limited Access Features (LAF)
<https://learn.microsoft.com/en-us/uwp/api/windows.applicationmodel.limitedaccessfeatures?view=winrt-26100>`_ are
features which require a special token and attestation to unlock them before
their corresponding APIs can be called. These usually take the form
``com.microsoft.windows.featureFamily.name``. This is most relevant to Firefox in
the context of pinning to the Windows taskbar as the new Windows pinning APIs require
Firefox to first unlock the corresponding ``com.microsoft.windows.taskbar.pin``
LAF.
If we need to use a new Limited Access Feature we should notify Microsoft
if requested in the feature's documentation.
-------------------
Unlocking Procedure
-------------------
Applications which exist in a packaged context, such as MSIX installs,
have something called a Package Family Name (PFN). The PFN is generated
at build time for MSIX installs and varies between channels. This can be
accessed through Windows API calls on MSIX. For non-MSIX installs we are
provided a specific PFN by Microsoft which lives in the rc file in
the final install and can be modified in ``create_rc.py``.
The registry key corresponding to the LAF can be read at
``HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModel\LimitedAccessFeatures\<lafId>``
This key's default value contains a string which is the "key" for the
Limited Access Feature.
To get the complete unlocking token, the LAF identifier, LAF key, and PFN
can be combined in the format ``"<lafId>!<lafKey>!<PFN>"`` and then
encoded in SHA256. Taking the first 16 characters of this output and
converting to Base64 yields the final token. The overall process is
as follows:
.. code:: text
Base64(SHA256Encode("<lafId>!<lafKey>!<PFN>")[0..16])
The other piece of unlocking is the attestation. We first need
the publisher identifier, which consists of the last 13 characters
of the PFN. With that, the attestation is assembled using the boilerplate
below:
.. code:: text
<PFN[-13]> has registered their use of <lafId>
with Microsoft and agrees to the terms of use.
The token and attestation can then be passed into ``LimitedAccessFeature.TryUnlockFeature()``
to unlock the corresponding APIs for use.

View File

@ -7,3 +7,4 @@ Firefox on Windows
blocklist
windows-pointing-device/index
LimitedAccessFeature