Botcraft 1.21.4
Loading...
Searching...
No Matches
Authentifier.cpp
Go to the documentation of this file.
1#include <asio/ip/tcp.hpp>
2#include <asio/connect.hpp>
3#include <asio/streambuf.hpp>
4#include <asio/read_until.hpp>
5#include <asio/read.hpp>
6
7#ifdef USE_ENCRYPTION
8#include <asio/ssl.hpp>
9#include <openssl/sha.h>
10#endif
11
12#include <iomanip>
13#include <fstream>
14#include <sstream>
15#include <iostream>
16
19
21
22using namespace ProtocolCraft;
23
24namespace Botcraft
25{
26 const std::string Authentifier::cached_credentials_path = "botcraft_cached_credentials.json";
27 const std::string Authentifier::botcraft_app_id = "a0ad834d-e78a-4881-87f6-390aa0f4b283";
29 { "msa", {
30 { "access_token", nullptr },
31 { "expires_date", nullptr },
32 { "refresh_token", nullptr }
33 }},
34 { "name", nullptr },
35 { "id", nullptr },
36 { "mc_token", nullptr },
37 { "expires_date", nullptr },
38#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
39 { "certificates", {
40 { "private_key", nullptr },
41 { "public_key", nullptr },
42 { "expires_date", nullptr },
43 { "signature_v1", nullptr },
44 { "signature_v2", nullptr }
45 }}
46#endif
47 };
48
50 {
52#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
53 key_timestamp = 0;
54 rnd = std::mt19937(static_cast<unsigned int>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count()));
55#endif
56 }
57
62
63 const bool Authentifier::AuthMicrosoft(const std::string& login)
64 {
65#ifndef USE_ENCRYPTION
66 return false;
67#else
68 const Json::Value cached = GetCachedCredentials(login);
69 if (!cached.contains("mc_token") || !cached["mc_token"].is_string() ||
70 !cached.contains("expires_date") || !cached["expires_date"].is_number() ||
71 !cached.contains("name") || !cached["name"].is_string() ||
72 !cached.contains("id") || !cached["id"].is_string())
73 {
74 LOG_WARNING("Missing or malformed cached credentials for Microsoft account, starting auth flow...");
75 }
76 else if (IsTokenExpired(cached["expires_date"].get<long long int>()))
77 {
78 LOG_INFO("Cached Minecraft token for Microsoft account expired, starting auth flow...");
79 }
80 else
81 {
82 mc_access_token = cached["mc_token"].get_string();
83 player_display_name = cached["name"].get_string();
84 mc_player_uuid = cached["id"].get_string();
86 LOG_INFO("Cached Minecraft token for Microsoft account still valid.");
87
88#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
89 LOG_INFO("Getting player certificates...");
91 if (private_key.empty() || public_key.empty() || key_signature.empty())
92 {
93 LOG_ERROR("Unable to get player certificates");
94 return false;
95 }
96 LOG_INFO("Player certificates obtained!");
97#endif
98 return true;
99 }
100
101 // This auth flow is directly inspired from https://github.com/maxsupermanhd/go-mc-ms-auth
102 LOG_INFO("Trying to get Microsoft access token...");
103 const std::string msa_token = GetMSAToken(login);
104 if (msa_token.empty())
105 {
106 LOG_ERROR("Unable to get a microsoft auth token");
107 return false;
108 }
109
110 LOG_INFO("Trying to get XBL token...");
111 const std::string xbl_token = GetXBLToken(msa_token);
112 if (xbl_token.empty())
113 {
114 LOG_ERROR("Unable to get a XBL token");
115 return false;
116 }
117 LOG_INFO("XBL token obtained!");
118
119 LOG_INFO("Trying to get XSTS token...");
120 const auto [xsts_token, xsts_userhash] = GetXSTSToken(xbl_token);
121 if (xsts_token.empty())
122 {
123 LOG_ERROR("Unable to get a XSTS token");
124 return false;
125 }
126 LOG_INFO("XSTS token obtained!");
127
128 LOG_INFO("Trying to get MC token...");
129 mc_access_token = GetMCToken(login, xsts_token, xsts_userhash);
130 if (mc_access_token.empty())
131 {
132 LOG_ERROR("Unable to get a MC token");
133 return false;
134 }
135 LOG_INFO("MC token obtained! Almost there...");
136
137
138 // We assume you're using an account owning minecraft so
139 // we don't check (and also a bit because it's complicated)
140 // If you don't, Botcraft won't work on online mode.
141 // But you can buy yourself a copy of the game:
142 // https://www.minecraft.net/get-minecraft
143 LOG_INFO("Assuming the account owns Minecraft...");
144
145 LOG_INFO("Trying to get MC profile...");
147 if (mc_player_uuid.empty())
148 {
149 LOG_ERROR("Unable to get a MC profile");
150 return false;
151 }
153 LOG_INFO("MC profile obtained!");
154
155#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
156 LOG_INFO("Getting player certificates...");
158 if (private_key.empty() || public_key.empty() || key_signature.empty())
159 {
160 LOG_ERROR("Unable to get player certificates");
161 return false;
162 }
163 LOG_INFO("Player certificates obtained!");
164#endif
165
166 LOG_INFO("Authentication completed!");
167
168 return true;
169#endif
170 }
171
172 const bool Authentifier::JoinServer(const std::string& server_id, const std::vector<unsigned char>& shared_secret, const std::vector<unsigned char>& public_key) const
173 {
174#ifndef USE_ENCRYPTION
175 return false;
176#else
177 if (mc_player_uuid.empty())
178 {
179 LOG_ERROR("Trying to join a server before authentication");
180 return false;
181 }
182
183 SHA_CTX sha_context;
184 SHA1_Init(&sha_context);
185
186 SHA1_Update(&sha_context, server_id.c_str(), server_id.length());
187 SHA1_Update(&sha_context, shared_secret.data(), shared_secret.size());
188 SHA1_Update(&sha_context, public_key.data(), public_key.size());
189
190 std::vector<unsigned char> digest(SHA_DIGEST_LENGTH);
191 SHA1_Final(digest.data(), &sha_context);
192
193 // Compute minecraft special hexdigest (see https://wiki.vg/Protocol_Encryption#Client)
194
195 bool is_negative = digest[0] & (1 << 7);
196
197 // Take two complement
198 if (is_negative)
199 {
200 // Revert bits
201 for (int i = 0; i < digest.size(); ++i)
202 {
203 digest[i] = ~digest[i];
204 }
205
206 // add 1
207 int position = static_cast<int>(digest.size()) - 1;
208 while (digest[position] == 255 && position > 0)
209 {
210 digest[position] = 0;
211 position -= 1;
212 }
213 digest[position] += 1;
214 }
215
216 // Get hex representation
217 std::stringstream ss;
218 for (int i = 0; i < digest.size(); ++i)
219 {
220 ss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(digest[i] & 0xFF);
221 }
222
223 std::string server_hash = ss.str();
224 // Remove leading 0
225 const size_t start = server_hash.find_first_not_of('0');
226 if (start != std::string::npos)
227 {
228 server_hash = server_hash.substr(start);
229 }
230 else
231 {
232 server_hash = "";
233 }
234
235 if (is_negative)
236 {
237 server_hash = "-" + server_hash;
238 }
239
240 // Prepare the data to send to the server
241 const Json::Value data = {
242 { "accessToken", mc_access_token },
243 { "selectedProfile", mc_player_uuid },
244 { "serverId", server_hash}
245 };
246
247 const WebRequestResponse post_response = POSTRequest("sessionserver.mojang.com", "/session/minecraft/join",
248 "application/json; charset=utf-8", "*/*", "", data.Dump());
249
250 if (post_response.status_code != 204)
251 {
252 LOG_ERROR("Response returned with status code " << post_response.status_code
253 << " (" << post_response.status_message << ") during server join:\n"
254 << post_response.response.Dump(4));
255 return false;
256 }
257
258 return true;
259#endif
260 }
261
262 const std::string& Authentifier::GetPlayerDisplayName() const
263 {
264 return player_display_name;
265 }
266
267 const std::array<unsigned char, 16>& Authentifier::GetPlayerUUID() const
268 {
270 }
271
272#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
273 const std::string& Authentifier::GetPrivateKey() const
274 {
275 return private_key;
276 }
277
278 const std::string& Authentifier::GetPublicKey() const
279 {
280 return public_key;
281 }
282
283 const std::string& Authentifier::GetKeySignature() const
284 {
285 return key_signature;
286 }
287
288 const long long int Authentifier::GetKeyTimestamp() const
289 {
290 return key_timestamp;
291 }
292
293#if PROTOCOL_VERSION == 759 /* 1.19 */
294 const std::vector<unsigned char> Authentifier::GetMessageSignature(const std::string& message,
295 long long int& salt, long long int& timestamp)
296#elif PROTOCOL_VERSION == 760 /* 1.19.1/2 */
297 const std::vector<unsigned char> Authentifier::GetMessageSignature(const std::string& message,
298 const std::vector<unsigned char>& previous_signature, const std::vector<LastSeenMessagesEntry>& last_seen,
299 long long int& salt, long long int& timestamp)
300#else
301 const std::vector<unsigned char> Authentifier::GetMessageSignature(const std::string& message,
302 const int message_sent_index, const UUID& chat_session_uuid,
303 const std::vector<std::vector<unsigned char>>& last_seen,
304 long long int& salt, long long int& timestamp)
305#endif
306 {
307#ifndef USE_ENCRYPTION
308 LOG_ERROR("Trying to compute message signature while botcraft was compiled without USE_ENCRYPTION.");
309 return {};
310#else
311 if (mc_player_uuid.empty() || private_key.empty())
312 {
313 LOG_ERROR("Trying to join a server before authentication");
314 return {};
315 }
316
317 // Generate random salt and timestamp
318 salt = std::uniform_int_distribution<long long int>(std::numeric_limits<long long int>::min(), std::numeric_limits<long long int>::max())(rnd);
319 timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
320 std::array<unsigned char, 8> salt_bytes;
321 std::array<unsigned char, 8> timestamp_bytes;
322
323 for (int i = 0; i < 8; ++i)
324 {
325 salt_bytes[i] = static_cast<unsigned char>((salt >> (8 * (7 - i))) & 0xFF);
326 // Signature is computed with seconds not milliseconds
327 timestamp_bytes[i] = static_cast<unsigned char>(((timestamp / 1000) >> (8 * (7 - i))) & 0xFF);
328 }
329
330 std::array<unsigned char, SHA256_DIGEST_LENGTH> signature_hash;
331#if PROTOCOL_VERSION == 759 /* 1.19 */
332 // Signature is computed with a dumb json instead of the actual string
333 const std::string jsoned_message = "{\"text\":\"" + message + "\"}";
334
335 // Compute hash
336 SHA256_CTX sha256;
337 SHA256_Init(&sha256);
338 SHA256_Update(&sha256, salt_bytes.data(), salt_bytes.size());
339 SHA256_Update(&sha256, mc_player_uuid_bytes.data(), mc_player_uuid_bytes.size());
340 SHA256_Update(&sha256, timestamp_bytes.data(), timestamp_bytes.size());
341 SHA256_Update(&sha256, jsoned_message.data(), jsoned_message.size());
342#elif PROTOCOL_VERSION == 760 /* 1.19.1/2 */
343 const unsigned char const_byte_70 = 70;
344
345 // Body hash
346 std::array<unsigned char, SHA256_DIGEST_LENGTH> body_hash;
347 SHA256_CTX body_sha256;
348 SHA256_Init(&body_sha256);
349 SHA256_Update(&body_sha256, salt_bytes.data(), salt_bytes.size());
350 SHA256_Update(&body_sha256, timestamp_bytes.data(), timestamp_bytes.size());
351 SHA256_Update(&body_sha256, message.data(), message.size());
352 SHA256_Update(&body_sha256, &const_byte_70, 1);
353 // All previously seen messages
354 for (int i = 0; i < last_seen.size(); ++i)
355 {
356 SHA256_Update(&body_sha256, &const_byte_70, 1);
357 SHA256_Update(&body_sha256, last_seen[i].GetProfileId().data(), last_seen[i].GetProfileId().size());
358 SHA256_Update(&body_sha256, last_seen[i].GetLastSignature().data(), last_seen[i].GetLastSignature().size());
359 }
360 SHA256_Final(body_hash.data(), &body_sha256);
361
362
363 // Signature hash
364 SHA256_CTX sha256;
365 SHA256_Init(&sha256);
366 if (!previous_signature.empty())
367 {
368 SHA256_Update(&sha256, previous_signature.data(), previous_signature.size());
369 }
370 SHA256_Update(&sha256, mc_player_uuid_bytes.data(), mc_player_uuid_bytes.size());
371 SHA256_Update(&sha256, body_hash.data(), body_hash.size());
372#else
373 std::array<unsigned char, 4> bytes_1_big_endian;
374 std::array<unsigned char, 4> message_sent_index_bytes;
375 std::array<unsigned char, 4> message_size_bytes;
376 std::array<unsigned char, 4> last_seen_size_bytes;
377
378 for (int i = 0; i < 4; ++i)
379 {
380 bytes_1_big_endian[i] = static_cast<unsigned char>((1 >> (8 * (3 - i))) & 0xFF);
381 message_sent_index_bytes[i] = static_cast<unsigned char>((message_sent_index >> (8 * (3 - i))) & 0xFF);
382 message_size_bytes[i] = static_cast<unsigned char>((static_cast<int>(message.size()) >> (8 * (3 - i))) & 0xFF);
383 last_seen_size_bytes[i] = static_cast<unsigned char>((static_cast<int>(last_seen.size()) >> (8 * (3 - i))) & 0xFF);
384 }
385
386 // Compute hash
387 SHA256_CTX sha256;
388 SHA256_Init(&sha256);
389 SHA256_Init(&sha256);
390 // Big endian (int)1
391 SHA256_Update(&sha256, bytes_1_big_endian.data(), bytes_1_big_endian.size());
392 // signed message link
393 SHA256_Update(&sha256, mc_player_uuid_bytes.data(), mc_player_uuid_bytes.size());
394 SHA256_Update(&sha256, chat_session_uuid.data(), chat_session_uuid.size());
395 SHA256_Update(&sha256, message_sent_index_bytes.data(), message_sent_index_bytes.size());
396 // signed message body
397 SHA256_Update(&sha256, salt_bytes.data(), salt_bytes.size());
398 SHA256_Update(&sha256, timestamp_bytes.data(), timestamp_bytes.size());
399 SHA256_Update(&sha256, message_size_bytes.data(), message_size_bytes.size());
400 SHA256_Update(&sha256, message.data(), message.size());
401 SHA256_Update(&sha256, last_seen_size_bytes.data(), last_seen_size_bytes.size());
402 for (size_t i = 0; i < last_seen.size(); ++i)
403 {
404 SHA256_Update(&sha256, last_seen[i].data(), last_seen[i].size());
405 }
406#endif
407 SHA256_Final(signature_hash.data(), &sha256);
408
409 // Extract signature key from PEM string
410 // TODO: store RSA key so we don't have to extract it every chat message?
411 // it's not that important as you're not supposed to spam chat hundreds of times per second anyway
412 RSA* rsa_signature = nullptr;
413 const char* c_string = private_key.c_str();
414 BIO* keybio = BIO_new_mem_buf((void*)c_string, -1);
415 rsa_signature = PEM_read_bio_RSAPrivateKey(keybio, &rsa_signature, NULL, NULL);
416 BIO_free(keybio);
417
418 // Compute signature
419 const int rsa_signature_size = RSA_size(rsa_signature);
420 std::vector<unsigned char> signature(rsa_signature_size);
421 unsigned int signature_size;
422 RSA_sign(NID_sha256, signature_hash.data(), static_cast<unsigned int>(signature_hash.size()), signature.data(), &signature_size, rsa_signature);
423 RSA_free(rsa_signature);
424 signature.resize(signature_size);
425
426 return signature;
427#endif
428 }
429#endif
430
432 {
433 for (int i = 0; i < 32; i += 2)
434 {
435 const std::string byte_str = mc_player_uuid.substr(i, 2);
436 mc_player_uuid_bytes[i / 2] = static_cast<unsigned char>(std::strtol(byte_str.c_str(), nullptr, 16));
437 }
438 }
439
440#ifdef USE_ENCRYPTION
442 {
443 std::ifstream cache_file(cached_credentials_path);
444 if (!cache_file.good())
445 {
446 return {};
447 }
448 Json::Value cached_content;
449 cache_file >> cached_content;
450 cache_file.close();
451
452 return cached_content;
453 }
454
455 Json::Value Authentifier::GetCachedCredentials(const std::string& login) const
456 {
457 const Json::Value profiles = GetCachedProfiles();
458
459 if (profiles.size() > 0 &&
460 profiles.contains(login) &&
461 profiles[login].is_object())
462 {
463 return profiles[login];
464 }
465
467 }
468
469 const std::tuple<std::string, std::string, std::string> Authentifier::ExtractMCFromResponse(const Json::Value& response) const
470 {
471 if (response.contains("error"))
472 {
473 LOG_ERROR("Error trying to authenticate: " << response["errorMessage"].get_string());
474 return { "","","" };
475 }
476
477 if (!response.contains("accessToken"))
478 {
479 LOG_ERROR("Error trying to authenticate, no accessToken returned");
480 return { "","","" };
481 }
482
483 if (!response.contains("selectedProfile"))
484 {
485 LOG_ERROR("Error trying to authenticate, no selectedProfile item found");
486 return { "","","" };
487 }
488
489 const Json::Value& profile = response.get_object().at("selectedProfile");
490
491 if (!profile.contains("name"))
492 {
493 LOG_ERROR("Error trying to authenticate, no name in selected profile");
494 return { "","","" };
495 }
496
497 if (!profile.contains("id"))
498 {
499 LOG_ERROR("Error trying to authenticate, no id in selected profile");
500 return { "","","" };
501 }
502
503 return { response["accessToken"].get_string(), profile["name"].get_string(), profile["id"].get_string() };
504 }
505
506 const bool Authentifier::IsTokenExpired(const long long int& t) const
507 {
508 return t < std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
509 }
510
511 void Authentifier::WriteCacheFile(const Json::Value& profiles) const
512 {
513 std::ofstream cached_ofile(cached_credentials_path);
514 if (!cached_ofile.is_open())
515 {
516 return;
517 }
518 cached_ofile << profiles.Dump(4) << std::endl;
519 cached_ofile.close();
520 }
521
522 void Authentifier::UpdateCachedMSA(const std::string& login,
523 const std::string& access_token, const std::string& refresh_token,
524 const long long int& expiration) const
525 {
526 Json::Value profiles = GetCachedProfiles();
527
528 if (!profiles.contains(login))
529 {
530 profiles[login] = defaultCachedCredentials;
531 }
532
533 if (access_token.empty())
534 {
535 profiles[login]["msa"]["access_token"] = nullptr;
536 }
537 else
538 {
539 profiles[login]["msa"]["access_token"] = access_token;
540 }
541
542 if (refresh_token.empty())
543 {
544 profiles[login]["msa"]["refresh_token"] = nullptr;
545 }
546 else
547 {
548 profiles[login]["msa"]["refresh_token"] = refresh_token;
549 }
550
551 if (expiration == -1)
552 {
553 profiles[login]["msa"]["expires_date"] = nullptr;
554 }
555 else
556 {
557 profiles[login]["msa"]["expires_date"] = expiration;
558 }
559
560 WriteCacheFile(profiles);
561 }
562
563 void Authentifier::UpdateCachedMCToken(const std::string& login,
564 const std::string& mc_token, const long long int& expiration) const
565 {
566 Json::Value profiles = GetCachedProfiles();
567
568 if (!profiles.contains(login))
569 {
570 profiles[login] = defaultCachedCredentials;
571 }
572
573 if (mc_token.empty())
574 {
575 profiles[login]["mc_token"] = nullptr;
576 }
577 else
578 {
579 profiles[login]["mc_token"] = mc_token;
580 }
581
582 if (expiration == -1)
583 {
584 profiles[login]["expires_date"] = nullptr;
585 }
586 else
587 {
588 profiles[login]["expires_date"] = expiration;
589 }
590
591 WriteCacheFile(profiles);
592 }
593
594 void Authentifier::UpdateCachedMCProfile(const std::string& login, const std::string& name, const std::string& id) const
595 {
596 Json::Value profiles = GetCachedProfiles();
597
598 if (!profiles.contains(login))
599 {
600 profiles[login] = defaultCachedCredentials;
601 }
602
603 if (name.empty())
604 {
605 profiles[login]["name"] = nullptr;
606 }
607 else
608 {
609 profiles[login]["name"] = name;
610 }
611
612 if (id.empty())
613 {
614 profiles[login]["id"] = nullptr;
615 }
616 else
617 {
618 profiles[login]["id"] = id;
619 }
620
621 WriteCacheFile(profiles);
622 }
623
624#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
625 void Authentifier::UpdateCachedPlayerCertificates(const std::string& login, const std::string& private_k,
626 const std::string& public_k, const std::string& signature_v1,
627 const std::string& signature_v2, const long long int& expiration) const
628 {
629 Json::Value profiles = GetCachedProfiles();
630
631 if (!profiles.contains(login))
632 {
633 profiles[login] = defaultCachedCredentials;
634 }
635
636 if (private_k.empty())
637 {
638 profiles[login]["certificates"]["private_key"] = nullptr;
639 }
640 else
641 {
642 profiles[login]["certificates"]["private_key"] = private_k;
643 }
644
645 if (public_k.empty())
646 {
647 profiles[login]["certificates"]["public_key"] = nullptr;
648 }
649 else
650 {
651 profiles[login]["certificates"]["public_key"] = public_k;
652 }
653
654 if (signature_v1.empty())
655 {
656 profiles[login]["certificates"]["signature_v1"] = nullptr;
657 }
658 else
659 {
660 profiles[login]["certificates"]["signature_v1"] = signature_v1;
661 }
662
663 if (signature_v2.empty())
664 {
665 profiles[login]["certificates"]["signature_v2"] = nullptr;
666 }
667 else
668 {
669 profiles[login]["certificates"]["signature_v2"] = signature_v2;
670 }
671
672 if (expiration == -1)
673 {
674 profiles[login]["certificates"]["expires_date"] = nullptr;
675 }
676 else
677 {
678 profiles[login]["certificates"]["expires_date"] = expiration;
679 }
680
681 WriteCacheFile(profiles);
682 }
683#endif
684
685 const std::string Authentifier::GetMSAToken(const std::string& login) const
686 {
687 // Retrieve cached microsoft credentials
688 const Json::Value cached = GetCachedCredentials(login);
689
690 // In case there is something wrong in the cached data
691 if (!cached.contains("msa") || !cached["msa"].is_object() ||
692 !cached["msa"].contains("refresh_token") || !cached["msa"]["refresh_token"].is_string() ||
693 !cached["msa"].contains("access_token") || !cached["msa"]["access_token"].is_string() ||
694 !cached["msa"].contains("expires_date") || !cached["msa"]["expires_date"].is_number())
695 {
696 LOG_ERROR("Error trying to get cached Microsoft credentials");
697 UpdateCachedMSA(login, "", "", -1);
698 LOG_INFO("Starting authentication process...");
699 return MSAAuthDeviceFlow(login);
700 }
701
702 if (IsTokenExpired(cached["msa"]["expires_date"].get<long long int>()))
703 {
704 LOG_INFO("Refreshing Microsoft token...");
705 std::string refresh_data;
706 refresh_data += "client_id=" + botcraft_app_id;
707 refresh_data += "&refresh_token=" + cached["msa"]["refresh_token"].get_string();
708 refresh_data += "&grant_type=refresh_token";
709 refresh_data += "&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient";
710
711 const WebRequestResponse post_response = POSTRequest("login.live.com", "/oauth20_token.srf",
712 "application/x-www-form-urlencoded", "*/*", "", refresh_data);
713
714 // If refresh fails, remove the cache
715 // file and restart the whole auth flow
716 if (post_response.status_code != 200)
717 {
718 LOG_ERROR("Response returned with status code " << post_response.status_code
719 << " (" << post_response.status_message << ") during Microsoft token refresh:\n"
720 << post_response.response.Dump(4));
721 UpdateCachedMSA(login, "", "", -1);
722 LOG_WARNING("Failed to refresh token, starting Microsoft authentication process...");
723 return MSAAuthDeviceFlow(login);
724 }
725
726 const Json::Value& response = post_response.response;
727
728 if (!response.contains("expires_in"))
729 {
730 LOG_ERROR("Error trying to refresh Microsoft token, no expires_in in response");
731 UpdateCachedMSA(login, "", "", -1);
732 LOG_WARNING("Failed to refresh token, starting Microsoft authentication process...");
733 return MSAAuthDeviceFlow(login);
734 }
735
736 if (!response.contains("refresh_token"))
737 {
738 LOG_ERROR("Error trying to refresh microsoft token, no refresh_token in response");
739 UpdateCachedMSA(login, "", "", -1);
740 LOG_WARNING("Failed to refresh token, starting Microsoft authentication process...");
741 return MSAAuthDeviceFlow(login);
742 }
743
744 if (!response.contains("access_token"))
745 {
746 LOG_ERROR("Error trying to refresh microsoft token, no access_token in response");
747 UpdateCachedMSA(login, "", "", -1);
748 LOG_WARNING("Failed to refresh token, starting Microsoft authentication process...");
749 return MSAAuthDeviceFlow(login);
750 }
751
752 UpdateCachedMSA(login, response["access_token"].get_string(),
753 response["refresh_token"].get_string(),
754 response["expires_in"].get<long long int>() + std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count()
755 );
756
757 LOG_INFO("Cached Microsoft token refreshed");
758
759 return response["access_token"].get_string();
760 }
761
762 LOG_INFO("Microsoft token obtained from cache");
763
764 return cached["msa"]["access_token"].get_string();
765 }
766
767 const std::string Authentifier::MSAAuthDeviceFlow(const std::string& login) const
768 {
769 std::string auth_data = "";
770 auth_data += "client_id=" + botcraft_app_id;
771 auth_data += "&scope=XboxLive.signin%20offline_access";
772
773 const WebRequestResponse post_response = POSTRequest("login.microsoftonline.com", "/consumers/oauth2/v2.0/devicecode",
774 "application/x-www-form-urlencoded", "*/*", "", auth_data);
775
776 if (post_response.status_code != 200)
777 {
778 LOG_ERROR("Response returned with status code " << post_response.status_code << " ("
779 << post_response.status_message << ") during microsoft authentification:\n"
780 << post_response.response.Dump(4));
781 return "";
782 }
783
784 const Json::Value& auth_response = post_response.response;
785
786 if (!auth_response.contains("interval"))
787 {
788 LOG_ERROR("Error trying to get microsoft token, no interval in authentication response");
789 return "";
790 }
791
792 if (!auth_response.contains("message"))
793 {
794 LOG_ERROR("Error trying to get microsoft token, no message in authentication response");
795 return "";
796 }
797
798 if (!auth_response.contains("device_code"))
799 {
800 LOG_ERROR("Error trying to get microsoft token, no device_code in authentication response");
801 return "";
802 }
803
804 // Display the instructions the user has to follow to authenticate in the console
805 std::cout << auth_response["message"].get_string() << std::endl;
806 LOG_INFO(auth_response["message"].get_string());
807
808 const long long int pool_interval = auth_response["interval"].get_number<long long int>();
809 while (true)
810 {
811 std::this_thread::sleep_for(std::chrono::seconds(pool_interval + 1));
812
813 const std::string check_auth_status_data =
814 "client_id=" + botcraft_app_id +
815 "&scope=XboxLive.signin%20offline_access" +
816 "&grant_type=urn:ietf:params:oauth:grant-type:device_code" +
817 "&device_code=" + auth_response["device_code"].get_string();
818
819 const WebRequestResponse post_response = POSTRequest("login.microsoftonline.com", "/consumers/oauth2/v2.0/token",
820 "application/x-www-form-urlencoded", "*/*", "", check_auth_status_data);
821
822 const Json::Value& status_response = post_response.response;
823
824 if (post_response.status_code == 400)
825 {
826 if (!status_response.contains("error"))
827 {
828 LOG_ERROR("Unknown error happened during microsoft device authentication process");
829 return "";
830 }
831
832 const std::string error = status_response["error"].get_string();
833
834 if (error == "authorization_pending")
835 {
836 continue;
837 }
838 else if (error == "authorization_declined")
839 {
840 LOG_ERROR("User declined authorization during microsoft device authentication check");
841 return "";
842 }
843 else if (error == "expired_token")
844 {
845 LOG_ERROR("User took too long to perform device authentication, aborting");
846 return "";
847 }
848 else if (error == "invalid_grant")
849 {
850 if (!status_response.contains("error_description"))
851 {
852 LOG_ERROR("While waiting for microsoft device authentication, token got invalidated (no further information)");
853 }
854 else
855 {
856 LOG_ERROR("While waiting for microsoft device authentication, token got invalidated: " << status_response["error_description"].get_string());
857 }
858
859 return "";
860 }
861 }
862 else if (post_response.status_code == 200)
863 {
864 if (!status_response.contains("expires_in"))
865 {
866 LOG_ERROR("Error trying to get microsoft token, no expires_in in device authentication status response");
867 return "";
868 }
869
870 if (!status_response.contains("refresh_token"))
871 {
872 LOG_ERROR("Error trying to get microsoft token, no refresh_token in device authentication status response");
873 return "";
874 }
875
876 if (!status_response.contains("access_token"))
877 {
878 LOG_ERROR("Error trying to get microsoft token, no access_token in device authentication status response");
879 return "";
880 }
881
882 UpdateCachedMSA(login,
883 status_response["access_token"].get_string(),
884 status_response["refresh_token"].get_string(),
885 status_response["expires_in"].get<long long int>() + std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count()
886 );
887
888 LOG_INFO("Newly obtained Microsoft token stored in cache");
889
890 return status_response["access_token"].get_string();
891 }
892 else
893 {
894 LOG_ERROR("Response returned with status code " << post_response.status_code << " (" << post_response.status_message << ") during microsoft device authentification check");
895 return "";
896 }
897 }
898 }
899
900 const std::string Authentifier::GetXBLToken(const std::string& msa_token) const
901 {
902 Json::Value request_data = {
903 { "Properties", {
904 {"AuthMethod", "RPS"},
905 {"SiteName", "user.auth.xboxlive.com"},
906 {"RpsTicket", "d=" + msa_token}
907 }
908 },
909 { "RelyingParty", "http://auth.xboxlive.com"},
910 { "TokenType", "JWT"}
911 };
912
913 const WebRequestResponse post_response = POSTRequest("user.auth.xboxlive.com", "/user/authenticate",
914 "application/json", "application/json", "", request_data.Dump());
915
916 if (post_response.status_code != 200)
917 {
918 LOG_ERROR("Response returned with status code " << post_response.status_code
919 << " (" << post_response.status_message << ") during XBL authentication:\n"
920 << post_response.response.Dump(4));
921 return "";
922 }
923
924 const Json::Value& response = post_response.response;
925
926 if (!response.contains("Token"))
927 {
928 LOG_ERROR("Error trying to get XBL token, no Token in authentication response");
929 return "";
930 }
931
932 return response["Token"].get_string();
933 }
934
935 const std::pair<std::string, std::string> Authentifier::GetXSTSToken(const std::string& xbl_token) const
936 {
937 Json::Value request_data = {
938 { "Properties", {
939 {"SandboxId", "RETAIL"},
940 {"UserTokens", { xbl_token } }
941 }
942 },
943 { "RelyingParty", "rp://api.minecraftservices.com/"},
944 { "TokenType", "JWT"}
945 };
946
947 const WebRequestResponse post_response = POSTRequest("xsts.auth.xboxlive.com", "/xsts/authorize",
948 "application/json", "application/json", "", request_data.Dump());
949
950 if (post_response.status_code != 200)
951 {
952 LOG_ERROR("Response returned with status code " << post_response.status_code
953 << " (" << post_response.status_message << ") during XSTS authentication:\n"
954 << post_response.response.Dump(4));
955 return { "", "" };
956 }
957
958 const Json::Value& response = post_response.response;
959
960 if (!response.contains("Token"))
961 {
962 LOG_ERROR("Error trying to get XSTS token, no Token in authentication response");
963 return { "", "" };
964 }
965
966 if (!response.contains("DisplayClaims") || !response["DisplayClaims"].contains("xui")
967 || !response["DisplayClaims"]["xui"].is_array() || response["DisplayClaims"]["xui"].size() < 1
968 || !response["DisplayClaims"]["xui"][0].contains("uhs"))
969 {
970 LOG_ERROR("Error trying to get XSTS token, no DisplayClaims/xui/0/uhs in authentication response");
971 return { "", "" };
972 }
973
974 return { response["Token"].get_string(), response["DisplayClaims"]["xui"][0]["uhs"].get_string() };
975 }
976
977 const std::string Authentifier::GetMCToken(const std::string& login, const std::string& xsts_token, const std::string& user_hash) const
978 {
979 Json::Value request_data = {
980 { "identityToken", "XBL3.0 x=" + user_hash + ";" + xsts_token }
981 };
982
983 const WebRequestResponse post_response = POSTRequest("api.minecraftservices.com", "/authentication/login_with_xbox",
984 "application/json", "application/json", "", request_data.Dump());
985
986 if (post_response.status_code != 200)
987 {
988 LOG_ERROR("Response returned with status code " << post_response.status_code
989 << " (" << post_response.status_message << ") during MC authentication:\n"
990 << post_response.response.Dump(4));
991 return "";
992 }
993
994 const Json::Value& response = post_response.response;
995
996 if (!response.contains("access_token"))
997 {
998 LOG_ERROR("Error trying to get MC token, no access_token in authentication response");
999 return "";
1000 }
1001
1002 if (!response.contains("expires_in"))
1003 {
1004 LOG_WARNING("No expires_in in authentication response of MC");
1005 // if no expires_in assuming it is one-time, don't need to cache it
1006 return response["access_token"].get_string();
1007 }
1008
1009 UpdateCachedMCToken(login,
1010 response["access_token"].get_string(),
1011 response["expires_in"].get<long long int>() + std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count()
1012 );
1013
1014 return response["access_token"].get_string();
1015 }
1016
1017 const std::pair<std::string, std::string> Authentifier::GetMCProfile(const std::string& login, const std::string& mc_token) const
1018 {
1019 const WebRequestResponse get_response = GETRequest("api.minecraftservices.com", "/minecraft/profile",
1020 "Bearer " + mc_token);
1021
1022 if (get_response.status_code != 200)
1023 {
1024 LOG_ERROR("Response returned with status code " << get_response.status_code << " (" << get_response.status_message << ") during MC profile retrieval");
1025 return { "", "" };
1026 }
1027
1028 const Json::Value& response = get_response.response;
1029
1030 if (response.contains("errorMessage"))
1031 {
1032 LOG_ERROR("Error trying to get MC profile : " << response["errorMessage"].get_string());
1033 return { "", "" };
1034 }
1035
1036 if (!response.contains("id"))
1037 {
1038 LOG_ERROR("Error trying to get MC profile, no id in response");
1039 return { "", "" };
1040 }
1041
1042 if (!response.contains("name"))
1043 {
1044 LOG_ERROR("Error trying to get MC profile, no name in response");
1045 return { "", "" };
1046 }
1047
1049 response["name"].get_string(),
1050 response["id"].get_string()
1051 );
1052 return { response["id"].get_string(), response["name"].get_string() };
1053 }
1054
1055#if PROTOCOL_VERSION > 758 /* > 1.18.2 */
1056 const std::tuple<std::string, std::string, std::string, long long int> Authentifier::GetPlayerCertificates(const std::string& login,
1057 const std::string& mc_token) const
1058 {
1059 // Retrieve cached certificates
1060 Json::Value cached = GetCachedCredentials(login);
1061
1062 const bool invalid_cached_values = !cached.contains("certificates") || !cached["certificates"].is_object() ||
1063 !cached["certificates"].contains("private_key") || !cached["certificates"]["private_key"].is_string() ||
1064 !cached["certificates"].contains("public_key") || !cached["certificates"]["public_key"].is_string() ||
1065#if PROTOCOL_VERSION == 759 /* 1.19 */
1066 !cached["certificates"].contains("signature_v1") || !cached["certificates"]["signature_v1"].is_string() ||
1067#else
1068 !cached["certificates"].contains("signature_v2") || !cached["certificates"]["signature_v2"].is_string() ||
1069#endif
1070 !cached["certificates"].contains("expires_date") || !cached["certificates"]["expires_date"].is_number();
1071
1072 if (invalid_cached_values)
1073 {
1074 LOG_INFO("Invalid player certificates cached data");
1075 }
1076
1077 // Divided by 1000 because this one is stored in ms
1078 // TODO: investigate why it sometimes wrongly detects expired keys as still valid
1079 const bool expired = !invalid_cached_values && IsTokenExpired(cached["certificates"]["expires_date"].get<long long int>() / 1000);
1080
1081 if (expired)
1082 {
1083 LOG_INFO("Player certificates expired");
1084 }
1085
1086 // In case there is something wrong in the cached data
1087 if (invalid_cached_values || expired)
1088 {
1089 LOG_INFO("Starting player certificates acquisition process...");
1090
1091 const WebRequestResponse post_response = POSTRequest("api.minecraftservices.com", "/player/certificates",
1092 "application/json", "application/json", "Bearer " + mc_token, "");
1093
1094 if (post_response.status_code != 200)
1095 {
1096 LOG_ERROR("Response returned with status code " << post_response.status_code
1097 << " (" << post_response.status_message << ") during player certificates acquisition:\n"
1098 << post_response.response.Dump(4));
1099 return { "", "", "", 0};
1100 }
1101
1102 const Json::Value& response = post_response.response;
1103
1104 if (!response.contains("keyPair"))
1105 {
1106 LOG_ERROR("Error trying to get player certificates, no keyPair in response");
1107 return { "", "", "", 0};
1108 }
1109
1110 if (!response["keyPair"].contains("privateKey"))
1111 {
1112 LOG_ERROR("Error trying to get player certificates, no privateKey in response");
1113 return { "", "", "", 0 };
1114 }
1115
1116 if (!response["keyPair"].contains("publicKey"))
1117 {
1118 LOG_ERROR("Error trying to get player certificates, no publicKey in response");
1119 return { "", "", "", 0 };
1120 }
1121
1122 if (!response.contains("publicKeySignature"))
1123 {
1124 LOG_ERROR("Error trying to get player certificates, no publicKeySignature in response");
1125 return { "", "", "", 0 };
1126 }
1127
1128 if (!response.contains("publicKeySignatureV2"))
1129 {
1130 LOG_ERROR("Error trying to get player certificates, no publicKeySignatureV2 in response");
1131 return { "", "", "", 0 };
1132 }
1133
1134 if (!response.contains("expiresAt"))
1135 {
1136 LOG_ERROR("Error trying to get player certificates, no expiresAt in response");
1137 return { "", "", "", 0 };
1138 }
1139
1140 // Convert expires date in ISO8601 to ms since UNIX epoch
1141 const long long int expires_timestamp = Utilities::TimestampMilliFromISO8601(response["expiresAt"].get_string());
1142
1143 UpdateCachedPlayerCertificates(login, response["keyPair"]["privateKey"].get_string(),
1144 response["keyPair"]["publicKey"].get_string(), response["publicKeySignature"].get_string(),
1145 response["publicKeySignatureV2"].get_string(), expires_timestamp
1146 );
1147
1148 return { response["keyPair"]["privateKey"].get_string(),
1149 response["keyPair"]["publicKey"].get_string(),
1150#if PROTOCOL_VERSION == 759 /* 1.19 */
1151 response["publicKeySignature"].get_string(),
1152#else
1153 response["publicKeySignatureV2"].get_string(),
1154#endif
1155 expires_timestamp
1156 };
1157 }
1158
1159 LOG_INFO("Cached player certificates still valid!");
1160 return { cached["certificates"]["private_key"].get_string(),
1161 cached["certificates"]["public_key"].get_string(),
1162 #if PROTOCOL_VERSION == 759 /* 1.19 */
1163 cached["certificates"]["signature_v1"].get_string(),
1164#else
1165 cached["certificates"]["signature_v2"].get_string(),
1166#endif
1167 cached["certificates"]["expires_date"].get<long long int>(),
1168 };
1169 }
1170#endif
1171
1172 const WebRequestResponse Authentifier::WebRequest(const std::string& host, const std::string& raw_request) const
1173 {
1174 asio::io_context io_context;
1175
1176 // Get a list of endpoints corresponding to the server name.
1177 asio::ip::tcp::resolver resolver(io_context);
1178 asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, "https");
1179
1180 asio::ssl::context ctx(asio::ssl::context::sslv23);
1181 ctx.set_default_verify_paths();
1182 ctx.set_options(asio::ssl::context::default_workarounds | asio::ssl::context::verify_none);
1183
1184 asio::ssl::stream<asio::ip::tcp::socket> socket(io_context, ctx);
1185 socket.set_verify_mode(asio::ssl::verify_none);
1186 socket.set_verify_callback([](bool, asio::ssl::verify_context&) {return true; });
1187 SSL_set_tlsext_host_name(socket.native_handle(), host.c_str());
1188 asio::connect(socket.lowest_layer(), endpoints);
1189 socket.handshake(socket.client);
1190 socket.lowest_layer().set_option(asio::ip::tcp::no_delay(true));
1191
1192 // Send the request
1193 asio::streambuf request;
1194 std::ostream request_stream(&request);
1195 request_stream << raw_request;
1196
1197 asio::write(socket, request);
1198
1199 WebRequestResponse web_response;
1200
1201 // Read the response status line. The response streambuf will automatically
1202 // grow to accommodate the entire line. The growth may be limited by passing
1203 // a maximum size to the streambuf constructor.
1204 asio::streambuf response;
1205 asio::read_until(socket, response, "\r\n");
1206
1207 // Check that response is OK.
1208 std::istream response_stream(&response);
1209 std::string http_version;
1210 response_stream >> http_version;
1211 response_stream >> web_response.status_code;
1212 std::getline(response_stream, web_response.status_message);
1213
1214 // Remove any \r in status message
1215 web_response.status_message.erase(std::remove(web_response.status_message.begin(), web_response.status_message.end(), '\r'),
1216 web_response.status_message.end());
1217
1218 if (!response_stream || http_version.substr(0, 5) != "HTTP/")
1219 {
1220 LOG_ERROR("Invalid response during web request");
1221 web_response.response = {};
1222 return web_response;
1223 }
1224
1225 // Empty response
1226 if (web_response.status_code == 204)
1227 {
1228 web_response.response = {};
1229 return web_response;
1230 }
1231
1232 // Read the response headers, which are terminated by a blank line.
1233 asio::read_until(socket, response, "\r\n\r\n");
1234
1235 // Process the response headers.
1236 std::string header;
1237 long long int data_length = -1;
1238 while (std::getline(response_stream, header) && header != "\r")
1239 {
1240 if (header.find("Content-Length: ") == 0)
1241 {
1242 data_length = std::stoll(header.substr(16));
1243 }
1244 }
1245
1246 // Write whatever content we already have to output.
1247 std::stringstream output_stringstream;
1248 if (response.size() > 0)
1249 {
1250 output_stringstream << &response;
1251 }
1252
1253 // Read until EOF, writing data to output as we go.
1254 asio::error_code error;
1255 while (asio::read(socket, response, asio::transfer_at_least(1), error))
1256 {
1257 output_stringstream << &response;
1258 }
1259 const std::string raw_response = output_stringstream.str();
1260
1261 if (error != asio::error::eof && raw_response.size() != data_length)
1262 {
1263 LOG_ERROR("Error trying to read web request response, Error:\n " << error);
1264 web_response.response = {};
1265 }
1266 else
1267 {
1268 web_response.response = Json::Parse(raw_response);
1269 }
1270
1271 return web_response;
1272 }
1273
1274 const WebRequestResponse Authentifier::POSTRequest(const std::string& host, const std::string& endpoint,
1275 const std::string& content_type, const std::string& accept,
1276 const std::string& authorization, const std::string& data) const
1277 {
1278 // Form the request. We specify the "Connection: close" header so that the
1279 // server will close the socket after transmitting the response. This will
1280 // allow us to treat all data up until the EOF as the content.
1281 std::string raw_request = "";
1282 raw_request += "POST " + endpoint + " HTTP/1.1 \r\n";
1283 raw_request += "Host: " + host + "\r\n";
1284 raw_request += "User-Agent: C/1.0\r\n";
1285 raw_request += "Content-Type: " + content_type + " \r\n";
1286 raw_request += "Accept: " + accept + "\r\n";
1287 if (!authorization.empty())
1288 {
1289 raw_request += "Authorization: " + authorization + "\r\n";
1290 }
1291 raw_request += "Content-Length: " + std::to_string(data.length()) + "\r\n";
1292 raw_request += "Connection: close\r\n\r\n";
1293 raw_request += data;
1294
1295 return WebRequest(host, raw_request);
1296 }
1297
1298 const WebRequestResponse Authentifier::GETRequest(const std::string& host, const std::string& endpoint, const std::string& authorization) const
1299 {
1300 // Form the request. We specify the "Connection: close" header so that the
1301 // server will close the socket after transmitting the response. This will
1302 // allow us to treat all data up until the EOF as the content.
1303 std::string raw_request = "";
1304 raw_request += "GET " + endpoint + " HTTP/1.1 \r\n";
1305 raw_request += "Host: " + host + "\r\n";
1306 if (!authorization.empty())
1307 {
1308 raw_request += "Authorization: " + authorization + "\r\n";
1309 }
1310 raw_request += "User-Agent: C/1.0\r\n";
1311 raw_request += "Connection: close\r\n\r\n";
1312
1313 return WebRequest(host, raw_request);
1314 }
1315#endif
1316}
#define LOG_ERROR(osstream)
Definition Logger.hpp:45
#define LOG_WARNING(osstream)
Definition Logger.hpp:44
#define LOG_INFO(osstream)
Definition Logger.hpp:43
ProtocolCraft::Json::Value GetCachedProfiles() const
Get the content of the whole cache file.
const std::string & GetPrivateKey() const
ProtocolCraft::Json::Value GetCachedCredentials(const std::string &login) const
Try to find a cached account corresponding to login.
void UpdateCachedMCProfile(const std::string &login, const std::string &name, const std::string &id) const
Update the cached MC profile data for the given login.
const WebRequestResponse WebRequest(const std::string &host, const std::string &raw_request) const
Send a web request with ssl stuff.
const std::string GetXBLToken(const std::string &msa_token) const
Try to get XBox Live token from Microsoft token.
std::string player_display_name
const std::string MSAAuthDeviceFlow(const std::string &login) const
Try to authenticate with microsoft account using device flow.
const bool IsTokenExpired(const long long int &t) const
Check if a validity time is in the present or in the future.
const std::string GetMSAToken(const std::string &login) const
Check if there is a saved credentials file and if the token is still valid.
static const std::string cached_credentials_path
Path to cache the credentials.
static const std::string botcraft_app_id
Botcraft app ID for microsoft auth.
const std::string & GetPlayerDisplayName() const
long long int key_timestamp
void UpdateUUIDBytes()
Compute the UUID bytes from the string one.
const bool AuthMicrosoft(const std::string &login)
Authentication using a Microsoft account.
static const ProtocolCraft::Json::Value defaultCachedCredentials
Default cached credentials JSON.
const WebRequestResponse POSTRequest(const std::string &host, const std::string &endpoint, const std::string &content_type, const std::string &accept, const std::string &authorization, const std::string &data) const
Send a POST request with ssl stuff.
const std::vector< unsigned char > GetMessageSignature(const std::string &message, const int message_sent_index, const ProtocolCraft::UUID &chat_session_uuid, const std::vector< std::vector< unsigned char > > &last_seen, long long int &salt, long long int &timestamp)
Compute the signature of a message.
const std::tuple< std::string, std::string, std::string, long long int > GetPlayerCertificates(const std::string &login, const std::string &mc_token) const
Try to get player certificates from Minecraft token.
const std::string & GetKeySignature() const
const long long int GetKeyTimestamp() const
const WebRequestResponse GETRequest(const std::string &host, const std::string &endpoint, const std::string &authorization="") const
Send a GET request with ssl stuff.
const std::string GetMCToken(const std::string &login, const std::string &xsts_token, const std::string &user_hash) const
Try to get MC token from XSTS token and user hash.
const std::pair< std::string, std::string > GetXSTSToken(const std::string &xbl_token) const
Try to get XSTS token from XBL token.
const std::array< unsigned char, 16 > & GetPlayerUUID() const
const std::tuple< std::string, std::string, std::string > ExtractMCFromResponse(const ProtocolCraft::Json::Value &response) const
Extract the token, the name and the uuid from a server response.
std::array< unsigned char, 16 > mc_player_uuid_bytes
const std::string & GetPublicKey() const
void WriteCacheFile(const ProtocolCraft::Json::Value &profiles) const
Save a profiles list to cache file.
void UpdateCachedMCToken(const std::string &login, const std::string &mc_token, const long long int &expiration) const
Update the cached MC token data for the given login.
const bool JoinServer(const std::string &server_id, const std::vector< unsigned char > &shared_secret, const std::vector< unsigned char > &public_key) const
void UpdateCachedPlayerCertificates(const std::string &login, const std::string &private_k, const std::string &public_k, const std::string &signature_v1, const std::string &signature_v2, const long long int &expiration) const
Update the cached player certificates for the given login.
const std::pair< std::string, std::string > GetMCProfile(const std::string &login, const std::string &mc_token) const
Try to get Minecraft profile from Minecraft token.
void UpdateCachedMSA(const std::string &login, const std::string &access_token, const std::string &refresh_token, const long long int &expiration) const
Update the cached MSA data for the given login.
Main class, basically a JsonVariant with extra utility functions it doesn't inherit JsonVariant direc...
Definition Json.hpp:45
bool is_string() const
Definition Json.cpp:144
size_t size() const
Definition Json.cpp:237
bool is_number() const
Definition Json.cpp:170
std::string Dump(const int indent=-1, const char indent_char=' ') const
public dump interface
Definition Json.cpp:287
bool is_array() const
Definition Json.cpp:154
bool is_object() const
Definition Json.cpp:149
bool contains(const std::string &s) const
Definition Json.cpp:232
std::string & get_string()
Definition Json.cpp:119
long long int TimestampMilliFromISO8601(const std::string &s)
Value Parse(std::string_view::const_iterator iter, size_t length, bool no_except=false)
Parse a string_view from iter for at most length characters.
Definition Json.cpp:390
std::array< unsigned char, 16 > UUID
ProtocolCraft::Json::Value response