1// SPDX-FileCopyrightText: 2019 Alexey Andreyev <aa13q@ya.ru>
2// SPDX-FileCopyrightText: 2019 Kitsune Ral <Kitsune-Ral@users.sf.net>
3// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
4// SPDX-License-Identifier: LGPL-2.1-or-later
5
6#pragma once
7
8#include <Quotient/converters.h>
9
10#include <QtCore/QMetaType>
11#include <QtCore/QStringBuilder>
12
13#include <array>
14
15#ifdef Quotient_E2EE_ENABLED
16# include <Quotient/expected.h>
17
18# include <olm/error.h>
19# include <span>
20# include <variant>
21#endif
22
23namespace Quotient {
24
25constexpr inline auto AlgorithmKeyL = "algorithm"_ls;
26constexpr inline auto RotationPeriodMsKeyL = "rotation_period_ms"_ls;
27constexpr inline auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls;
28
29constexpr inline auto AlgorithmKey = "algorithm"_ls;
30constexpr inline auto RotationPeriodMsKey = "rotation_period_ms"_ls;
31constexpr inline auto RotationPeriodMsgsKey = "rotation_period_msgs"_ls;
32
33constexpr inline auto Ed25519Key = "ed25519"_ls;
34constexpr inline auto Curve25519Key = "curve25519"_ls;
35constexpr inline auto SignedCurve25519Key = "signed_curve25519"_ls;
36
37constexpr inline auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_ls;
38constexpr inline auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls;
39
40constexpr std::array SupportedAlgorithms { OlmV1Curve25519AesSha2AlgoKey,
41 MegolmV1AesSha2AlgoKey };
42
43inline bool isSupportedAlgorithm(const QString& algorithm)
44{
45 return std::find(first: SupportedAlgorithms.cbegin(), last: SupportedAlgorithms.cend(),
46 val: algorithm)
47 != SupportedAlgorithms.cend();
48}
49
50#ifdef Quotient_E2EE_ENABLED
51
52#define QOLM_INTERNAL_ERROR_X(Message_, LastError_) \
53 qFatal("%s, internal error: %s", Message_, LastError_)
54
55#define QOLM_INTERNAL_ERROR(Message_) \
56 QOLM_INTERNAL_ERROR_X(Message_, lastError())
57
58#define QOLM_FAIL_OR_LOG_X(InternalCondition_, Message_, LastErrorText_) \
59 do { \
60 const QString errorMsg{ (Message_) }; \
61 if (InternalCondition_) \
62 QOLM_INTERNAL_ERROR_X(qPrintable(errorMsg), (LastErrorText_)); \
63 qWarning(E2EE).nospace() << errorMsg << ": " << (LastErrorText_); \
64 } while (false) /* End of macro */
65
66#define QOLM_FAIL_OR_LOG(InternalFailureValue_, Message_) \
67 QOLM_FAIL_OR_LOG_X(lastErrorCode() == (InternalFailureValue_), (Message_), \
68 lastError())
69
70template <typename T>
71using QOlmExpected = Expected<T, OlmErrorCode>;
72
73//! \brief Initialise a buffer object for use with Olm calls
74//!
75//! Qt and Olm use different size types; this causes the warning noise
76QUOTIENT_API QByteArray byteArrayForOlm(size_t bufferSize);
77
78//! \brief Get a size of Qt container coerced to size_t
79//!
80//! It's a safe cast since size_t can easily accommodate the range between
81//! 0 and INTMAX - 1 that Qt containers support; yet compilers complain...
82inline size_t unsignedSize(const auto& qtBuffer)
83{
84 return static_cast<size_t>(qtBuffer.size());
85}
86
87class QUOTIENT_API FixedBufferBase {
88public:
89 enum InitOptions { Uninitialized, FillWithZeros, FillWithRandom };
90
91 static constexpr auto TotalSecureHeapSize = 65'536;
92
93 auto size() const { return data_ == nullptr ? 0 : size_; }
94 auto empty() const { return data_ == nullptr || size_ == 0; }
95
96 void clear();
97
98 QByteArray viewAsByteArray() const
99 {
100 // Ensure static_cast<int> is safe
101 static_assert(TotalSecureHeapSize < std::numeric_limits<int>::max());
102 return QByteArray::fromRawData(reinterpret_cast<const char*>(data_),
103 static_cast<int>(size_));
104 }
105 QByteArray toBase64(QByteArray::Base64Options options) const
106 {
107 return viewAsByteArray().toBase64(options);
108 }
109
110 Q_DISABLE_COPY(FixedBufferBase)
111 FixedBufferBase& operator=(FixedBufferBase&&) = delete;
112
113protected:
114 FixedBufferBase(size_t bufferSize, InitOptions options);
115 ~FixedBufferBase() { clear(); }
116
117 FixedBufferBase(FixedBufferBase&& other)
118 : data_(std::exchange(other.data_, nullptr)), size_(other.size_)
119 {}
120
121 void fillFrom(QByteArray&& source);
122
123 uint8_t* dataForWriting() { return data_; }
124 const uint8_t* data() const { return data_; }
125
126private:
127 uint8_t* data_ = nullptr;
128 size_t size_ = 0;
129};
130
131template <size_t ExtentN = std::dynamic_extent, bool DataIsWriteable = true>
132class QUOTIENT_API FixedBuffer : public FixedBufferBase {
133public:
134 static constexpr auto extent = ExtentN; // Matching std::span
135 static_assert(extent == std::dynamic_extent
136 || (extent < TotalSecureHeapSize / 2 && extent % 4 == 0));
137
138 explicit FixedBuffer(InitOptions fillMode) requires(extent
139 != std::dynamic_extent)
140 : FixedBufferBase(ExtentN, fillMode)
141 {}
142 explicit FixedBuffer(size_t bufferSize, InitOptions fillMode)
143 requires(extent == std::dynamic_extent)
144 : FixedBufferBase(bufferSize, fillMode)
145 {}
146
147 using FixedBufferBase::data;
148 uint8_t* data() requires DataIsWriteable { return dataForWriting(); }
149};
150
151inline auto getRandom(size_t bytes)
152{
153 return FixedBuffer<>{ bytes, FixedBufferBase::FillWithRandom };
154}
155
156template <size_t SizeN>
157inline auto getRandom()
158{
159 return FixedBuffer<SizeN>{ FixedBufferBase::FillWithRandom };
160}
161
162class PicklingKey : public FixedBuffer<128, /*DataIsWriteable=*/false> {
163private:
164 // `using` would have exposed the constructor as it's public in the parent
165 explicit PicklingKey(InitOptions options) : FixedBuffer(options)
166 {
167 Q_ASSERT(options != FillWithZeros);
168 }
169
170public:
171 static PicklingKey generate() { return PicklingKey(FillWithRandom); }
172 static PicklingKey fromByteArray(QByteArray&& keySource)
173 {
174 PicklingKey k(Uninitialized);
175 k.fillFrom(std::move(keySource));
176 return k;
177 }
178 static PicklingKey mock() { return PicklingKey(Uninitialized); }
179};
180
181#endif // Quotient_E2EE_ENABLED
182
183struct IdentityKeys
184{
185 // Despite being Base64 payloads, these keys are stored in QStrings because
186 // in the vast majority of cases they are used to read from or write to
187 // QJsonObjects, and that effectively requires QStrings
188 QString curve25519;
189 QString ed25519;
190};
191
192//! Struct representing the one-time keys.
193struct UnsignedOneTimeKeys
194{
195 QHash<QString, QHash<QString, QString>> keys;
196
197 //! Get the HashMap containing the curve25519 one-time keys.
198 QHash<QString, QString> curve25519() const { return keys[Curve25519Key]; }
199};
200
201class SignedOneTimeKey {
202public:
203 explicit SignedOneTimeKey(const QString& unsignedKey, const QString& userId,
204 const QString& deviceId,
205 const QByteArray& signature)
206 : payload { { "key"_ls, unsignedKey },
207 { "signatures"_ls,
208 QJsonObject {
209 { userId, QJsonObject { { "ed25519:"_ls % deviceId,
210 QString::fromUtf8(ba: signature) } } } } } }
211 {}
212 explicit SignedOneTimeKey(const QJsonObject& jo = {})
213 : payload(jo)
214 {}
215
216 //! Unpadded Base64-encoded 32-byte Curve25519 public key
217 QByteArray key() const { return payload["key"_ls].toString().toLatin1(); }
218
219 //! \brief Signatures of the key object
220 //!
221 //! The signature is calculated using the process described at
222 //! https://spec.matrix.org/v1.3/appendices/#signing-json
223 auto signatures() const
224 {
225 return fromJson<QHash<QString, QHash<QString, QString>>>(
226 json: payload["signatures"_ls]);
227 }
228
229 QByteArray signature(QStringView userId, QStringView deviceId) const
230 {
231 return payload["signatures"_ls][userId]["ed25519:"_ls % deviceId]
232 .toString()
233 .toLatin1();
234 }
235
236 //! Whether the key is a fallback key
237 bool isFallback() const { return payload["fallback"_ls].toBool(); }
238 auto toJson() const { return payload; }
239 auto toJsonForVerification() const
240 {
241 auto json = payload;
242 json.remove(key: "signatures"_ls);
243 json.remove(key: "unsigned"_ls);
244 return QJsonDocument(json).toJson(format: QJsonDocument::Compact);
245 }
246
247private:
248 QJsonObject payload;
249};
250
251using OneTimeKeys = QHash<QString, std::variant<QString, SignedOneTimeKey>>;
252
253} // namespace Quotient
254
255Q_DECLARE_METATYPE(Quotient::SignedOneTimeKey)
256