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