ucb/CppunitTest_ucb_webdav_core.mk           |   14 +
 ucb/Library_ucpdav1.mk                       |   19 ++
 ucb/source/ucp/webdav-curl/CurlSession.cxx   |   13 +
 ucb/source/ucp/webdav-curl/CurlSession.hxx   |    4 
 ucb/source/ucp/webdav-curl/ImportCookies.cxx |  236 +++++++++++++++++++++++++++
 5 files changed, 286 insertions(+)

New commits:
commit e0a66ba5852d480c70507d9f389c17c308324d55
Author:     Michael Stahl <[email protected]>
AuthorDate: Fri Aug 26 20:55:46 2022 +0200
Commit:     Michael Stahl <[email protected]>
CommitDate: Fri Aug 26 20:58:37 2022 +0200

    ucb: webdav-curl: import Sharepoint FedAuth cookie from Edge
    
    Change-Id: I1d22ff039f10304588820388e62701ea1f515a80

diff --git a/ucb/CppunitTest_ucb_webdav_core.mk 
b/ucb/CppunitTest_ucb_webdav_core.mk
index 2d37401da28b..060968e96b61 100644
--- a/ucb/CppunitTest_ucb_webdav_core.mk
+++ b/ucb/CppunitTest_ucb_webdav_core.mk
@@ -29,6 +29,20 @@ $(eval $(call 
gb_CppunitTest_use_library_objects,ucb_webdav_core, \
        ucpdav1 \
 ))
 
+ifeq ($(OS),WNT)
+$(eval $(call gb_CppunitTest_add_libs,ucb_webdav_core,\
+       $(call gb_UnpackedTarball_get_dir,nss)/dist/out/lib/sqlite3.lib \
+))
+$(eval $(call gb_CppunitTest_use_externals,ucb_webdav_core,\
+       nss3 \
+))
+$(eval $(call gb_CppunitTest_use_system_win32_libs,ucb_webdav_core,\
+       crypt32 \
+       Ole32 \
+       shell32 \
+))
+endif
+
 $(eval $(call gb_CppunitTest_use_externals,ucb_webdav_core,\
        boost_headers \
        libxml2 \
diff --git a/ucb/Library_ucpdav1.mk b/ucb/Library_ucpdav1.mk
index 7b8812563e87..91933763f0ec 100644
--- a/ucb/Library_ucpdav1.mk
+++ b/ucb/Library_ucpdav1.mk
@@ -36,6 +36,24 @@ $(eval $(call gb_Library_use_externals,ucpdav1,\
        curl \
 ))
 
+ifeq ($(OS),WNT)
+$(eval $(call gb_Library_set_include,ucpdav1,\
+    $$(INCLUDE) \
+       -I$(call gb_UnpackedTarball_get_dir,nss)/dist/private/nss \
+))
+$(eval $(call gb_Library_add_libs,ucpdav1,\
+       $(call gb_UnpackedTarball_get_dir,nss)/dist/out/lib/sqlite3.lib \
+))
+$(eval $(call gb_Library_use_externals,ucpdav1,\
+       nss3 \
+))
+$(eval $(call gb_Library_use_system_win32_libs,ucpdav1,\
+       crypt32 \
+       Ole32 \
+       shell32 \
+))
+endif
+
 $(eval $(call gb_Library_add_exception_objects,ucpdav1,\
        ucb/source/ucp/webdav-curl/ContentProperties \
        ucb/source/ucp/webdav-curl/CurlSession \
@@ -45,6 +63,7 @@ $(eval $(call gb_Library_add_exception_objects,ucpdav1,\
        ucb/source/ucp/webdav-curl/DAVSessionFactory \
        ucb/source/ucp/webdav-curl/DAVTypes \
        ucb/source/ucp/webdav-curl/DateTimeHelper \
+       ucb/source/ucp/webdav-curl/ImportCookies \
        ucb/source/ucp/webdav-curl/PropfindCache \
        ucb/source/ucp/webdav-curl/SerfLockStore \
        ucb/source/ucp/webdav-curl/UCBDeadPropertyValue \
diff --git a/ucb/source/ucp/webdav-curl/CurlSession.cxx 
b/ucb/source/ucp/webdav-curl/CurlSession.cxx
index c3fd76062e2c..c503b265fcb0 100644
--- a/ucb/source/ucp/webdav-curl/CurlSession.cxx
+++ b/ucb/source/ucp/webdav-curl/CurlSession.cxx
@@ -707,6 +707,19 @@ 
CurlSession::CurlSession(uno::Reference<uno::XComponentContext> const& xContext,
         rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_FORBID_REUSE, 1L);
         assert(rc == CURLE_OK);
     }
+#ifdef _WIN32
+    if (m_URI.GetScheme() == "https")
+    {
+        OString const cookies(TryImportCookies(m_xContext, m_URI.GetHost()));
+        if (!cookies.isEmpty())
+        {
+            rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_COOKIEFILE, "");
+            assert(rc == CURLE_OK);
+            rc = curl_easy_setopt(m_pCurl.get(), CURLOPT_COOKIE, 
cookies.getStr());
+            assert(rc == CURLE_OK);
+        }
+    }
+#endif
 }
 
 CurlSession::~CurlSession() {}
diff --git a/ucb/source/ucp/webdav-curl/CurlSession.hxx 
b/ucb/source/ucp/webdav-curl/CurlSession.hxx
index 75ccf682692c..5428bb98b23f 100644
--- a/ucb/source/ucp/webdav-curl/CurlSession.hxx
+++ b/ucb/source/ucp/webdav-curl/CurlSession.hxx
@@ -140,6 +140,10 @@ public:
     auto NonInteractive_UNLOCK(OUString const& rURI) -> void;
 };
 
+OString TryImportCookies(
+    ::com::sun::star::uno::Reference<::com::sun::star::uno::XComponentContext> 
const& xContext,
+    OUString const& rHost);
+
 } // namespace http_dav_ucp
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/ucb/source/ucp/webdav-curl/ImportCookies.cxx 
b/ucb/source/ucp/webdav-curl/ImportCookies.cxx
new file mode 100644
index 000000000000..897299da3c0a
--- /dev/null
+++ b/ucb/source/ucp/webdav-curl/ImportCookies.cxx
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * 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 "CurlSession.hxx"
+#include "CurlUri.hxx"
+
+#include <comphelper/base64.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+
+#include <com/sun/star/xml/crypto/DigestID.hpp>
+#include <com/sun/star/xml/crypto/NSSInitializer.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <osl/file.hxx>
+#include <sal/log.hxx>
+#include <rtl/string.hxx>
+#include <rtl/ustring.hxx>
+
+#ifdef _WIN32
+#include <boost/property_tree/json_parser.hpp>
+
+#include <sqlite3.h>
+#include <pk11pub.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <Shlobj.h>
+#include <Knownfolders.h>
+#include <dpapi.h>
+#endif
+
+using namespace ::com::sun::star;
+
+namespace http_dav_ucp
+{
+#ifdef _WIN32
+struct Value
+{
+    OString value;
+    OString encryptedValue;
+};
+
+static int callback(void* pArg, int argc, char** argv, char** ppColNames)
+{
+    Value* const pValue(static_cast<Value*>(pArg));
+    assert(argc == 3);
+    assert(strcmp(ppColNames[0], "value") == 0);
+    assert(strcmp(ppColNames[2], "encrypted_value") == 0);
+    pValue->value = OString(argv[0]); // base64 has no embedded 0
+    auto const len(OString(argv[1]).toInt32());
+    assert(len >= 0);
+    pValue->encryptedValue = OString(argv[2], len);
+    return 0;
+}
+#endif
+
+OString TryImportCookies(uno::Reference<uno::XComponentContext> const& 
xContext[[maybe_unused]],
+                         OUString const& rHost[[maybe_unused]])
+{
+#ifdef _WIN32
+    // Sharepoint authentication: try to find a cookie from Microsoft Edge
+    PWSTR ladPath;
+    if (S_OK != SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT, 
nullptr, &ladPath))
+    {
+        SAL_INFO("ucb.ucp.webdav.curl", "ShGetKnownFolderPath failed");
+        return OString();
+    }
+    OUString const localAppDirPath(o3tl::toU(ladPath));
+    CoTaskMemFree(ladPath);
+    OUString localAppDirUrl;
+    ::osl::File::getFileURLFromSystemPath(localAppDirPath, localAppDirUrl);
+    ::osl::DirectoryItem temp;
+    OUString dbUrlU = localAppDirUrl + "/Microsoft/Edge/User 
Data/Default/Network/Cookies";
+    if (::osl::DirectoryItem::get(dbUrlU, temp) != osl_File_E_None)
+    {
+        dbUrlU = localAppDirUrl + "/Microsoft/Edge/User Data/Default/Cookies";
+        if (::osl::DirectoryItem::get(dbUrlU, temp) != osl_File_E_None)
+        {
+            SAL_INFO("ucb.ucp.webdav.curl", "no Cookies file");
+            return OString();
+        }
+    }
+    OString const dbUrl(::rtl::OUStringToOString(dbUrlU, 
RTL_TEXTENCODING_UTF8));
+    sqlite3* db;
+    int rc = sqlite3_open_v2(dbUrl.getStr(), &db, SQLITE_OPEN_READONLY | 
SQLITE_OPEN_URI, nullptr);
+    if (rc != SQLITE_OK)
+    {
+        SAL_INFO("ucb.ucp.webdav.curl", "sqlite3_open failed: " << 
sqlite3_errmsg(db));
+        sqlite3_close(db);
+    }
+    char* err(nullptr);
+    Value value;
+    OString const statement("SELECT value, LENGTH(encrypted_value), 
encrypted_value FROM cookies "
+                            "WHERE name = \"FedAuth\"  and host_key = \""
+                            + ::rtl::OUStringToOString(rHost, 
RTL_TEXTENCODING_ASCII_US) + "\";");
+    rc = sqlite3_exec(db, statement.getStr(), callback, &value, &err);
+    if (rc != SQLITE_OK)
+    {
+        SAL_WARN("ucb.ucp.webdav.curl", "sqlite3_exec failed: " << err);
+        sqlite3_free(err);
+    }
+    sqlite3_close(db);
+    if (!value.value.isEmpty())
+    {
+        return value.value;
+    }
+    if (value.encryptedValue.getLength() < 3 + 12 + 16)
+    {
+        SAL_INFO("ucb.ucp.webdav.curl", "encrypted_value too short");
+        return OString();
+    }
+
+    OString const iv(value.encryptedValue.copy(3, 12));
+    OString const encryptedValue(
+        value.encryptedValue.copy(3 + 12, value.encryptedValue.getLength() - 3 
- 12 - 16));
+    OString const 
tag(value.encryptedValue.copy(value.encryptedValue.getLength() - 16, 16));
+
+    OUString const stateUrl = localAppDirUrl + "/Microsoft/Edge/User 
Data/Local State";
+    OUString statePathU;
+    ::osl::File::getSystemPathFromFileURL(stateUrl, statePathU);
+    OString statePath(::rtl::OUStringToOString(statePathU, 
RTL_TEXTENCODING_UTF8));
+    ::std::string sEncryptedKey;
+    try
+    {
+        ::boost::property_tree::ptree localState;
+        ::boost::property_tree::read_json(::std::string(statePath.getStr()), 
localState);
+        sEncryptedKey = localState.get<std::string>("os_crypt.encrypted_key");
+    }
+    catch (...)
+    {
+        SAL_INFO("ucb.ucp.webdav.curl", "failed to parse Local State");
+        return OString();
+    }
+    OUString const encodedEncryptedKey(sEncryptedKey.data(), 
sEncryptedKey.size(),
+                                       RTL_TEXTENCODING_UTF8);
+    uno::Sequence<sal_Int8> decodedEncryptedKey;
+    ::comphelper::Base64::decode(decodedEncryptedKey, encodedEncryptedKey);
+    DATA_BLOB protectedKey;
+    protectedKey.cbData = decodedEncryptedKey.getLength() - 5;
+    protectedKey.pbData
+        = 
reinterpret_cast<BYTE*>(const_cast<sal_Int8*>(decodedEncryptedKey.getConstArray()))
 + 5;
+    DATA_BLOB unprotectedKey;
+    if (CryptUnprotectData(&protectedKey, nullptr, nullptr, nullptr, nullptr,
+                           CRYPTPROTECT_UI_FORBIDDEN, &unprotectedKey)
+        == FALSE)
+    {
+        SAL_INFO("ucb.ucp.webdav.curl", "CryptUnprotectData failed");
+        assert(false);
+        return OString();
+    }
+    ::comphelper::ScopeGuard g([&]() {
+        SecureZeroMemory(unprotectedKey.pbData, unprotectedKey.cbData);
+        LocalFree(unprotectedKey.pbData);
+    });
+    if (unprotectedKey.cbData < 16)
+    {
+        SAL_WARN("ucb.ucp.webdav.curl", "CryptUnprotectData result too small");
+        return OString();
+    }
+
+    // first, init NSS - but can't do AES GCM via API so do it directly
+    uno::Reference<xml::crypto::XNSSInitializer> xNSS(
+        xml::crypto::NSSInitializer::create(xContext));
+    xNSS->getDigestContext(xml::crypto::DigestID::SHA256, {});
+    SECItem keyItem = { siBuffer, reinterpret_cast<unsigned 
char*>(unprotectedKey.pbData),
+                        sal::static_int_cast<unsigned>(unprotectedKey.cbData) 
};
+    ::std::unique_ptr<PK11SlotInfo, deleter_from_fn<PK11SlotInfo, 
PK11_FreeSlot>> pSlot(
+        PK11_GetBestSlot(CKM_AES_GCM, nullptr));
+    if (!pSlot)
+    {
+        SAL_WARN("ucb.ucp.webdav.curl", "PK11_GetBestSlot failed");
+        return OString();
+    }
+    ::std::unique_ptr<PK11SymKey, deleter_from_fn<PK11SymKey, 
PK11_FreeSymKey>> pSymKey(
+        PK11_ImportSymKey(pSlot.get(), CKM_AES_GCM, PK11_OriginDerive, 
CKA_DECRYPT, &keyItem,
+                          nullptr));
+    if (!pSymKey)
+    {
+        SAL_WARN("ucb.ucp.webdav.curl", "PK11_ImportSymKey failed");
+        return OString();
+    }
+    SECItem dummy = { siBuffer, nullptr, 0 };
+    struct ContextDeleter
+    {
+        void operator()(PK11Context* p) const { PK11_DestroyContext(p, 
PR_TRUE); }
+    };
+    ::std::unique_ptr<PK11Context, ContextDeleter> 
pContext(PK11_CreateContextBySymKey(
+        CKM_AES_GCM, CKA_NSS_MESSAGE | CKA_DECRYPT, pSymKey.get(), &dummy));
+    if (!pContext)
+    {
+        SAL_WARN("ucb.ucp.webdav.curl", "PK11_CreateContextBySymKey failed");
+        return OString();
+    }
+
+    ::std::vector<unsigned char> unencryptedValue;
+    unencryptedValue.resize(encryptedValue.getLength());
+    int outLength(0);
+    SECStatus rv = PK11_AEADOp(
+        pContext.get(), CKG_NO_GENERATE, 0, // only used for encryption
+        reinterpret_cast<unsigned char*>(const_cast<sal_Char*>(iv.getStr())), 
iv.getLength(),
+        nullptr, 0, // "additional data" not used
+        unencryptedValue.data(), &outLength, encryptedValue.getLength(),
+        reinterpret_cast<unsigned char*>(const_cast<sal_Char*>(tag.getStr())), 
tag.getLength(),
+        reinterpret_cast<const unsigned char*>(encryptedValue.getStr()),
+        encryptedValue.getLength());
+    if (rv != SECSuccess)
+    {
+        SAL_WARN("ucb.ucp.webdav.curl", "PK11_AEADOp failed");
+        return OString();
+    }
+    if (outLength != encryptedValue.getLength())
+    {
+        SAL_WARN("ucb.ucp.webdav.curl", "PK11_AEADOp unexpected output 
length");
+        return OString();
+    }
+
+    return "FedAuth="
+           + OString(reinterpret_cast<const char*>(unencryptedValue.data()),
+                     unencryptedValue.size())
+           + ";";
+
+#else
+    return OString();
+#endif
+}
+
+} // namespace http_dav_ucp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

Reply via email to