[Libreoffice-commits] online.git: Makefile.am wsd/LOOLWSD.cpp wsd/ProofKey.cpp wsd/ProofKey.hpp wsd/Storage.cpp
Mike Kaganski (via logerrit)
logerrit at kemper.freedesktop.org
Thu Nov 21 11:57:11 UTC 2019
Makefile.am | 3
wsd/LOOLWSD.cpp | 11 ++
wsd/ProofKey.cpp | 220 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
wsd/ProofKey.hpp | 32 ++++++++
wsd/Storage.cpp | 38 +++++----
5 files changed, 286 insertions(+), 18 deletions(-)
New commits:
commit a986aabeb1d899b123fa3486008e4ecb2b981642
Author: Mike Kaganski <mike.kaganski at collabora.com>
AuthorDate: Thu Nov 7 18:24:37 2019 +0300
Commit: Mike Kaganski <mike.kaganski at collabora.com>
CommitDate: Thu Nov 21 12:56:53 2019 +0100
Initial implementation of proof-key
Change-Id: I7ab79218ca2af268dd4573cb64c6353dc71b5f03
Reviewed-on: https://gerrit.libreoffice.org/82232
Reviewed-by: Mike Kaganski <mike.kaganski at collabora.com>
Tested-by: Mike Kaganski <mike.kaganski at collabora.com>
diff --git a/Makefile.am b/Makefile.am
index 9bed60626..1035a22e2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -109,7 +109,8 @@ loolwsd_sources = common/Crypto.cpp \
wsd/ClientSession.cpp \
wsd/FileServer.cpp \
wsd/Storage.cpp \
- wsd/TileCache.cpp
+ wsd/TileCache.cpp \
+ wsd/ProofKey.cpp
loolwsd_SOURCES = $(loolwsd_sources) \
$(shared_sources)
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index e7b4f7439..cfa03034a 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -10,6 +10,7 @@
#include <config.h>
#include "LOOLWSD.hpp"
+#include "ProofKey.hpp"
/* Default host used in the start test URI */
#define LOOLWSD_TEST_HOST "localhost"
@@ -3002,6 +3003,16 @@ private:
LOOLWSD::EditFileExtensions.insert(elem->getAttribute("ext"));
}
+ const auto& proofAttribs = GetProofKeyAttributes();
+ if (!proofAttribs.empty())
+ {
+ // Add proof-key element to wopi-discovery root
+ AutoPtr<Element> keyElem = docXML->createElement("proof-key");
+ for (const auto& attrib : proofAttribs)
+ keyElem->setAttribute(attrib.first, attrib.second);
+ docXML->documentElement()->appendChild(keyElem);
+ }
+
std::ostringstream ostrXML;
DOMWriter writer;
writer.writeNode(ostrXML, docXML);
diff --git a/wsd/ProofKey.cpp b/wsd/ProofKey.cpp
new file mode 100644
index 000000000..d4587dc70
--- /dev/null
+++ b/wsd/ProofKey.cpp
@@ -0,0 +1,220 @@
+/* -*- 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 <config.h>
+
+#include "ProofKey.hpp"
+#include "LOOLWSD.hpp"
+
+#include <cassert>
+#include <chrono>
+#include <cstdlib>
+#include <memory>
+
+#include <Poco/Base64Decoder.h>
+#include <Poco/Base64Encoder.h>
+#include <Poco/BinaryWriter.h>
+#include <Poco/Crypto/RSADigestEngine.h>
+#include <Poco/Crypto/RSAKey.h>
+#include <Poco/Dynamic/Var.h>
+#include <Poco/JSON/Object.h>
+#include <Poco/JSON/Parser.h>
+#include <Poco/LineEndingConverter.h>
+#include <Poco/Net/HTTPClientSession.h>
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/HTTPResponse.h>
+#include <Poco/Net/NetException.h>
+#include <Poco/StringTokenizer.h>
+#include <Poco/Timestamp.h>
+#include <Poco/URI.h>
+#include <Poco/Util/Application.h>
+
+#include <Log.hpp>
+#include <Util.hpp>
+
+namespace{
+
+class Proof {
+public:
+ Proof();
+ VecOfStringPairs GetProofHeaders(const std::string& access_token, const std::string& uri) const;
+ const VecOfStringPairs& GetProofKeyAttributes() const { return m_aAttribs; }
+private:
+ static std::string ProofKeyPath();
+
+ // Returns .Net tick (=100ns) count since 0001-01-01 00:00:00 Z
+ // See https://docs.microsoft.com/en-us/dotnet/api/system.datetime.ticks
+ static int64_t DotNetTicks(const std::chrono::system_clock::time_point& utc);
+ // Returns string of bytes to sign and base64-encode
+ // See http://www.wictorwilen.se/sharepoint-2013-building-your-own-wopi-client-part-2
+ static std::string GetProof(const std::string& access_token, const std::string& uri, int64_t ticks);
+ // Signs string of bytes and returns base64-encoded string
+ std::string SignProof(const std::string& proof) const;
+
+ const std::unique_ptr<const Poco::Crypto::RSAKey> m_pKey;
+ VecOfStringPairs m_aAttribs;
+};
+
+Proof::Proof()
+ : m_pKey([]() -> Poco::Crypto::RSAKey* {
+ try
+ {
+ return new Poco::Crypto::RSAKey("", ProofKeyPath());
+ }
+ catch (const Poco::Exception& e)
+ {
+ LOG_ERR("Could not open proof RSA key: " << e.displayText());
+ }
+ catch (const std::exception& e)
+ {
+ LOG_ERR("Could not open proof RSA key: " << e.what());
+ }
+ catch (...)
+ {
+ LOG_ERR("Could not open proof RSA key: unknown exception");
+ }
+ return nullptr;
+ }())
+{
+ if (m_pKey)
+ {
+ {
+ // TODO: This is definitely not correct at the moment. The proof key must be
+ // base64-encoded blob in "unmanaged Microsoft Cryptographic API (CAPI)" format
+ // (as .Net's RSACryptoServiceProvider::ExportScpBlob returns).
+ std::ostringstream oss;
+ Poco::OutputLineEndingConverter lineEndingConv(oss, "");
+ m_pKey->save(&lineEndingConv);
+ std::string sKey = oss.str();
+ const std::string sBegin = "-----BEGIN RSA PUBLIC KEY-----";
+ const std::string sEnd = "-----END RSA PUBLIC KEY-----";
+ auto pos = sKey.find(sBegin);
+ if (pos != std::string::npos)
+ sKey = sKey.substr(pos + sBegin.length());
+ pos = sKey.find(sEnd);
+ if (pos != std::string::npos)
+ sKey = sKey.substr(0, pos);
+ m_aAttribs.emplace_back("value", sKey);
+ }
+ {
+ std::ostringstream oss;
+ // The signature generated contains CRLF line endings.
+ // Use a line ending converter to remove these CRLF
+ Poco::OutputLineEndingConverter lineEndingConv(oss, "");
+ Poco::Base64Encoder encoder(lineEndingConv);
+ const auto m = m_pKey->modulus();
+ encoder << std::string(m.begin(), m.end());
+ encoder.close();
+ m_aAttribs.emplace_back("modulus", oss.str());
+ }
+ {
+ std::ostringstream oss;
+ // The signature generated contains CRLF line endings.
+ // Use a line ending converter to remove these CRLF
+ Poco::OutputLineEndingConverter lineEndingConv(oss, "");
+ Poco::Base64Encoder encoder(lineEndingConv);
+ const auto e = m_pKey->encryptionExponent();
+ encoder << std::string(e.begin(), e.end());
+ encoder.close();
+ m_aAttribs.emplace_back("exponent", oss.str());
+ }
+ }
+}
+
+std::string Proof::ProofKeyPath()
+{
+ const std::string keyPath = LOOLWSD_CONFIGDIR "/proof_key";
+ if (!Poco::File(keyPath).exists())
+ {
+ std::string msg = "Could not find " + keyPath +
+ "\nNo proof-key will be present in discovery."
+ "\nGenerate an RSA key using this command line:"
+ "\n ssh-keygen -t rsa -N \"\" -f \"" + keyPath + "\"";
+ fprintf(stderr, "%s\n", msg.c_str());
+ LOG_WRN(msg);
+ }
+
+ return keyPath;
+}
+
+int64_t Proof::DotNetTicks(const std::chrono::system_clock::time_point& utc)
+{
+ // Get time point for Unix epoch; unfortunately from_time_t isn't constexpr
+ const auto aUnxEpoch(std::chrono::system_clock::from_time_t(0));
+ const auto duration_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(utc - aUnxEpoch);
+ return duration_ns.count() / 100 + 621355968000000000;
+}
+
+std::string Proof::GetProof(const std::string& access_token, const std::string& uri, int64_t ticks)
+{
+ std::string decoded_access_token;
+ Poco::URI::decode(access_token, decoded_access_token);
+ assert(decoded_access_token.size() <= std::numeric_limits<int32_t>::max()
+ && uri.size() <= std::numeric_limits<int32_t>::max());
+ const size_t size = 4 + decoded_access_token.size() + 4 + uri.size() + 4 + 8;
+ Poco::Buffer<char> buffer(size); // allocate enough size
+ buffer.resize(0); // start from empty buffer
+ Poco::MemoryBinaryWriter writer(buffer, Poco::BinaryWriter::NETWORK_BYTE_ORDER);
+ writer << static_cast<int32_t>(decoded_access_token.size())
+ << decoded_access_token
+ << static_cast<int32_t>(uri.size())
+ << uri
+ << int32_t(8)
+ << ticks;
+ assert(buffer.size() == size);
+ return std::string(buffer.begin(), buffer.end());
+}
+
+std::string Proof::SignProof(const std::string& proof) const
+{
+ assert(m_pKey);
+ std::ostringstream ostr;
+ static Poco::Crypto::RSADigestEngine digestEngine(*m_pKey, "SHA256");
+ digestEngine.update(proof.c_str(), proof.length());
+ Poco::Crypto::DigestEngine::Digest digest = digestEngine.signature();
+ // The signature generated contains CRLF line endings.
+ // Use a line ending converter to remove these CRLF
+ Poco::OutputLineEndingConverter lineEndingConv(ostr, "");
+ Poco::Base64Encoder encoder(lineEndingConv);
+ encoder << std::string(digest.begin(), digest.end());
+ encoder.close();
+ return ostr.str();
+}
+
+VecOfStringPairs Proof::GetProofHeaders(const std::string& access_token, const std::string& uri) const
+{
+ VecOfStringPairs vec;
+ if (m_pKey)
+ {
+ int64_t ticks = DotNetTicks(std::chrono::system_clock::now());
+ vec.emplace_back("X-WOPI-TimeStamp", std::to_string(ticks));
+ vec.emplace_back("X-WOPI-Proof", SignProof(GetProof(access_token, uri, ticks)));
+ }
+ return vec;
+}
+
+const Proof& GetProof()
+{
+ static const Proof proof;
+ return proof;
+}
+
+}
+
+VecOfStringPairs GetProofHeaders(const std::string& access_token, const std::string& uri)
+{
+ return GetProof().GetProofHeaders(access_token, uri);
+}
+
+const VecOfStringPairs& GetProofKeyAttributes()
+{
+ return GetProof().GetProofKeyAttributes();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/ProofKey.hpp b/wsd/ProofKey.hpp
new file mode 100644
index 000000000..e1d4b63a3
--- /dev/null
+++ b/wsd/ProofKey.hpp
@@ -0,0 +1,32 @@
+/* -*- 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/.
+ */
+
+// WOPI proof management
+#ifndef INCLUDED_PROOFKEY_HPP
+#define INCLUDED_PROOFKEY_HPP
+
+#include <string>
+#include <utility>
+#include <vector>
+
+typedef std::vector<std::pair<std::string, std::string>> VecOfStringPairs;
+
+// Returns pairs <header_name, header_value> to add to request
+// The headers returned are X-WOPI-TimeStamp, X-WOPI-Proof
+// If no proof key, returns empty vector
+// Both parameters are utf-8-encoded strings
+VecOfStringPairs GetProofHeaders(const std::string& access_token, const std::string& uri);
+
+// Returns pairs <attribute, value> to set in proof-key element in discovery xml.
+// If no proof key, returns empty vector
+const VecOfStringPairs& GetProofKeyAttributes();
+
+#endif // INCLUDED_PROOFKEY_HPP
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp
index 5569763ee..6b5b87daf 100644
--- a/wsd/Storage.cpp
+++ b/wsd/Storage.cpp
@@ -47,6 +47,7 @@
#include <Log.hpp>
#include <Unit.hpp>
#include <Util.hpp>
+#include "ProofKey.hpp"
#include <common/FileUtil.hpp>
#include <common/JsonUtil.hpp>
@@ -445,18 +446,18 @@ static void addStorageReuseCookie(Poco::Net::HTTPRequest& request, const std::st
}
}
-std::string getReuseCookies(const Poco::URI &uriObject)
+void addWopiProof(Poco::Net::HTTPRequest& request, const std::string& access_token)
{
- std::string reuseStorageCookies;
- for (const auto& param : uriObject.getQueryParameters())
- {
- if (param.first == "reuse_cookies")
- {
- reuseStorageCookies = param.second;
- break;
- }
- }
- return reuseStorageCookies;
+ for (const auto header : GetProofHeaders(access_token, request.getURI()))
+ request.set(header.first, header.second);
+}
+
+std::map<std::string, std::string> GetQueryParams(const Poco::URI& uri)
+{
+ std::map<std::string, std::string> result;
+ for (const auto& param : uri.getQueryParameters())
+ result.emplace(param);
+ return result;
}
} // anonymous namespace
@@ -467,7 +468,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au
Poco::URI uriObject(getUri());
auth.authorizeURI(uriObject);
const std::string uriAnonym = LOOLWSD::anonymizeUrl(uriObject.toString());
- std::string reuseStorageCookies = getReuseCookies(uriObject);
+ std::map<std::string, std::string> params = GetQueryParams(uriObject);
LOG_DBG("Getting info for wopi uri [" << uriAnonym << "].");
@@ -480,7 +481,8 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au
auth.authorizeRequest(request);
addStorageDebugCookie(request);
if (_reuseCookies)
- addStorageReuseCookie(request, reuseStorageCookies);
+ addStorageReuseCookie(request, params["reuse_cookies"]);
+ addWopiProof(request, params["access_token"]);
const auto startTime = std::chrono::steady_clock::now();
std::unique_ptr<Poco::Net::HTTPClientSession> psession(getHTTPClientSession(uriObject));
@@ -676,7 +678,7 @@ std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth, const
uriObject.setPath(uriObject.getPath() + "/contents");
auth.authorizeURI(uriObject);
- std::string reuseStorageCookies = getReuseCookies(uriObject);
+ std::map<std::string, std::string> params = GetQueryParams(uriObject);
Poco::URI uriObjectAnonym(getUri());
uriObjectAnonym.setPath(LOOLWSD::anonymizeUrl(uriObjectAnonym.getPath()) + "/contents");
@@ -705,7 +707,8 @@ std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth, const
auth.authorizeRequest(request);
addStorageDebugCookie(request);
if (_reuseCookies)
- addStorageReuseCookie(request, reuseStorageCookies);
+ addStorageReuseCookie(request, params["reuse_cookies"]);
+ addWopiProof(request, params["access_token"]);
psession->sendRequest(request);
Poco::Net::HTTPResponse response;
@@ -772,7 +775,7 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
uriObject.setPath(isSaveAs || isRename? uriObject.getPath(): uriObject.getPath() + "/contents");
auth.authorizeURI(uriObject);
- std::string reuseStorageCookies = getReuseCookies(uriObject);
+ std::map<std::string, std::string> params = GetQueryParams(uriObject);
const std::string uriAnonym = LOOLWSD::anonymizeUrl(uriObject.toString());
@@ -854,7 +857,8 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const Authorization&
request.setContentLength(size);
addStorageDebugCookie(request);
if (_reuseCookies)
- addStorageReuseCookie(request, reuseStorageCookies);
+ addStorageReuseCookie(request, params["reuse_cookies"]);
+ addWopiProof(request, params["access_token"]);
std::ostream& os = psession->sendRequest(request);
std::ifstream ifs(filePath);
More information about the Libreoffice-commits
mailing list