1#include "mtxclient/crypto/utils.hpp"
2
3#include <nlohmann/json.hpp>
4
5#include <openssl/aes.h>
6#include <openssl/evp.h>
7#include <openssl/hmac.h>
8#include <openssl/kdf.h>
9#include <openssl/rand.h>
10#include <openssl/sha.h>
11
12#include <olm/pk.h>
13
14#include <algorithm>
15#include <cstdint>
16
17#include "mtx/log.hpp"
18#include "mtxclient/crypto/client.hpp"
19
20namespace mtx {
21namespace crypto {
22BinaryBuf
23create_buffer(std::size_t nbytes)
24{
25 auto buf = BinaryBuf(nbytes);
26 RAND_bytes(buf: buf.data(), num: (int)buf.size());
27
28 return buf;
29}
30
31BinaryBuf
32PBKDF2_HMAC_SHA_512(const std::string &pass,
33 const BinaryBuf &salt,
34 uint32_t iterations,
35 uint32_t keylen)
36{
37 BinaryBuf out(keylen);
38 PKCS5_PBKDF2_HMAC(pass: &pass[0],
39 passlen: (int)pass.size(),
40 salt: salt.data(),
41 saltlen: (int)salt.size(),
42 iter: (int)iterations,
43 digest: EVP_sha512(),
44 keylen: (int)keylen,
45 out: out.data());
46
47 return out;
48}
49
50std::optional<BinaryBuf>
51key_from_passphrase(const std::string &password,
52 const mtx::secret_storage::AesHmacSha2KeyDescription &parameters)
53{
54 if (!parameters.passphrase)
55 throw std::invalid_argument("no passphrase to derive key from");
56 if (parameters.passphrase->algorithm != "m.pbkdf2")
57 throw std::invalid_argument("invalid pbkdf algorithm");
58 auto decryptionKey = PBKDF2_HMAC_SHA_512(pass: password,
59 salt: to_binary_buf(str: parameters.passphrase->salt),
60 iterations: parameters.passphrase->iterations,
61 keylen: parameters.passphrase->bits / 8);
62
63 // verify key
64 using namespace mtx::crypto;
65 auto testKeys = HKDF_SHA256(key: decryptionKey, salt: BinaryBuf(32, 0), info: BinaryBuf{});
66
67 auto encrypted = AES_CTR_256_Encrypt(
68 plaintext: std::string(32, '\0'), aes256Key: testKeys.aes, iv: to_binary_buf(str: base642bin(b64: parameters.iv)));
69
70 auto mac = HMAC_SHA256(hmacKey: testKeys.mac, data: encrypted);
71 if (mac != to_binary_buf(str: base642bin(b64: parameters.mac))) {
72 mtx::utils::log::log()->debug(
73 fmt: "mac mismatch: {} != {}", args: bin2base64(bin: to_string(buf: mac)), args: parameters.mac);
74 return std::nullopt;
75 }
76
77 return decryptionKey;
78}
79
80std::optional<BinaryBuf>
81key_from_recoverykey(const std::string &recoverykey,
82 const mtx::secret_storage::AesHmacSha2KeyDescription &parameters)
83{
84 auto tempKey = to_binary_buf(str: base582bin(bin: recoverykey));
85
86 if (tempKey.size() < 3 || tempKey[0] != 0x8b || tempKey[1] != 0x01)
87 return std::nullopt;
88
89 uint8_t parity = 0;
90 for (auto byte = tempKey.begin(); byte != tempKey.end() - 1; ++byte)
91 parity ^= *byte;
92
93 if (parity != tempKey.back())
94 return std::nullopt;
95
96 auto decryptionKey = BinaryBuf(tempKey.begin() + 2, tempKey.end() - 1);
97
98 // verify key
99 using namespace mtx::crypto;
100 auto testKeys = HKDF_SHA256(key: decryptionKey, salt: BinaryBuf(32, 0), info: BinaryBuf{});
101
102 auto encrypted = AES_CTR_256_Encrypt(
103 plaintext: std::string(32, '\0'), aes256Key: testKeys.aes, iv: to_binary_buf(str: base642bin(b64: parameters.iv)));
104
105 auto mac = HMAC_SHA256(hmacKey: testKeys.mac, data: encrypted);
106 if (mac != to_binary_buf(str: base642bin(b64: parameters.mac))) {
107 mtx::utils::log::log()->debug(
108 fmt: "mac mismatch: {} != {}", args: bin2base64(bin: to_string(buf: mac)), args: parameters.mac);
109 return std::nullopt;
110 }
111
112 return decryptionKey;
113}
114
115std::string
116key_to_recoverykey(const BinaryBuf &key)
117{
118 auto buf = BinaryBuf(key.size() + 3);
119 buf[0] = 0x8b;
120 buf[1] = 0x01;
121 std::copy(first: begin(cont: key), last: end(cont: key), result: begin(cont&: buf) + 2);
122
123 uint8_t parity = buf[0] ^ buf[1];
124 for (uint8_t b : key)
125 parity ^= b;
126 buf.back() = parity;
127
128 return bin2base58(bin: to_string(buf));
129}
130
131std::string
132decrypt(const mtx::secret_storage::AesHmacSha2EncryptedData &data,
133 const BinaryBuf &decryptionKey,
134 const std::string &key_name)
135{
136 auto keys = HKDF_SHA256(key: decryptionKey, salt: BinaryBuf(32, 0), info: to_binary_buf(str: key_name));
137 auto keyMac = HMAC_SHA256(hmacKey: keys.mac, data: to_binary_buf(str: base642bin(b64: data.ciphertext)));
138
139 if (keyMac != to_binary_buf(str: base642bin(b64: data.mac))) {
140 mtx::utils::log::log()->debug(
141 fmt: "mac mismatch: {} != {}", args: bin2base64(bin: to_string(buf: keyMac)), args: data.mac);
142 return "";
143 }
144
145 auto decryptedSecret = AES_CTR_256_Decrypt(
146 ciphertext: base642bin(b64: data.ciphertext), aes256Key: keys.aes, iv: to_binary_buf(str: base642bin(b64: data.iv)));
147
148 return to_string(buf: decryptedSecret);
149}
150
151mtx::secret_storage::AesHmacSha2EncryptedData
152encrypt(const std::string &data, const BinaryBuf &decryptionKey, const std::string &key_name)
153{
154 mtx::secret_storage::AesHmacSha2EncryptedData encrypted{};
155 auto iv = compatible_iv(incompatible_iv: create_buffer(nbytes: 16));
156 encrypted.iv = bin2base64(bin: to_string(buf: iv));
157
158 auto keys = HKDF_SHA256(key: decryptionKey, salt: BinaryBuf(32, 0), info: to_binary_buf(str: key_name));
159
160 auto ciphertext = AES_CTR_256_Encrypt(plaintext: data, aes256Key: keys.aes, iv);
161 encrypted.ciphertext = bin2base64(bin: to_string(buf: ciphertext));
162 encrypted.mac = bin2base64(bin: to_string(buf: HMAC_SHA256(hmacKey: keys.mac, data: ciphertext)));
163
164 return encrypted;
165}
166
167HkdfKeys
168HKDF_SHA256(const BinaryBuf &key, const BinaryBuf &salt, const BinaryBuf &info)
169{
170 BinaryBuf buf(64);
171 EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, e: nullptr);
172
173 if (EVP_PKEY_derive_init(ctx: pctx) <= 0) {
174 EVP_PKEY_CTX_free(ctx: pctx);
175 throw std::runtime_error("HKDF: failed derive init");
176 }
177 if (EVP_PKEY_CTX_set_hkdf_md(ctx: pctx, md: EVP_sha256()) <= 0) {
178 EVP_PKEY_CTX_free(ctx: pctx);
179 throw std::runtime_error("HKDF: failed to set digest");
180 }
181 if (EVP_PKEY_CTX_set1_hkdf_salt(ctx: pctx, salt: salt.data(), saltlen: (int)salt.size()) <= 0) {
182 EVP_PKEY_CTX_free(ctx: pctx);
183 throw std::runtime_error("HKDF: failed to set salt");
184 }
185 if (EVP_PKEY_CTX_set1_hkdf_key(ctx: pctx, key: key.data(), keylen: (int)key.size()) <= 0) {
186 EVP_PKEY_CTX_free(ctx: pctx);
187 throw std::runtime_error("HKDF: failed to set key");
188 }
189 if (EVP_PKEY_CTX_add1_hkdf_info(ctx: pctx, info: info.data(), infolen: (int)info.size()) <= 0) {
190 EVP_PKEY_CTX_free(ctx: pctx);
191 throw std::runtime_error("HKDF: failed to set info");
192 }
193
194 std::size_t outlen = buf.size();
195 if (EVP_PKEY_derive(ctx: pctx, key: buf.data(), keylen: &outlen) <= 0) {
196 EVP_PKEY_CTX_free(ctx: pctx);
197 throw std::runtime_error("HKDF: failed derive");
198 }
199
200 EVP_PKEY_CTX_free(ctx: pctx);
201
202 if (outlen != 64)
203 throw std::runtime_error("Invalid HKDF size!");
204
205 BinaryBuf macKey(buf.begin() + 32, buf.end());
206 buf.resize(new_size: 32);
207
208 return {.aes: std::move(buf), .mac: std::move(macKey)};
209}
210
211BinaryBuf
212compatible_iv(BinaryBuf incompatible_iv)
213{
214 // need to set bit 63 to 0
215 // Element and everyone else seems to be counting bytes from the back, i.e. iv_data[15] is
216 // the last byte. So we need to clear byte 15 - 63%8 = 15 - 7 = 8, the highest bit, 1 << 7
217 // see:
218 // https://github.com/matrix-org/matrix-js-sdk/blob/529fe93ab14b93c515e9ab0d0277c1942a5d73c5/src/crypto/aes.ts#L144
219 uint8_t *data = incompatible_iv.data();
220 constexpr std::uint8_t mask = static_cast<std::uint8_t>(~(1U << (63 / 8)));
221 data[15 - 63 % 8] &= mask;
222 return incompatible_iv;
223}
224
225BinaryBuf
226AES_CTR_256_Encrypt(const std::string &plaintext, const BinaryBuf &aes256Key, BinaryBuf iv)
227{
228 EVP_CIPHER_CTX *ctx;
229
230 int len;
231
232 int ciphertext_len;
233
234 // The ciphertext expand up to block size, which is 128 for AES256
235 BinaryBuf encrypted = compatible_iv(incompatible_iv: create_buffer(nbytes: plaintext.size() + AES_BLOCK_SIZE));
236
237 /* Create and initialise the context */
238 if (ctx = EVP_CIPHER_CTX_new(); !ctx) {
239 // handleErrors();
240 }
241
242 if (1 != EVP_EncryptInit_ex(ctx, cipher: EVP_aes_256_ctr(), impl: nullptr, key: aes256Key.data(), iv: iv.data())) {
243 // handleErrors();
244 }
245
246 /* Provide the message to be encrypted, and obtain the encrypted output.
247 * EVP_EncryptUpdate can be called multiple times if necessary
248 */
249 if (1 != EVP_EncryptUpdate(ctx,
250 out: encrypted.data(),
251 outl: &len,
252 in: reinterpret_cast<const unsigned char *>(&plaintext.c_str()[0]),
253 inl: (int)plaintext.size())) {
254 // handleErrors();
255 }
256 ciphertext_len = len;
257
258 /* Finalise the encryption. Further ciphertext bytes may be written at
259 * this stage.
260 */
261 if (1 != EVP_EncryptFinal_ex(ctx, out: encrypted.data() + len, outl: &len)) {
262 // handleErrors();
263 }
264
265 ciphertext_len += len;
266 encrypted.resize(new_size: ciphertext_len);
267
268 /* Clean up */
269 EVP_CIPHER_CTX_free(c: ctx);
270
271 return encrypted;
272}
273
274BinaryBuf
275AES_CTR_256_Decrypt(const std::string &ciphertext, const BinaryBuf &aes256Key, BinaryBuf iv)
276{
277 EVP_CIPHER_CTX *ctx;
278
279 int len;
280
281 int plaintext_len;
282
283 BinaryBuf decrypted = create_buffer(nbytes: ciphertext.size());
284
285 /* Create and initialise the context */
286 if (ctx = EVP_CIPHER_CTX_new(); !ctx) {
287 // handleErrors();
288 }
289
290 /* Initialise the decryption operation. IMPORTANT - ensure you use a key
291 * and IV size appropriate for your cipher
292 * In this example we are using 256 bit AES (i.e. a 256 bit key). The
293 * IV size for *most* modes is the same as the block size. For AES this
294 * is 128 bits */
295 if (1 != EVP_DecryptInit_ex(ctx, cipher: EVP_aes_256_ctr(), impl: nullptr, key: aes256Key.data(), iv: iv.data())) {
296 // handleErrors();
297 }
298
299 /* Provide the message to be decrypted, and obtain the plaintext output.
300 * EVP_DecryptUpdate can be called multiple times if necessary
301 */
302 if (1 != EVP_DecryptUpdate(ctx,
303 out: decrypted.data(),
304 outl: &len,
305 in: reinterpret_cast<const unsigned char *>(&ciphertext.data()[0]),
306 inl: (int)ciphertext.size())) {
307 // handleErrors();
308 }
309 plaintext_len = len;
310
311 /* Finalise the decryption. Further plaintext bytes may be written at
312 * this stage.
313 */
314 if (1 != EVP_DecryptFinal_ex(ctx, outm: decrypted.data() + len, outl: &len)) {
315 // handleErrors();
316 }
317 plaintext_len += len;
318 decrypted.resize(new_size: plaintext_len);
319
320 /* Clean up */
321 EVP_CIPHER_CTX_free(c: ctx);
322
323 return decrypted;
324}
325
326std::string
327CURVE25519_public_key_from_private(const BinaryBuf &privateKey)
328{
329 auto ctx = create_olm_object<PkDecryptionObject>();
330
331 BinaryBuf pubkey(::olm_pk_key_length());
332
333 ::olm_pk_key_from_private(
334 decryption: ctx.get(), pubkey: pubkey.data(), pubkey_length: pubkey.size(), privkey: privateKey.data(), privkey_length: privateKey.size());
335
336 return to_string(buf: pubkey);
337}
338
339CURVE25519_AES_SHA2_Encrypted
340CURVE25519_AES_SHA2_Encrypt(const std::string &plaintext, const std::string &base64_publicKey)
341{
342 auto ctx = create_olm_object<PkEncryptionObject>();
343
344 ::olm_pk_encryption_set_recipient_key(
345 encryption: ctx.get(), public_key: base64_publicKey.data(), public_key_length: base64_publicKey.size());
346
347 BinaryBuf ephemeral(::olm_pk_key_length());
348 BinaryBuf mac(::olm_pk_mac_length(encryption: ctx.get()));
349 BinaryBuf ciphertext(::olm_pk_ciphertext_length(encryption: ctx.get(), plaintext_length: plaintext.size()));
350 BinaryBuf randomBuf = create_buffer(nbytes: ::olm_pk_encrypt_random_length(encryption: ctx.get()));
351 auto encrypted_size = ::olm_pk_encrypt(encryption: ctx.get(),
352 plaintext: plaintext.data(),
353 plaintext_length: plaintext.size(),
354 ciphertext: ciphertext.data(),
355 ciphertext_length: ciphertext.size(),
356 mac: mac.data(),
357 mac_length: mac.size(),
358 ephemeral_key: ephemeral.data(),
359 ephemeral_key_size: ephemeral.size(),
360 random: randomBuf.data(),
361 random_length: randomBuf.size());
362
363 if (encrypted_size != olm_error()) {
364 CURVE25519_AES_SHA2_Encrypted val;
365 val.ciphertext = to_string(buf: ciphertext);
366 val.mac = to_string(buf: mac);
367 val.ephemeral = to_string(buf: ephemeral);
368 return val;
369 } else
370 throw olm_exception(__func__, ctx.get());
371}
372
373std::string
374CURVE25519_AES_SHA2_Decrypt(std::string base64_ciphertext,
375 const BinaryBuf &privateKey,
376 const std::string &ephemeral,
377 const std::string &mac)
378{
379 auto ctx = create_olm_object<PkDecryptionObject>();
380
381 BinaryBuf pubkey(::olm_pk_key_length());
382
383 ::olm_pk_key_from_private(
384 decryption: ctx.get(), pubkey: pubkey.data(), pubkey_length: pubkey.size(), privkey: privateKey.data(), privkey_length: privateKey.size());
385
386 std::string plaintext(olm_pk_max_plaintext_length(decryption: ctx.get(), ciphertext_length: base64_ciphertext.size()), '\0');
387 std::size_t decrypted_size = ::olm_pk_decrypt(decryption: ctx.get(),
388 ephemeral_key: ephemeral.data(),
389 ephemeral_key_length: ephemeral.size(),
390 mac: mac.data(),
391 mac_length: mac.size(),
392 ciphertext: base64_ciphertext.data(),
393 ciphertext_length: base64_ciphertext.size(),
394 plaintext: plaintext.data(),
395 max_plaintext_length: plaintext.size());
396
397 if (decrypted_size != olm_error()) {
398 plaintext.resize(n: decrypted_size);
399 return plaintext;
400 } else
401 throw olm_exception(__func__, ctx.get());
402}
403
404mtx::responses::backup::EncryptedSessionData
405encrypt_session(const mtx::responses::backup::SessionData &data, const std::string &publicKey)
406{
407 mtx::responses::backup::EncryptedSessionData d;
408
409 auto temp = CURVE25519_AES_SHA2_Encrypt(plaintext: nlohmann::json(data).dump(), base64_publicKey: publicKey);
410 d.ciphertext = std::move(temp.ciphertext);
411 d.mac = std::move(temp.mac);
412 d.ephemeral = std::move(temp.ephemeral);
413
414 return d;
415}
416
417mtx::responses::backup::SessionData
418decrypt_session(const mtx::responses::backup::EncryptedSessionData &data,
419 const BinaryBuf &privateKey)
420{
421 return nlohmann::json::parse(
422 i: CURVE25519_AES_SHA2_Decrypt(base64_ciphertext: data.ciphertext, privateKey, ephemeral: data.ephemeral, mac: data.mac))
423 .get<mtx::responses::backup::SessionData>();
424}
425
426std::string
427sha256(const std::string &data)
428{
429 bool success = false;
430 std::string hashed;
431
432#if OPENSSL_VERSION_NUMBER < 0x10100000L
433 EVP_MD_CTX *context = EVP_MD_CTX_create();
434#else
435 EVP_MD_CTX *context = EVP_MD_CTX_new();
436#endif
437
438 if (context != nullptr) {
439 if (EVP_DigestInit_ex(ctx: context, type: EVP_sha256(), impl: nullptr)) {
440 if (EVP_DigestUpdate(ctx: context, d: data.c_str(), cnt: data.length())) {
441 unsigned char hash[EVP_MAX_MD_SIZE];
442 unsigned int lengthOfHash = 0;
443
444 if (EVP_DigestFinal_ex(ctx: context, md: hash, s: &lengthOfHash)) {
445 hashed = std::string(hash, hash + lengthOfHash);
446 success = true;
447 }
448 }
449 }
450
451#if OPENSSL_VERSION_NUMBER < 0x10100000L
452 EVP_MD_CTX_destroy(context);
453#else
454 EVP_MD_CTX_free(ctx: context);
455#endif
456 }
457
458 if (success)
459 return hashed;
460 throw std::runtime_error("sha256 failed!");
461}
462
463BinaryBuf
464decrypt_file(const std::string &ciphertext, const mtx::crypto::EncryptedFile &encryption_info)
465{
466 if (encryption_info.v != "v2")
467 throw std::invalid_argument("Unsupported encrypted file version");
468
469 if (encryption_info.key.kty != "oct")
470 throw std::invalid_argument("Unsupported key type");
471
472 if (encryption_info.key.alg != "A256CTR")
473 throw std::invalid_argument("Unsupported algorithm");
474
475 // Be careful, the key should be urlsafe and unpadded, the iv and sha only need to
476 // be unpadded
477 if (bin2base64_unpadded(bin: sha256(data: ciphertext)) != encryption_info.hashes.at(k: "sha256"))
478 throw std::invalid_argument(
479 "sha256 of encrypted file does not match the ciphertext, expected '" +
480 bin2base64_unpadded(bin: sha256(data: ciphertext)) + "', got '" +
481 encryption_info.hashes.at(k: "sha256") + "'");
482
483 return AES_CTR_256_Decrypt(ciphertext,
484 aes256Key: to_binary_buf(str: base642bin_urlsafe_unpadded(b64: encryption_info.key.k)),
485 iv: to_binary_buf(str: base642bin_unpadded(b64: encryption_info.iv)));
486}
487
488std::pair<BinaryBuf, mtx::crypto::EncryptedFile>
489encrypt_file(const std::string &plaintext)
490{
491 mtx::crypto::EncryptedFile encryption_info;
492
493 // iv has to be 16 bytes, key 32!
494 BinaryBuf key = create_buffer(nbytes: 32);
495 BinaryBuf iv = create_buffer(nbytes: 16);
496 constexpr std::uint8_t mask = static_cast<std::uint8_t>(~(1U << (63 / 8)));
497 iv[15 - 63 % 8] &= mask;
498
499 // Counter should be 0 in v1.1 of the spec...
500 for (std::size_t i = 8; i < 16; i++)
501 iv[i] = 0;
502
503 BinaryBuf cyphertext = AES_CTR_256_Encrypt(plaintext, aes256Key: key, iv);
504
505 // Be careful, the key should be urlsafe and unpadded, the iv and sha only need to
506 // be unpadded
507 JWK web_key;
508 web_key.ext = true;
509 web_key.kty = "oct";
510 web_key.key_ops = {"encrypt", "decrypt"};
511 web_key.alg = "A256CTR";
512 web_key.k = bin2base64_urlsafe_unpadded(bin: to_string(buf: key));
513 web_key.ext = true;
514
515 encryption_info.key = web_key;
516 encryption_info.iv = bin2base64_unpadded(bin: to_string(buf: iv));
517 encryption_info.hashes["sha256"] = bin2base64_unpadded(bin: sha256(data: to_string(buf: cyphertext)));
518 encryption_info.v = "v2";
519
520 return std::make_pair(x&: cyphertext, y&: encryption_info);
521}
522
523template<typename T>
524void
525remove_substrs(std::basic_string<T> &s, const std::basic_string<T> &p)
526{
527 auto n = p.length();
528
529 for (auto i = s.find(p); i != std::basic_string<T>::npos; i = s.find(p))
530 s.erase(i, n);
531}
532
533std::string
534unpack_key_file(const std::string &data)
535{
536 std::string unpacked(data);
537 remove_substrs(s&: unpacked, p: HEADER_LINE);
538
539 remove_substrs(s&: unpacked, p: TRAILER_LINE);
540
541 remove_substrs(s&: unpacked, p: std::string("\n"));
542
543 return unpacked;
544}
545
546BinaryBuf
547HMAC_SHA256(const BinaryBuf &hmacKey, const BinaryBuf &data)
548{
549 unsigned int len = SHA256_DIGEST_LENGTH;
550 unsigned char digest[SHA256_DIGEST_LENGTH];
551 HMAC(evp_md: EVP_sha256(), key: hmacKey.data(), key_len: (int)hmacKey.size(), data: data.data(), data_len: data.size(), md: digest, md_len: &len);
552 BinaryBuf output(digest, digest + SHA256_DIGEST_LENGTH);
553 return output;
554}
555
556void
557uint8_to_uint32(uint8_t b[4], uint32_t &u32)
558{
559 u32 = std::uint32_t{b[0]} << 24 | std::uint32_t{b[1]} << 16 | std::uint32_t{b[2]} << 8 |
560 std::uint32_t{b[3]};
561}
562
563void
564uint32_to_uint8(uint8_t b[4], uint32_t u32)
565{
566 b[3] = (uint8_t)u32;
567 b[2] = (uint8_t)(u32 >>= 8);
568 b[1] = (uint8_t)(u32 >>= 8);
569 b[0] = (uint8_t)(u32 >> 8);
570}
571} // namespace crypto
572} // namespace mtx
573