diff --git a/browser/resources/settings/brave_sync_page/brave_sync_code_dialog.js b/browser/resources/settings/brave_sync_page/brave_sync_code_dialog.js
index 78e2091a7fb0..ce18bbcfa759 100644
--- a/browser/resources/settings/brave_sync_page/brave_sync_code_dialog.js
+++ b/browser/resources/settings/brave_sync_page/brave_sync_code_dialog.js
@@ -44,9 +44,9 @@ Polymer({
value: 'choose',
notify: true
},
- isInvalidSyncCode: {
- type: Boolean,
- value: false,
+ syncCodeValidationError: {
+ type: String,
+ value: '',
notify: true
},
syncCodeWordCount_: {
@@ -64,7 +64,6 @@ Polymer({
},
observers: [
- 'updateSyncCodeValidity_(syncCode)',
'getQRCode_(syncCode, codeType)',
],
@@ -75,10 +74,6 @@ Polymer({
this.syncBrowserProxy_ = BraveSyncBrowserProxy.getInstance();
},
- updateSyncCodeValidity_: function() {
- this.isInvalidSyncCode = false
- },
-
computeSyncCodeWordCount_: function() {
if (!this.syncCode) {
return 0
diff --git a/browser/resources/settings/brave_sync_page/brave_sync_page.js b/browser/resources/settings/brave_sync_page/brave_sync_page.js
index df5962796327..1ee6df479450 100644
--- a/browser/resources/settings/brave_sync_page/brave_sync_page.js
+++ b/browser/resources/settings/brave_sync_page/brave_sync_page.js
@@ -99,11 +99,11 @@ Polymer({
*/
handleSyncPrefsChanged_: async function(syncPrefs) {
if (this.syncStatus_ && !this.syncStatus_.firstSetupInProgress) {
- const syncCode = await this.braveBrowserProxy_.getSyncCode()
+ const pureSyncCode = await this.braveBrowserProxy_.getPureSyncCode()
if (syncPrefs.passphraseRequired) {
- await this.browserProxy_.setDecryptionPassphrase(syncCode);
+ await this.browserProxy_.setDecryptionPassphrase(pureSyncCode);
} else if (!this.isEncryptionSet_) {
- this.browserProxy_.setEncryptionPassphrase(syncCode)
+ this.browserProxy_.setEncryptionPassphrase(pureSyncCode)
.then(successfullySet => {
this.isEncryptionSet_ = successfullySet
})
diff --git a/browser/resources/settings/brave_sync_page/brave_sync_setup.html b/browser/resources/settings/brave_sync_page/brave_sync_setup.html
index 97f87866b3d1..da7dc9b10054 100644
--- a/browser/resources/settings/brave_sync_page/brave_sync_setup.html
+++ b/browser/resources/settings/brave_sync_page/brave_sync_setup.html
@@ -62,7 +62,7 @@
$i18n{braveSyncSetupTitle}
diff --git a/browser/resources/settings/brave_sync_page/brave_sync_setup.js b/browser/resources/settings/brave_sync_page/brave_sync_setup.js
index aab1a247bc27..8a478c22d9c7 100644
--- a/browser/resources/settings/brave_sync_page/brave_sync_setup.js
+++ b/browser/resources/settings/brave_sync_page/brave_sync_setup.js
@@ -43,10 +43,10 @@ Polymer({
type: Boolean,
value: false,
},
- isInvalidSyncCode_: {
- type: Boolean,
- value: false,
- }
+ syncCodeValidationError_: {
+ type: String,
+ value: '',
+ }
},
/** @private {?BraveSyncBrowserProxy} */
@@ -88,13 +88,11 @@ Polymer({
try {
success = await this.syncBrowserProxy_.setSyncCode(syncCodeToSubmit)
} catch (e) {
- console.error("Error setting sync code")
+ this.syncCodeValidationError_ = e
success = false
}
this.isSubmittingSyncCode_ = false
- if (!success) {
- this.isInvalidSyncCode_ = true
- } else {
+ if (success) {
this.syncCodeDialogType_ = undefined
this.fire('setup-success')
}
diff --git a/browser/ui/android/strings/android_brave_strings.grd b/browser/ui/android/strings/android_brave_strings.grd
index 07dbe1ce0d73..fa2cf44e8dfd 100644
--- a/browser/ui/android/strings/android_brave_strings.grd
+++ b/browser/ui/android/strings/android_brave_strings.grd
@@ -703,9 +703,6 @@ until they verify, or until 90 days have passed.
Word count: %1$d
-
- Incorrect number of words
-
Wrong sync code
diff --git a/browser/ui/webui/settings/brave_sync_handler.cc b/browser/ui/webui/settings/brave_sync_handler.cc
index 09505659b875..f995850ad22e 100644
--- a/browser/ui/webui/settings/brave_sync_handler.cc
+++ b/browser/ui/webui/settings/brave_sync_handler.cc
@@ -15,8 +15,10 @@
#include "brave/components/brave_sync/crypto/crypto.h"
#include "brave/components/brave_sync/qr_code_data.h"
#include "brave/components/brave_sync/sync_service_impl_helper.h"
+#include "brave/components/brave_sync/time_limited_words.h"
#include "brave/components/sync/driver/brave_sync_service_impl.h"
#include "brave/components/sync_device_info/brave_device_info.h"
+#include "brave/grit/brave_generated_resources.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/device_info_sync_service_factory.h"
#include "chrome/browser/sync/sync_service_factory.h"
@@ -25,8 +27,37 @@
#include "components/sync_device_info/device_info_tracker.h"
#include "components/sync_device_info/local_device_info_provider.h"
#include "content/public/browser/web_ui.h"
+#include "ui/base/l10n/l10n_util.h"
#include "ui/base/webui/web_ui_util.h"
+using brave_sync::TimeLimitedWords;
+using brave_sync::WordsValidationStatus;
+
+namespace {
+
+std::string GetSyncCodeValidationString(
+ WordsValidationStatus validation_result) {
+ switch (validation_result) {
+ case WordsValidationStatus::kValid:
+ return "";
+ case WordsValidationStatus::kWrongWordsNumber:
+ case WordsValidationStatus::kNotValidPureWords:
+ return l10n_util::GetStringUTF8(IDS_BRAVE_SYNC_CODE_INVALID);
+ case WordsValidationStatus::kVersionDeprecated:
+ return l10n_util::GetStringUTF8(
+ IDS_BRAVE_SYNC_CODE_FROM_DEPRECATED_VERSION);
+ case WordsValidationStatus::kExpired:
+ return l10n_util::GetStringUTF8(IDS_BRAVE_SYNC_CODE_EXPIRED);
+ case WordsValidationStatus::kValidForTooLong:
+ return l10n_util::GetStringUTF8(IDS_BRAVE_SYNC_CODE_VALID_FOR_TOO_LONG);
+ default:
+ NOTREACHED();
+ return "";
+ }
+}
+
+} // namespace
+
BraveSyncHandler::BraveSyncHandler() : weak_ptr_factory_(this) {}
BraveSyncHandler::~BraveSyncHandler() {}
@@ -45,6 +76,10 @@ void BraveSyncHandler::RegisterMessages() {
"SyncSetupGetSyncCode",
base::BindRepeating(&BraveSyncHandler::HandleGetSyncCode,
base::Unretained(this)));
+ web_ui()->RegisterMessageCallback(
+ "SyncSetupGetPureSyncCode",
+ base::BindRepeating(&BraveSyncHandler::HandleGetPureSyncCode,
+ base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"SyncGetQRCode", base::BindRepeating(&BraveSyncHandler::HandleGetQRCode,
base::Unretained(this)));
@@ -86,6 +121,22 @@ void BraveSyncHandler::HandleGetSyncCode(const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(1U, args.size());
+ auto* sync_service = GetSyncService();
+ std::string sync_code;
+ if (sync_service)
+ sync_code = sync_service->GetOrCreateSyncCode();
+
+ std::string time_limited_sync_code =
+ TimeLimitedWords::GenerateForNow(sync_code);
+
+ ResolveJavascriptCallback(args[0].Clone(),
+ base::Value(time_limited_sync_code));
+}
+
+void BraveSyncHandler::HandleGetPureSyncCode(const base::Value::List& args) {
+ AllowJavascript();
+ CHECK_EQ(1U, args.size());
+
auto* sync_service = GetSyncService();
std::string sync_code;
if (sync_service)
@@ -98,10 +149,18 @@ void BraveSyncHandler::HandleGetQRCode(const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(2U, args.size());
CHECK(args[1].is_string());
- const std::string sync_code = args[1].GetString();
+ const std::string time_limited_sync_code = args[1].GetString();
+
+ // Sync code arrives here with time-limit 25th word, remove it to get proper
+ // pure seed for QR generation (QR codes have their own expiry)
+ auto pure_words_with_status = TimeLimitedWords::Parse(time_limited_sync_code);
+ CHECK_EQ(pure_words_with_status.status, WordsValidationStatus::kValid);
+ CHECK(pure_words_with_status.pure_words);
+ CHECK_NE(pure_words_with_status.pure_words.value().size(), 0u);
std::vector
seed;
- if (!brave_sync::crypto::PassphraseToBytes32(sync_code, &seed)) {
+ if (!brave_sync::crypto::PassphraseToBytes32(
+ pure_words_with_status.pure_words.value(), &seed)) {
LOG(ERROR) << "invalid sync code when generating qr code";
RejectJavascriptCallback(args[0].Clone(), base::Value("invalid sync code"));
return;
@@ -144,16 +203,31 @@ void BraveSyncHandler::HandleSetSyncCode(const base::Value::List& args) {
AllowJavascript();
CHECK_EQ(2U, args.size());
CHECK(args[1].is_string());
- const std::string sync_code = args[1].GetString();
-
- if (sync_code.empty()) {
+ const std::string time_limited_sync_code = args[1].GetString();
+ if (time_limited_sync_code.empty()) {
LOG(ERROR) << "No sync code parameter provided!";
RejectJavascriptCallback(args[0].Clone(), base::Value(false));
return;
}
+ auto pure_words_with_status = TimeLimitedWords::Parse(time_limited_sync_code);
+
+ if (pure_words_with_status.status != WordsValidationStatus::kValid) {
+ LOG(ERROR) << "Could not validate a sync code, validation_result="
+ << static_cast(pure_words_with_status.status) << " "
+ << GetSyncCodeValidationString(pure_words_with_status.status);
+ RejectJavascriptCallback(args[0].Clone(),
+ base::Value(GetSyncCodeValidationString(
+ pure_words_with_status.status)));
+ return;
+ }
+
+ CHECK(pure_words_with_status.pure_words);
+ CHECK(!pure_words_with_status.pure_words.value().empty());
+
auto* sync_service = GetSyncService();
- if (!sync_service || !sync_service->SetSyncCode(sync_code)) {
+ if (!sync_service ||
+ !sync_service->SetSyncCode(pure_words_with_status.pure_words.value())) {
RejectJavascriptCallback(args[0].Clone(), base::Value(false));
return;
}
diff --git a/browser/ui/webui/settings/brave_sync_handler.h b/browser/ui/webui/settings/brave_sync_handler.h
index f41fd53b5a4a..7df6ff873fff 100644
--- a/browser/ui/webui/settings/brave_sync_handler.h
+++ b/browser/ui/webui/settings/brave_sync_handler.h
@@ -41,6 +41,7 @@ class BraveSyncHandler : public settings::SettingsPageUIHandler,
// Custom message handlers:
void HandleGetDeviceList(const base::Value::List& args);
void HandleGetSyncCode(const base::Value::List& args);
+ void HandleGetPureSyncCode(const base::Value::List& args);
void HandleSetSyncCode(const base::Value::List& args);
void HandleGetQRCode(const base::Value::List& args);
void HandleReset(const base::Value::List& args);
diff --git a/components/brave_sync/BUILD.gn b/components/brave_sync/BUILD.gn
index 7c280159d8a2..c654c3a28c8e 100644
--- a/components/brave_sync/BUILD.gn
+++ b/components/brave_sync/BUILD.gn
@@ -72,12 +72,14 @@ source_set("prefs") {
]
}
-source_set("qr_code_data") {
+source_set("time_limited_codes") {
sources = [
"qr_code_data.cc",
"qr_code_data.h",
"qr_code_validator.cc",
"qr_code_validator.h",
+ "time_limited_words.cc",
+ "time_limited_words.h",
]
deps = [
@@ -105,7 +107,7 @@ group("brave_sync") {
":features",
":network_time_helper",
":prefs",
- ":qr_code_data",
+ ":time_limited_codes",
"//base",
]
}
@@ -120,11 +122,12 @@ source_set("unit_tests") {
"//brave/components/brave_sync/brave_sync_prefs_unittest.cc",
"//brave/components/brave_sync/qr_code_data_unittest.cc",
"//brave/components/brave_sync/qr_code_validator_unittest.cc",
+ "//brave/components/brave_sync/time_limited_words_unittest.cc",
]
deps = [
":prefs",
- ":qr_code_data",
+ ":time_limited_codes",
"//base/test:test_support",
"//components/os_crypt:test_support",
"//components/prefs:test_support",
diff --git a/components/brave_sync/qr_code_data_unittest.cc b/components/brave_sync/qr_code_data_unittest.cc
index 25d960a3b599..7babbbd5565d 100644
--- a/components/brave_sync/qr_code_data_unittest.cc
+++ b/components/brave_sync/qr_code_data_unittest.cc
@@ -8,7 +8,6 @@
#include
#include
-#include "base/logging.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
diff --git a/components/brave_sync/time_limited_words.cc b/components/brave_sync/time_limited_words.cc
new file mode 100644
index 000000000000..044a90949a0d
--- /dev/null
+++ b/components/brave_sync/time_limited_words.cc
@@ -0,0 +1,209 @@
+/* Copyright (c) 2022 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "brave/components/brave_sync/time_limited_words.h"
+
+#include
+#include
+#include
+
+#include "base/containers/span.h"
+#include "base/logging.h"
+#include "base/notreached.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "brave/components/brave_sync/crypto/crypto.h"
+#include "brave/vendor/bip39wally-core-native/include/wally_bip39.h"
+#include "brave/vendor/bip39wally-core-native/src/wordlist.h"
+
+namespace brave_sync {
+
+namespace {
+
+static constexpr char kWordsv1SunsetDate[] = "Mon, 1 Aug 2022 00:00:00 GMT";
+static constexpr char kWordsv2Epoch[] = "Tue, 10 May 2022 00:00:00 GMT";
+
+} // namespace
+
+TimeLimitedWords::PureWordsWithStatus::PureWordsWithStatus() = default;
+
+TimeLimitedWords::PureWordsWithStatus::PureWordsWithStatus(
+ PureWordsWithStatus&& other) = default;
+
+TimeLimitedWords::PureWordsWithStatus::~PureWordsWithStatus() = default;
+
+TimeLimitedWords::PureWordsWithStatus&
+TimeLimitedWords::PureWordsWithStatus::operator=(PureWordsWithStatus&& other) =
+ default;
+
+using base::Time;
+using base::TimeDelta;
+
+Time TimeLimitedWords::words_v1_sunset_day_;
+Time TimeLimitedWords::words_v2_epoch_;
+
+std::string TimeLimitedWords::GetWordByIndex(size_t index) {
+ DCHECK_EQ(BIP39_WORDLIST_LEN, 2048);
+ index = index % BIP39_WORDLIST_LEN;
+ char* word = nullptr;
+ if (bip39_get_word(nullptr, index, &word) != WALLY_OK) {
+ LOG(ERROR) << "bip39_get_word failed for index " << index;
+ return std::string();
+ }
+
+ std::string str_word = word;
+ wally_free_string(word);
+
+ return str_word;
+}
+
+int TimeLimitedWords::GetIndexByWord(const std::string& word) {
+ std::string word_prepared = base::ToLowerASCII(word);
+
+ struct words* mnemonic_w = nullptr;
+ if (bip39_get_wordlist(nullptr, &mnemonic_w) != WALLY_OK) {
+ DCHECK(false);
+ return -1;
+ }
+
+ DCHECK_NE(mnemonic_w, nullptr);
+ size_t idx = wordlist_lookup_word(mnemonic_w, word_prepared.c_str());
+ if (!idx) {
+ return -1;
+ }
+
+ return idx - 1;
+}
+
+Time TimeLimitedWords::GetWordsV1SunsetDay() {
+ if (words_v1_sunset_day_.is_null()) {
+ bool convert_result =
+ Time::FromUTCString(kWordsv1SunsetDate, &words_v1_sunset_day_);
+ CHECK(convert_result);
+ }
+
+ CHECK(!words_v1_sunset_day_.is_null());
+
+ return words_v1_sunset_day_;
+}
+
+Time TimeLimitedWords::GetWordsV2Epoch() {
+ if (words_v2_epoch_.is_null()) {
+ bool convert_result = Time::FromUTCString(kWordsv2Epoch, &words_v2_epoch_);
+ CHECK(convert_result);
+ }
+
+ CHECK(!words_v2_epoch_.is_null());
+
+ return words_v2_epoch_;
+}
+
+int TimeLimitedWords::GetRoundedDaysDiff(const Time& time1, const Time& time2) {
+ TimeDelta delta = time2 - time1;
+
+ double delta_in_days_f = delta.InMillisecondsF() / Time::kMillisecondsPerDay;
+
+ int days_rounded = std::round(delta_in_days_f);
+ return days_rounded;
+}
+
+std::string TimeLimitedWords::GenerateForNow(const std::string& pure_words) {
+ return TimeLimitedWords::GenerateForDate(pure_words, Time::Now());
+}
+
+std::string TimeLimitedWords::GenerateForDate(const std::string& pure_words,
+ const Time& not_after) {
+ int days_since_words_v2_epoch =
+ GetRoundedDaysDiff(GetWordsV2Epoch(), not_after);
+
+ if (days_since_words_v2_epoch < 0) {
+ // Something goes bad, requested |not_after| is even before sync v2 epoch
+ return std::string();
+ }
+
+ std::string last_word = GetWordByIndex(days_since_words_v2_epoch);
+
+ std::string time_limited_code = base::StrCat({pure_words, " ", last_word});
+ return time_limited_code;
+}
+
+WordsValidationStatus TimeLimitedWords::Validate(
+ const std::string& time_limited_words,
+ std::string* pure_words) {
+ CHECK_NE(pure_words, nullptr);
+ *pure_words = std::string();
+
+ static constexpr size_t kPureWordsCount = 24u;
+ static constexpr size_t kWordsV2Count = 25u;
+
+ auto now = Time::Now();
+
+ std::vector words = base::SplitString(
+ time_limited_words, " ", base::WhitespaceHandling::TRIM_WHITESPACE,
+ base::SplitResult::SPLIT_WANT_NONEMPTY);
+
+ size_t num_words = words.size();
+
+ if (num_words == kPureWordsCount) {
+ if (now < GetWordsV1SunsetDay()) {
+ std::string recombined_pure_words = base::JoinString(
+ base::span(words.begin(), kPureWordsCount), " ");
+ if (crypto::IsPassphraseValid(recombined_pure_words)) {
+ *pure_words = recombined_pure_words;
+ return WordsValidationStatus::kValid;
+ } else {
+ return WordsValidationStatus::kNotValidPureWords;
+ }
+ } else {
+ return WordsValidationStatus::kVersionDeprecated;
+ }
+ } else if (num_words == kWordsV2Count) {
+ std::string recombined_pure_words = base::JoinString(
+ base::span(words.begin(), kPureWordsCount), " ");
+ if (crypto::IsPassphraseValid(recombined_pure_words)) {
+ int days_actual =
+ GetRoundedDaysDiff(GetWordsV2Epoch(), now) % BIP39_WORDLIST_LEN;
+
+ int days_encoded = GetIndexByWord(words[kWordsV2Count - 1]);
+ DCHECK(days_encoded < BIP39_WORDLIST_LEN);
+
+ int days_abs_diff = std::abs(days_actual - days_encoded);
+ if (days_abs_diff <= 1) {
+ *pure_words = recombined_pure_words;
+ return WordsValidationStatus::kValid;
+ } else if (days_actual > days_encoded) {
+ return WordsValidationStatus::kExpired;
+ } else if (days_encoded > days_actual) {
+ return WordsValidationStatus::kValidForTooLong;
+ }
+ } else {
+ return WordsValidationStatus::kNotValidPureWords;
+ }
+ } else {
+ return WordsValidationStatus::kWrongWordsNumber;
+ }
+
+ NOTREACHED();
+ return WordsValidationStatus::kNotValidPureWords;
+}
+
+TimeLimitedWords::PureWordsWithStatus TimeLimitedWords::Parse(
+ const std::string& time_limited_words) {
+ PureWordsWithStatus ret;
+ std::string pure_words;
+ ret.status = Validate(time_limited_words, &pure_words);
+
+ if (ret.status == WordsValidationStatus::kValid) {
+ ret.pure_words = pure_words;
+ } else {
+ ret.pure_words = absl::nullopt;
+ }
+
+ return ret;
+}
+
+} // namespace brave_sync
diff --git a/components/brave_sync/time_limited_words.h b/components/brave_sync/time_limited_words.h
new file mode 100644
index 000000000000..87515e7e33ec
--- /dev/null
+++ b/components/brave_sync/time_limited_words.h
@@ -0,0 +1,79 @@
+/* Copyright (c) 2022 The Brave Authors. All rights reserved.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef BRAVE_COMPONENTS_BRAVE_SYNC_TIME_LIMITED_WORDS_H_
+#define BRAVE_COMPONENTS_BRAVE_SYNC_TIME_LIMITED_WORDS_H_
+
+#include