libQuotient
A Qt library for building matrix clients
Loading...
Searching...
No Matches
e2ee_common.h
Go to the documentation of this file.
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 "../converters.h"
9#include "../expected.h" // It's only here to not break client code still using Expected
10
11#include <QtCore/QMetaType>
12#include <QtCore/QStringBuilder>
13
14#include <olm/error.h>
15
16#include <array>
17#include <span>
18#include <variant>
19
20namespace Quotient {
21
22constexpr inline auto AlgorithmKeyL = "algorithm"_L1;
23constexpr inline auto RotationPeriodMsKeyL = "rotation_period_ms"_L1;
24constexpr inline auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_L1;
25
26constexpr inline auto AlgorithmKey = "algorithm"_L1;
27constexpr inline auto RotationPeriodMsKey = "rotation_period_ms"_L1;
28constexpr inline auto RotationPeriodMsgsKey = "rotation_period_msgs"_L1;
29
30constexpr inline auto Ed25519Key = "ed25519"_L1;
31constexpr inline auto Curve25519Key = "curve25519"_L1;
32constexpr inline auto SignedCurve25519Key = "signed_curve25519"_L1;
33
34constexpr inline auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_L1;
35
36constexpr std::array SupportedAlgorithms { OlmV1Curve25519AesSha2AlgoKey,
37 MegolmV1AesSha2AlgoKey };
38
39inline bool isSupportedAlgorithm(const QString& algorithm)
40{
41 return std::ranges::find(SupportedAlgorithms, algorithm) != SupportedAlgorithms.cend();
42}
43
44#define QOLM_INTERNAL_ERROR_X(Message_, LastError_)
45 qFatal("%s, internal error: %s", QUO_CSTR(Message_), LastError_)
46
47#define QOLM_INTERNAL_ERROR(Message_)
48 QOLM_INTERNAL_ERROR_X((Message_), lastError())
49
50#define QOLM_FAIL_OR_LOG_X(InternalCondition_, Message_, LastErrorText_)
51 do {
52 if (InternalCondition_)
53 QOLM_INTERNAL_ERROR_X((Message_), (LastErrorText_));
54 qWarning(E2EE).nospace() << (Message_) << ": " << (LastErrorText_);
55 } while (false) /* End of macro */
56
57#define QOLM_FAIL_OR_LOG(InternalFailureValue_, Message_)
58 QOLM_FAIL_OR_LOG_X(lastErrorCode() == (InternalFailureValue_), (Message_), lastError())
59
60template <typename T>
62
63//! \brief Initialise a buffer object for use with Olm calls
64//!
65//! Qt and Olm use different size types; this causes the warning noise
67
68//! \brief Get a size of a container coerced to size_t
69//!
70//! This is mainly aimed at Qt containers because they have signed size; but it can also be called
71//! on other containers or even C arrays, e.g. - to spare generic code from special-casing.
72//! For Qt containers, it's a safe cast since size_t can always accommodate the range between 0 and
73//! SIZE_MAX / 2 - 1 that they support; yet compilers complain...
74inline size_t unsignedSize(const auto& buffer)
75 requires (sizeof(std::size(buffer)) <= sizeof(size_t))
76{
77 return static_cast<size_t>(std::size(buffer));
78}
79
80// Can't use std::byte normally recommended for the purpose because both Olm
81// and OpenSSL get uint8_t* pointers, and std::byte* is not implicitly
82// convertible to uint8_t* (and adding explicit casts in each case kinda defeats
83// the purpose of all the span machinery below meant to replace reinterpret_ or
84// any other casts).
85
87
88template <size_t N = std::dynamic_extent>
89using byte_view_t = std::span<const byte_t, N>;
90
91template <size_t N = std::dynamic_extent>
93
94namespace _impl {
95 QUOTIENT_API void checkForSpanShortfall(QByteArray::size_type inputSize, int neededSize);
96
97 template <typename SpanT>
98 inline auto spanFromBytes(auto& byteArray)
99 {
100 // OpenSSL only handles int sizes; Release builds will cut the tail off
101 Q_ASSERT_X(std::in_range<int>(std::size(byteArray)), __func__, "Too long array for OpenSSL");
102 if constexpr (SpanT::extent != std::dynamic_extent) {
103 static_assert(std::in_range<int>(SpanT::extent));
104 checkForSpanShortfall(std::size(byteArray), static_cast<int>(SpanT::extent));
105 }
106 return SpanT(std::bit_cast<typename SpanT::pointer>(std::data(byteArray)),
107 std::min(SpanT::extent, unsignedSize(byteArray)));
108 }
109} // namespace _impl
110
111//! \brief Obtain a std::span<const byte_t, N> looking into the passed buffer
112//!
113//! This function returns an adaptor object that is suitable for OpenSSL/Olm
114//! invocations (via std::span<>::data() accessor) so that you don't have
115//! to wrap your containers into reinterpret/bit_casts on every OpenSSL call.
116//! \note The caller is responsible for making sure that bytes.size() is small
117//! enough to fit into an int (OpenSSL only handles int sizes atm) but
118//! also large enough to have at least N bytes if N is not `std::dynamic_extent`
119//! \sa asWritableCBytes for the case when you need to pass a buffer for writing
120template <size_t N = std::dynamic_extent>
121inline auto asCBytes(const auto& buf)
122{
123 return _impl::spanFromBytes<byte_view_t<N>>(buf);
124}
125
126//! Obtain a std::span<byte_t, N> looking into the passed buffer
127template <size_t N = std::dynamic_extent>
128inline auto asWritableCBytes(auto& buf)
129{
130 return _impl::spanFromBytes<byte_span_t<N>>(buf);
131}
132
133inline auto viewAsByteArray(const auto& aRange) -> auto
134 requires (sizeof(*aRange.data()) == sizeof(char))
135{ // -> auto to activate SFINAE, it's always QByteArray when well-formed
136 return QByteArray::fromRawData(std::bit_cast<const char*>(std::data(aRange)),
137 static_cast<int>(std::size(aRange)));
138}
139
140//! Non-template base for owning byte span classes
142public:
144
147
148 static constexpr auto TotalSecureHeapSize = 65'536;
149
150 auto size() const { return data_ == nullptr ? 0 : size_; }
151 auto empty() const { return data_ == nullptr || size_ == 0; }
152
153 void clear();
154
155 //! \brief Access the bytes of the fixed buffer via QByteArray interface
156 //!
157 //! This uses QByteArray::fromRawData() to create a QByteArray object that
158 //! refers to the original fixed buffer, without copying.
159 //! \warning the lifetime of the returned QByteArray should not exceed the
160 //! lifetime of the underlying buffer; in particular, you should
161 //! never try using the result of viewAsByteArray() as a return
162 //! value of your function
163 //! \sa copyToByteArray
165 {
167 return QByteArray::fromRawData(std::bit_cast<const char*>(data_),
168 static_cast<QByteArray::size_type>(size_));
169 }
170
171 //! \brief Copy the contents of the buffer to a QByteArray
172 //!
173 //! Unlike viewAsByteArray(), this function actually copies the buffer to
174 //! non-secure memory.
176 {
177 if (untilPos < 0 || static_cast<size_type>(untilPos) > size_)
178 untilPos = static_cast<QByteArray::size_type>(size_);
179 return { std::bit_cast<const char*>(data_), untilPos };
180 }
181
184 {
186 }
187
190
191protected:
194
196 : data_(std::exchange(other.data_, nullptr)), size_(other.size_)
197 {}
198
200
202 const value_type* data() const { return data_; }
203
204private:
205 value_type* data_ = nullptr;
206 size_type size_ = 0;
207};
208
209template <size_t ExtentN = std::dynamic_extent, bool DataIsWriteable = true>
211public:
212 static constexpr auto extent = ExtentN; // Matching std::span
213 static_assert(extent == std::dynamic_extent
214 || (extent < TotalSecureHeapSize / 2 && extent % 4 == 0));
215
219 {}
223 {}
227 {}
228
231
233 {
234 return byte_view_t<ExtentN>(data(), size());
235 }
236
239 {
241 }
242};
243
244inline auto getRandom(size_t bytes)
245{
246 return FixedBuffer<>{ bytes, FixedBufferBase::FillWithRandom };
247}
248
249template <size_t SizeN>
250inline auto getRandom()
251{
252 return FixedBuffer<SizeN>{ FixedBufferBase::FillWithRandom };
253}
254
255//! \brief Fill the buffer with the securely generated random bytes
256//!
257//! You should use this throughout Quotient where pseudo-random generators
258//! are not enough (i.e. in crypto cases). Don't use it when proper randomness
259//! is not critical; it tries to rely on system entropy that is in (somewhat)
260//! limited supply.
261//! There's no fancy stuff internally, it's just a way to unify secure RNG usage
262//! in Quotient. See the function definition for details if you want/need.
264
265class PicklingKey : public FixedBuffer<128, /*DataIsWriteable=*/false> {
266private:
267 // `using` would have exposed the constructor as it's public in the parent
268 explicit PicklingKey(InitOptions options) : FixedBuffer(options)
269 {
270 Q_ASSERT(options != FillWithZeros);
271 }
272
273public:
274 static PicklingKey generate() { return PicklingKey(FillWithRandom); }
275 static PicklingKey fromByteArray(QByteArray&& keySource)
276 {
277 PicklingKey k(Uninitialized);
278 k.fillFrom(std::move(keySource));
279 return k;
280 }
281 static PicklingKey mock() { return PicklingKey(Uninitialized); }
282};
283
285{
286 // Despite being Base64 payloads, these keys are stored in QStrings because
287 // in the vast majority of cases they are used to read from or write to
288 // QJsonObjects, and that effectively requires QStrings
291};
292
293//! Struct representing the one-time keys.
295{
297
298 //! Get the HashMap containing the curve25519 one-time keys.
299 QHash<QString, QString> curve25519() const { return keys[Curve25519Key]; }
300};
301
303public:
305 const QString& deviceId,
306 const QByteArray& signature)
307 : payload{
308 { "key"_L1, unsignedKey },
309 { "signatures"_L1,
311 { userId, QJsonObject{ { "ed25519:"_L1 % deviceId,
312 QString::fromUtf8(signature) } } } } }
313 }
314 {}
315 explicit SignedOneTimeKey(const QJsonObject& jo = {})
316 : payload(jo)
317 {}
318
319 //! Unpadded Base64-encoded 32-byte Curve25519 public key
320 QByteArray key() const { return payload["key"_L1].toString().toLatin1(); }
321
322 //! \brief Signatures of the key object
323 //!
324 //! The signature is calculated using the process described at
325 //! https://spec.matrix.org/v1.3/appendices/#signing-json
326 auto signatures() const
327 {
329 payload["signatures"_L1]);
330 }
331
333 {
334 return payload["signatures"_L1][userId]["ed25519:"_L1 % deviceId]
335 .toString()
336 .toLatin1();
337 }
338
339 //! Whether the key is a fallback key
340 bool isFallback() const { return payload["fallback"_L1].toBool(); }
341 auto toJson() const { return payload; }
343 {
344 auto json = payload;
345 json.remove("signatures"_L1);
346 json.remove("unsigned"_L1);
348 }
349
350private:
352};
353
355
356} // namespace Quotient
357
static PicklingKey fromByteArray(QByteArray &&keySource)
static PicklingKey mock()
static PicklingKey generate()
#define QOLM_FAIL_OR_LOG_X(InternalCondition_, Message_, LastErrorText_)
Definition e2ee_common.h:50
#define QOLM_INTERNAL_ERROR_X(Message_, LastError_)
Definition e2ee_common.h:44
auto asWritableCBytes(auto &buf)
Obtain a std::span<byte_t, N> looking into the passed buffer.
constexpr auto Ed25519Key
Definition e2ee_common.h:30
constexpr auto OlmV1Curve25519AesSha2AlgoKey
Definition e2ee_common.h:34
auto getRandom(size_t bytes)
bool isSupportedAlgorithm(const QString &algorithm)
Definition e2ee_common.h:39
constexpr auto SignedCurve25519Key
Definition e2ee_common.h:32
constexpr auto RotationPeriodMsKeyL
Definition e2ee_common.h:23
constexpr auto AlgorithmKey
Definition e2ee_common.h:26
auto getRandom()
constexpr auto Curve25519Key
Definition e2ee_common.h:31
constexpr auto RotationPeriodMsgsKeyL
Definition e2ee_common.h:24
auto asCBytes(const auto &buf)
Obtain a std::span<const byte_t, N> looking into the passed buffer.
constexpr auto RotationPeriodMsgsKey
Definition e2ee_common.h:28
constexpr auto AlgorithmKeyL
Definition e2ee_common.h:22
QUOTIENT_API void fillFromSecureRng(std::span< byte_t > bytes)
Fill the buffer with the securely generated random bytes.
constexpr std::array SupportedAlgorithms
Definition e2ee_common.h:36
constexpr auto RotationPeriodMsKey
Definition e2ee_common.h:27
#define QUOTIENT_API
Struct representing the one-time keys.
QHash< QString, QString > curve25519() const
Get the HashMap containing the curve25519 one-time keys.
QHash< QString, QHash< QString, QString > > keys
#define QUO_CSTR(StringConvertible_)
q(Utf8)Printable that can handle more than just QStrings
Definition util.h:63