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; }