libQuotient
A Qt library for building matrix clients
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 
20 namespace Quotient {
21 
22 constexpr inline auto AlgorithmKeyL = "algorithm"_ls;
23 constexpr inline auto RotationPeriodMsKeyL = "rotation_period_ms"_ls;
24 constexpr inline auto RotationPeriodMsgsKeyL = "rotation_period_msgs"_ls;
25 
26 constexpr inline auto AlgorithmKey = "algorithm"_ls;
27 constexpr inline auto RotationPeriodMsKey = "rotation_period_ms"_ls;
28 constexpr inline auto RotationPeriodMsgsKey = "rotation_period_msgs"_ls;
29 
30 constexpr inline auto Ed25519Key = "ed25519"_ls;
31 constexpr inline auto Curve25519Key = "curve25519"_ls;
32 constexpr inline auto SignedCurve25519Key = "signed_curve25519"_ls;
33 
34 constexpr inline auto OlmV1Curve25519AesSha2AlgoKey = "m.olm.v1.curve25519-aes-sha2"_ls;
35 constexpr inline auto MegolmV1AesSha2AlgoKey = "m.megolm.v1.aes-sha2"_ls;
36 
37 constexpr std::array SupportedAlgorithms { OlmV1Curve25519AesSha2AlgoKey,
38  MegolmV1AesSha2AlgoKey };
39 
40 inline bool isSupportedAlgorithm(const QString& algorithm)
41 {
42  return std::find(SupportedAlgorithms.cbegin(), SupportedAlgorithms.cend(),
43  algorithm)
44  != SupportedAlgorithms.cend();
45 }
46 
47 #define QOLM_INTERNAL_ERROR_X(Message_, LastError_)
48  qFatal("%s, internal error: %s", Message_, LastError_)
49 
50 #define QOLM_INTERNAL_ERROR(Message_)
51  QOLM_INTERNAL_ERROR_X(Message_, lastError())
52 
53 #define QOLM_FAIL_OR_LOG_X(InternalCondition_, Message_, LastErrorText_)
54  do {
55  const QString errorMsg{ (Message_) };
56  if (InternalCondition_)
57  QOLM_INTERNAL_ERROR_X(qPrintable(errorMsg), (LastErrorText_));
58  qWarning(E2EE).nospace() << errorMsg << ": " << (LastErrorText_);
59  } while (false) /* End of macro */
60 
61 #define QOLM_FAIL_OR_LOG(InternalFailureValue_, Message_)
62  QOLM_FAIL_OR_LOG_X(lastErrorCode() == (InternalFailureValue_), (Message_),
63  lastError())
64 
65 template <typename T>
66 using QOlmExpected = Expected<T, OlmErrorCode>;
67 
68 //! \brief Initialise a buffer object for use with Olm calls
69 //!
70 //! Qt and Olm use different size types; this causes the warning noise
71 QUOTIENT_API QByteArray byteArrayForOlm(size_t bufferSize);
72 
73 //! \brief Get a size of Qt container coerced to size_t
74 //!
75 //! It's a safe cast since size_t can always accommodate the range between
76 //! 0 and SIZE_MAX / 2 - 1 that Qt containers support; yet compilers complain...
77 inline size_t unsignedSize(const auto& qtBuffer)
78 {
79  return static_cast<size_t>(qtBuffer.size());
80 }
81 
82 // Can't use std::byte normally recommended for the purpose because both Olm
83 // and OpenSSL get uint8_t* pointers, and std::byte* is not implicitly
84 // convertible to uint8_t* (and adding explicit casts in each case kinda defeats
85 // the purpose of all the span machinery below meant to replace reinterpret_ or
86 // any other casts).
87 
88 using byte_t = uint8_t;
89 
90 template <size_t N = std::dynamic_extent>
91 using byte_view_t = std::span<const byte_t, N>;
92 
93 template <size_t N = std::dynamic_extent>
94 using byte_span_t = std::span<byte_t, N>;
95 
96 namespace _impl {
97  QUOTIENT_API void checkForSpanShortfall(QByteArray::size_type inputSize, int neededSize);
98 
99  template <typename SpanT>
100  inline auto spanFromByteArray(auto& byteArray)
101  {
102  // OpenSSL only handles int sizes; Release builds will cut the tail off
103  Q_ASSERT_X(std::in_range<int>(byteArray.size()), __func__, "Too long array for OpenSSL");
104  if constexpr (SpanT::extent != std::dynamic_extent) {
105  static_assert(std::in_range<int>(SpanT::extent));
106  checkForSpanShortfall(byteArray.size(), static_cast<int>(SpanT::extent));
107  }
108  return SpanT(reinterpret_cast<typename SpanT::pointer>(byteArray.data()),
109  std::min(SpanT::extent, unsignedSize(byteArray)));
110  }
111 } // namespace _impl
112 
113 //! \brief Obtain a std::span<const byte_t, N> looking into the passed buffer
114 //!
115 //! This function returns an adaptor object that is suitable for OpenSSL/Olm
116 //! invocations (via std::span<>::data() accessor) so that you don't have
117 //! to wrap your QByteArray into ugly reinterpret_casts on every OpenSSL call.
118 //! \note The caller is responsible for making sure that bytes.size() is small
119 //! enough to fit into an int (OpenSSL only handles int sizes atm) but
120 //! also large enough to have at least N bytes if N is not
121 //! `std::dynamic_extent`
122 //! \sa asWritableCBytes for the case when you need to pass a buffer for writing
123 template <size_t N = std::dynamic_extent>
124 inline auto asCBytes(const QByteArray& bytes)
125 {
126  return _impl::spanFromByteArray<byte_view_t<N>>(bytes);
127 }
128 
129 template <size_t N = std::dynamic_extent>
130 inline auto asCBytes(QLatin1String bytes)
131 { // TODO, 0.9: Replace this and QByteArray overload above with a single one for QByteArrayView
132  return _impl::spanFromByteArray<byte_view_t<N>>(bytes);
133 }
134 
135 //! Obtain a std::span<byte_t, N> looking into the passed buffer
136 template <size_t N = std::dynamic_extent>
137 inline auto asWritableCBytes(QByteArray& bytes)
138 {
139  return _impl::spanFromByteArray<byte_span_t<N>>(bytes);
140 }
141 
142 // 0.9: use Ranges instead?
143 template <class BufferT>
144 inline auto asWritableCBytes(BufferT& buf) -> auto
145  requires(!std::is_const_v<typename BufferT::value_type>)
146 {
147  return byte_span_t<BufferT::extent>(buf);
148 }
149 
150 template <class BufferT>
151 inline auto asCBytes(const BufferT& buf) -> auto
152 {
153  return byte_view_t<BufferT::extent>(buf);
154 }
155 
156 inline auto viewAsByteArray(const auto& aRange) -> auto
157  requires (sizeof(*aRange.data()) == sizeof(char))
158 { // -> auto to activate SFINAE, it's always QByteArray when well-formed
159  return QByteArray::fromRawData(reinterpret_cast<const char*>(
160  std::data(aRange)),
161  static_cast<int>(std::size(aRange)));
162 }
163 
164 //! Non-template base for owning byte span classes
165 class QUOTIENT_API FixedBufferBase {
166 public:
167  enum InitOptions { Uninitialized, FillWithZeros, FillWithRandom };
168 
169  using value_type = byte_t; // TODO, 0.9: uint8_t -> value_type below
170  using size_type = size_t; // TODO, 0.9: size_t -> size_type below
171 
172  static constexpr auto TotalSecureHeapSize = 65'536;
173 
174  auto size() const { return data_ == nullptr ? 0 : size_; }
175  auto empty() const { return data_ == nullptr || size_ == 0; }
176 
177  void clear();
178 
179  //! \brief Access the bytes of the fixed buffer via QByteArray interface
180  //!
181  //! This uses QByteArray::fromRawData() to create a QByteArray object that
182  //! refers to the original fixed buffer, without copying.
183  //! \warning the lifetime of the returned QByteArray should not exceed the
184  //! lifetime of the underlying buffer; in particular, you should
185  //! never try using the result of viewAsByteArray() as a return
186  //! value of your function
187  //! \sa copyToByteArray
188  QByteArray viewAsByteArray() const
189  {
190  static_assert(std::in_range<QByteArray::size_type>(TotalSecureHeapSize));
191  return QByteArray::fromRawData(reinterpret_cast<const char*>(data_),
192  static_cast<QByteArray::size_type>(size_));
193  }
194 
195  //! \brief Copy the contents of the buffer to a QByteArray
196  //!
197  //! Unlike viewAsByteArray(), this function actually copies the buffer to
198  //! non-secure memory.
199  QByteArray copyToByteArray(QByteArray::size_type untilPos = -1) const
200  {
201  if (untilPos < 0 || static_cast<size_type>(untilPos) > size_)
202  untilPos = static_cast<QByteArray::size_type>(size_);
203  return { reinterpret_cast<const char*>(data_), untilPos };
204  }
205 
206  // TODO, 0.9: merge the overloads
207 
208  QByteArray toBase64() const { return viewAsByteArray().toBase64(); }
209  QByteArray toBase64(QByteArray::Base64Options options) const
210  {
211  return viewAsByteArray().toBase64(options);
212  }
213 
214  Q_DISABLE_COPY(FixedBufferBase)
215  FixedBufferBase& operator=(FixedBufferBase&&) = delete;
216 
217 protected:
218  FixedBufferBase(size_t bufferSize, InitOptions options);
219  ~FixedBufferBase() { clear(); }
220 
221  FixedBufferBase(FixedBufferBase&& other)
222  : data_(std::exchange(other.data_, nullptr)), size_(other.size_)
223  {}
224 
225  void fillFrom(QByteArray&& source);
226 
227  uint8_t* dataForWriting() { return data_; }
228  const uint8_t* data() const { return data_; }
229 
230 private:
231  uint8_t* data_ = nullptr;
232  size_t size_ = 0;
233 };
234 
235 template <size_t ExtentN = std::dynamic_extent, bool DataIsWriteable = true>
236 class QUOTIENT_API FixedBuffer : public FixedBufferBase {
237 public:
238  static constexpr auto extent = ExtentN; // Matching std::span
239  static_assert(extent == std::dynamic_extent
240  || (extent < TotalSecureHeapSize / 2 && extent % 4 == 0));
241 
242  FixedBuffer() // TODO, 0.9: merge with the next constructor
243  requires(extent != std::dynamic_extent)
244  : FixedBuffer(FillWithZeros)
245  {}
246  explicit FixedBuffer(InitOptions fillMode)
247  requires(extent != std::dynamic_extent)
248  : FixedBufferBase(ExtentN, fillMode)
249  {}
250  explicit FixedBuffer(size_t bufferSize)
251  requires(extent == std::dynamic_extent)
252  : FixedBuffer(bufferSize, FillWithZeros)
253  {}
254  explicit FixedBuffer(size_t bufferSize, InitOptions fillMode)
255  requires(extent == std::dynamic_extent)
256  : FixedBufferBase(bufferSize, fillMode)
257  {}
258 
259  using FixedBufferBase::data;
260  uint8_t* data() requires DataIsWriteable { return dataForWriting(); }
261 
262  // NOLINTNEXTLINE(google-explicit-constructor)
263  QUO_IMPLICIT operator byte_view_t<ExtentN>() const
264  {
265  return byte_view_t<ExtentN>(data(), size());
266  }
267 
268  // NOLINTNEXTLINE(google-explicit-constructor)
269  QUO_IMPLICIT operator byte_span_t<ExtentN>()
270  requires DataIsWriteable
271  {
272  return byte_span_t<ExtentN>(dataForWriting(), size());
273  }
274 };
275 
276 inline auto getRandom(size_t bytes)
277 {
278  return FixedBuffer<>{ bytes, FixedBufferBase::FillWithRandom };
279 }
280 
281 template <size_t SizeN>
282 inline auto getRandom()
283 {
284  return FixedBuffer<SizeN>{ FixedBufferBase::FillWithRandom };
285 }
286 
287 //! \brief Fill the buffer with the securely generated random bytes
288 //!
289 //! You should use this throughout Quotient where pseudo-random generators
290 //! are not enough (i.e. in crypto cases). Don't use it when proper randomness
291 //! is not critical; it tries to rely on system entropy that is in (somewhat)
292 //! limited supply.
293 //! There's no fancy stuff internally, it's just a way to unify secure RNG usage
294 //! in Quotient. See the function definition for details if you want/need.
295 QUOTIENT_API void fillFromSecureRng(std::span<byte_t> bytes);
296 
297 class PicklingKey : public FixedBuffer<128, /*DataIsWriteable=*/false> {
298 private:
299  // `using` would have exposed the constructor as it's public in the parent
300  explicit PicklingKey(InitOptions options) : FixedBuffer(options)
301  {
302  Q_ASSERT(options != FillWithZeros);
303  }
304 
305 public:
306  static PicklingKey generate() { return PicklingKey(FillWithRandom); }
307  static PicklingKey fromByteArray(QByteArray&& keySource)
308  {
309  PicklingKey k(Uninitialized);
310  k.fillFrom(std::move(keySource));
311  return k;
312  }
313  static PicklingKey mock() { return PicklingKey(Uninitialized); }
314 };
315 
316 struct IdentityKeys
317 {
318  // Despite being Base64 payloads, these keys are stored in QStrings because
319  // in the vast majority of cases they are used to read from or write to
320  // QJsonObjects, and that effectively requires QStrings
321  QString curve25519;
322  QString ed25519;
323 };
324 
325 //! Struct representing the one-time keys.
326 struct UnsignedOneTimeKeys
327 {
328  QHash<QString, QHash<QString, QString>> keys;
329 
330  //! Get the HashMap containing the curve25519 one-time keys.
331  QHash<QString, QString> curve25519() const { return keys[Curve25519Key]; }
332 };
333 
334 class QUOTIENT_API SignedOneTimeKey {
335 public:
336  explicit SignedOneTimeKey(const QString& unsignedKey, const QString& userId,
337  const QString& deviceId,
338  const QByteArray& signature)
339  : payload{
340  { "key"_ls, unsignedKey },
341  { "signatures"_ls,
342  QJsonObject{
343  { userId, QJsonObject{ { "ed25519:"_ls % deviceId,
344  QString::fromUtf8(signature) } } } } }
345  }
346  {}
347  explicit SignedOneTimeKey(const QJsonObject& jo = {})
348  : payload(jo)
349  {}
350 
351  //! Unpadded Base64-encoded 32-byte Curve25519 public key
352  QByteArray key() const { return payload["key"_ls].toString().toLatin1(); }
353 
354  //! \brief Signatures of the key object
355  //!
356  //! The signature is calculated using the process described at
357  //! https://spec.matrix.org/v1.3/appendices/#signing-json
358  auto signatures() const
359  {
360  return fromJson<QHash<QString, QHash<QString, QString>>>(
361  payload["signatures"_ls]);
362  }
363 
364  QByteArray signature(QStringView userId, QStringView deviceId) const
365  {
366  return payload["signatures"_ls][userId]["ed25519:"_ls % deviceId]
367  .toString()
368  .toLatin1();
369  }
370 
371  //! Whether the key is a fallback key
372  bool isFallback() const { return payload["fallback"_ls].toBool(); }
373  auto toJson() const { return payload; }
374  auto toJsonForVerification() const
375  {
376  auto json = payload;
377  json.remove("signatures"_ls);
378  json.remove("unsigned"_ls);
379  return QJsonDocument(json).toJson(QJsonDocument::Compact);
380  }
381 
382 private:
383  QJsonObject payload;
384 };
385 
386 using OneTimeKeys = QHash<QString, std::variant<QString, SignedOneTimeKey>>;
387 
388 } // namespace Quotient
389 
390 Q_DECLARE_METATYPE(Quotient::SignedOneTimeKey)