Hi all,

You are correct with respect to cookie size limit. Because there is a 4k limit 
for all cookies, when we introduced multiple accounts, we hit this limit and 
some proxies started to block our requests. That why we changed our cookies to 
headers.

Right now, IProofOfPossessionCookieInfoManager::GetCookieInfoForUri can return 
either a list of cookies or a list of headers.

GET 
https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=1fec8e78-bce4-4aaf-ab1b-5451cc387264&redirect_uri=ms-appx-web%3a%2f%2fMicrosoft.AAD.BrokerPlugin%2f1fec8e78-bce4-4aaf-ab1b-5451cc387264&resource=https%3a%2f%2fapi.spaces.skype.com&add_account=multiple&login_hint=aaa%40microsoft.com&response_mode=form_post&windows_api_version=2.0
 HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 
(KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.21329
x-ms-DeviceCredential: eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QiLCAieDVjI...
x-ms-RefreshTokenCredential: 
eyJrZGZfdmVyIjoyLCJjdHgiOiIyckJrVzcxQzFCSjg1Q1ZSSWtlWmRmYklReW9...
x-ms-DeviceCredential1: eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QiLCAieDVjI...
x-ms-RefreshTokenCredential1: 
eyJrZGZfdmVyIjoyLCJjdHgiOiIyckJrVzcxQzFCSjg1Q1ZSSWtlWmRmYklReW9...

if the name of the cookie starts from x-ms- then this cookie should be added as 
a header, otherwise as a cookie, please, see attached example:

                if (_wcsnicmp(tokens[i].name, L"x-ms-", _countof(L"x-ms-")) == 
0)
                {
                    std::wcout << "Add as a header: \n\t" << tokens[i].name << 
": " << tokens[i].data << std::endl;
                }
                else
                {
                    std::wcout << "Add as a cookie: \n\t" << tokens[i].name << 
"==" << tokens[i].data << tokens[i].p3pHeader << std::endl;
                    std::wcout << "\tP3P: " << tokens[i].p3pHeader << std::endl;
                    std::wcout << "\tdwFlags for InternetSetCookieEx: "<< 
std::hex << tokens[i].flags<< std::endl;
                    // 
https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetsetcookieexa
                }

Thank you,
Sasha

From: Ryan Sleevi rsle...@chromium.org<mailto:rsle...@chromium.org>
Sent: Tuesday, October 19, 2021 10:00 PM
To: Sasha Tokarev alex...@microsoft.com<mailto:alex...@microsoft.com>
Cc: z...@chromium.org<mailto:z...@chromium.org>; 
rsle...@chromium.org<mailto:rsle...@chromium.org>; 
blink-dev@chromium.org<mailto:blink-dev@chromium.org>; 
g...@chromium.org<mailto:g...@chromium.org>; 
mme...@chromium.org<mailto:mme...@chromium.org>; 
a...@chromium.org<mailto:a...@chromium.org>; 
yhir...@chromium.org<mailto:yhir...@chromium.org>; 
pastarm...@chromium.org<mailto:pastarm...@chromium.org>
Subject: Re: [EXTERNAL] Re: Native support of Windows SSO in Chrome



On Tue, Oct 19, 2021 at 9:41 PM Sasha Tokarev 
<alex...@microsoft.com<mailto:alex...@microsoft.com>> wrote:
> All of this relates to the questions I was previously asking, because at 
> least if my understanding is correct, this basically means that as currently 
> designed, it's not possible to really describe a "standard" flow or 
> specification. For example, it's not that a particular cookie value will be 
> present, or a particular header value - the web account provider can return 
> arbitrary cookies via the WebAccountProviderRetrieveCookiesOperation, and 
> these should just be passed on to any of the managedUrls. So IdP Foo might 
> call their cookie "SID", while IdP Bar might call their cookie "Token", and 
> IdP Baz might use multiple cookies, like "CAW", "DIDC", and "DIDCL". The 
> browser should just overwrite any cookies it has (e.g. from the browser 
> cookie jar, or from extensions) with the cookies provided by the Web Account 
> Provider/IProofOfPossessionCookieInfoManager - right?

-True, we tried to make IDP life easier, we wanted them to use any cookies 
names and semantic. Cookies are a private contract between IDP native component 
and IDP web service. We never pursued the goal to standardize this aspect.

All we can spec for browser SSO on Windows:

  1.  Windows can have a native IDP component, which installed by the user or 
built-in in the platform.
  2.  There is a public API that any web browser can use to pull a list of 
cookies/headers when navigation happens to an IDP url.
  3.  Cookie/header names and their semantic is a private contract between an 
IDP web and its native component on the platform.
  4.  The web browser should just override cookies with the same names.

I hope it clarifies.

It really does, and helps understand how this is a very new, very different 
integration, and does affect some of the understanding of how this fits in the 
overall Web Platform and interop story (while being mindful of balancing that 
with our existing threat 
model)<https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fchromium.googlesource.com%2Fchromium%2Fsrc%2F%2B%2Frefs%2Fheads%2Fmain%2Fdocs%2Fsecurity%2Ffaq.md%23Why-arent-physically_local-attacks-in-Chromes-threat-model&data=04%7C01%7Calextok%40microsoft.com%7Cd78230704f844cdd956608d9938685cb%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637703028293896522%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C1000&sdata=bAM8ClzapvH4WnHIQUT0DHM%2BcFp2jE7M5l8BoJbmwRc%3D&reserved=0>.

One question I had, that I forgot to follow-up on: with these APIs; whether 
WebAccountProviderRetrieveCookiesOperation or 
IProofOfPossessionCookieInfoManager::GetCookieInfoForUri, these deal with 
cookies. You mentioned headers a few times - could you provide any pointers to 
where the header interactions are / what the APIs? The context here that I'm 
thinking about is situations like cookie size limits and how these PoP cookies, 
which we don't know the size apriori, would interact with the browser cookie 
store. The use of headers mitigates some of that, although with their own 
complexities, and so it'd be useful to understand what that API shape looks 
like.

-- 
You received this message because you are subscribed to the Google Groups 
"blink-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to blink-dev+unsubscr...@chromium.org.
To view this discussion on the web visit 
https://groups.google.com/a/chromium.org/d/msgid/blink-dev/MW2PR00MB0378BA4CBABD5F1BF1C93DB3A1BF9%40MW2PR00MB0378.namprd00.prod.outlook.com.
// BSSO.cpp : This file contains the 'main' function. Program execution begins 
and ends there.
//

#include <iostream>
#include <wrl.h>
#include <windows.foundation.h>
#include <windows.security.authentication.web.core.h>
#include <lm.h>
#include <proofofpossessioncookieinfo.h>

#pragma comment(lib, "runtimeobject.lib")
#pragma comment(lib, "netapi32.lib")

using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Security::Authentication::Web::Core;
using namespace ABI::Windows::Security::Credentials;

bool IsWindowsSSOCapable();
bool IsHostNameSSOCapable(LPCWSTR hostWithScheme);
void DumpSSOArtifacts(LPCWSTR fullUrl);

int main()
{
    std::cout << "IsWindowsSSOCapable: " << std::boolalpha << 
IsWindowsSSOCapable() << std::endl;
    std::cout << "IsHostName SSO Capable (https://www.office.com): " << 
IsHostNameSSOCapable(L"https://www.office.com";) << std::endl;
    std::cout << "IsHostName SSO Capable (https://login.live.com): " << 
IsHostNameSSOCapable(L"https://login.live.com";) << std::endl;
    std::cout << "IsHostName SSO Capable (https://login.microsoftonline.com): " 
<< IsHostNameSSOCapable(L"https://login.microsoftonline.com";) << std::endl;
    
DumpSSOArtifacts(L"https://login.microsoftonline.com/common/oauth2/authorize?client_id=4345a7b9-9a63-4910-a426-35363201d503&redirect_uri=https%3A%2F%2Fwww.office.com%2Flanding&response_type=code%20id_token&scope=openid%20profile&response_mode=form_post&nonce=637606044728492219.NzE4ZTM3MzMtNzFkZi00ZjMyLThmYzYtMGUxMTdhNWNkMmUzYWE4OTM0N2YtZjJkMS00ZmJjLWI0YzEtNGJhNTMyNjlhMTg1&ui_locales=en-US&mkt=en-US&state=W0fX569Ah_dknKwvc2R-tgcZBfwBqHpVBSW5HRHXSg6-Nt_7fFL5AQaqjQAMUJYRdgPFb2H-xFToII8YmJONvgBNWx8sheflTJGpPR1A3hYKd_gmJEk2Td_kDcquzsbS_g5jHQ_D01AeEWFYgzyRUEPLfhsdB7-JqgNdIZ99NKGodX5Kf8-hMTz7I6CP34xnZ1QYnIvP7dhrq-3rqthF04J3i-n7-rKNGJcLIXHkdFyG3rnAGt1bepwVauuC6wEt3iBZ5zj9Xt5m_ndIoy96MQ&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=6.8.0.0#";);
    
DumpSSOArtifacts(L"https://login.live.com/login.srf?wa=wsignin1.0&rpsnv=13&ct=1625007720&rver=7.0.6737.0&wp=MBI_SSL&wreply=https%3a%2f%2foutlook.live.com%2fowa%2f%3fnlp%3d1%26RpsCsrfState%3dae3545d5-d1b4-517a-a807-9a503f122ce9&id=292841&aadredir=1&CBCXT=out&lw=1&fl=dob%2cflname%2cwld&cobrandid=90015";);
}

bool IsMSASSOCapable();
bool IsAADSSOCabable();

bool IsWindowsSSOCapable()
{
    return IsMSASSOCapable() || IsAADSSOCabable();
}

class CoInitializeWrapper
{
    HRESULT _hr;
public:
    CoInitializeWrapper(DWORD flags)
    {

        _hr = ::CoInitializeEx(nullptr, flags);
    }

    ~CoInitializeWrapper()
    {
        if (SUCCEEDED(_hr))
        {
            ::CoUninitialize();
        }
    }

    operator HRESULT()
    {
        return _hr;
    }
};

bool IsHostNameSSOCapable(LPCWSTR hostWithScheme)
{
    CoInitializeWrapper coInit(COINIT_MULTITHREADED);

    DWORD foundTokenCount = 0;
    ProofOfPossessionCookieInfo* tokens = nullptr;
    ComPtr<IProofOfPossessionCookieInfoManager> pCookieInfoManager;
    HRESULT hr = CoCreateInstance(__uuidof(ProofOfPossessionCookieInfoManager), 
nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pCookieInfoManager));
    if (SUCCEEDED(hr))
    {
        hr = pCookieInfoManager->GetCookieInfoForUri(hostWithScheme, 
&foundTokenCount, &tokens);
        if (SUCCEEDED(hr))
        {
            FreeProofOfPossessionCookieInfoArray(tokens, foundTokenCount);
            return foundTokenCount != 0;
        }
    }

    return false;
}


void DumpSSOArtifacts(LPCWSTR fullUrl)
{
    CoInitializeWrapper coInit(COINIT_MULTITHREADED);

    DWORD foundTokenCount = 0;
    ProofOfPossessionCookieInfo* tokens = nullptr;
    ComPtr<IProofOfPossessionCookieInfoManager> pCookieInfoManager;
    HRESULT hr = CoCreateInstance(__uuidof(ProofOfPossessionCookieInfoManager), 
nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pCookieInfoManager));
    if (SUCCEEDED(hr))
    {
        hr = pCookieInfoManager->GetCookieInfoForUri(fullUrl, &foundTokenCount, 
&tokens);
        if (SUCCEEDED(hr))
        {
            for (DWORD i = 0; i < foundTokenCount; i++)
            {
                if (_wcsnicmp(tokens[i].name, L"x-ms-", _countof(L"x-ms-")) == 
0)
                {
                    std::wcout << "Add as a header: \n\t" << tokens[i].name << 
": " << tokens[i].data << std::endl;
                }
                else
                {
                    std::wcout << "Add as a cookie: \n\t" << tokens[i].name << 
"==" << tokens[i].data << tokens[i].p3pHeader << std::endl;
                    std::wcout << "\tP3P: " << tokens[i].p3pHeader << std::endl;
                    std::wcout << "\tdwFlags for InternetSetCookieEx: "<< 
std::hex << tokens[i].flags<< std::endl;
                    // 
https://docs.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetsetcookieexa
                }
            }

            FreeProofOfPossessionCookieInfoArray(tokens, foundTokenCount);
        }
    }
}

template<
    typename InnerType,
    typename OutThing>
    HRESULT AwaitOperationResult(
        ABI::Windows::Foundation::IAsyncOperation<InnerType> *pOperation,
        OutThing &&out);

bool IsMSASSOCapable()
{
    Microsoft::WRL::Wrappers::RoInitializeWrapper 
initialize(RO_INIT_MULTITHREADED);

    HRESULT hr;
    ComPtr<IWebAuthenticationCoreManagerStatics> authenticationCoreManager;
    ComPtr<ABI::Windows::Foundation::IAsyncOperation<WebAccountProvider*>> 
findAccountProviderOperation;
    ComPtr<IWebAccountProvider> webProvider;
    ComPtr<IWebAccountProvider2> webProvider2;

    hr = ABI::Windows::Foundation::GetActivationFactory(
        
HStringReference(RuntimeClass_Windows_Security_Authentication_Web_Core_WebAuthenticationCoreManager).Get(),
        &authenticationCoreManager);
    if (FAILED(hr))
    {
        return false;
    }

    hr = authenticationCoreManager->FindAccountProviderAsync(
        HStringReference(L"https://login.windows.local";).Get(),
        &findAccountProviderOperation);
    
    if (FAILED(hr))
    {
        return false;
    }

    hr = AwaitOperationResult(findAccountProviderOperation.Get(), &webProvider);
    if (FAILED(hr))
    {
        return false;
    }

    if (webProvider == nullptr)
    {
        return false;
    }

    webProvider.As(&webProvider2);

    if (webProvider2 == nullptr)
    {
        return false;
    }

    HString value;
    hr = webProvider2->get_Authority(value.GetAddressOf());
    
    if (FAILED(hr))
    {
        return false;
    }

    LPCWSTR authority = value.GetRawBuffer(nullptr);

    return authority != nullptr && _wcsicmp(L"consumers", authority) == 0;
}

bool IsAADSSOCabable()
{
    PDSREG_JOIN_INFO pJoinInfo =  nullptr;
    HRESULT hr;
    hr = NetGetAadJoinInformation(nullptr, &pJoinInfo);

    if (hr != S_OK)
    {
        return false;
    }

    bool isAADJoined = pJoinInfo->joinType != DSREG_UNKNOWN_JOIN;

    if (pJoinInfo)
    {
        NetFreeAadJoinInformation(pJoinInfo);
    }

    return isAADJoined;
}

template<typename T> struct OperationCallback : public 
Microsoft::WRL::RuntimeClass<
    Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
    ABI::Windows::Foundation::IAsyncOperationCompletedHandler<T>>
{
public:

    HRESULT RuntimeClassInitialize()
    {
        _completedEvent = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS);

        return S_OK;
    }

    ~OperationCallback()
    {
        if (_completedEvent != NULL)
        {
            CloseHandle(_completedEvent);
            _completedEvent = NULL;
        }
    }

    IFACEMETHOD(Invoke)(ABI::Windows::Foundation::IAsyncOperation<T> *pOp, 
ABI::Windows::Foundation::AsyncStatus status)
    {
        UNREFERENCED_PARAMETER(pOp);
        UNREFERENCED_PARAMETER(status);
        SetEvent(_completedEvent);
        return S_OK;
    }

    HRESULT Wait()
    {
        DWORD index = 0;
        // Wait until Get Token UI flow is completed.
        HRESULT hr = CoWaitForMultipleObjects(CWMO_DISPATCH_CALLS, INFINITE, 1, 
&_completedEvent, &index);
        return hr;
    }

    HANDLE _completedEvent;
};

template<typename InnerType, typename OutThing>
HRESULT AwaitOperationResult(
    ABI::Windows::Foundation::IAsyncOperation<InnerType> *pOperation,
    OutThing &&out)
{
    HRESULT hr = S_OK;
    Microsoft::WRL::ComPtr<OperationCallback<InnerType>> completer;
    Microsoft::WRL::MakeAndInitialize<OperationCallback<InnerType>>(&completer);

    pOperation->put_Completed(completer.Get());
    hr = completer->Wait();
    if (FAILED(hr))
    {
        return hr;
    }

    hr = pOperation->GetResults(out);
    return hr;
}

Reply via email to