auto recover from corrupted mnemonic words by calling User::generateNewPassphrase

This commit is contained in:
Dario 2019-12-13 14:17:45 +01:00
parent 561826ddce
commit 2910d85729
10 changed files with 116 additions and 21 deletions

View File

@ -39,7 +39,7 @@ KeyPair::~KeyPair()
bool KeyPair::generateFromPassphrase(const char* passphrase, Mnemonic* word_source)
{
auto er = ErrorManager::getInstance();
// libsodium doc: https://libsodium.gitbook.io/doc/advanced/hmac-sha2
// https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
//crypto_auth_hmacsha512_keygen
@ -53,7 +53,14 @@ bool KeyPair::generateFromPassphrase(const char* passphrase, Mnemonic* word_sour
unsigned char word_cursor = 0;
for (size_t i = 0; i < pass_phrase_size; i++) {
if (passphrase[i] == ' ') {
word_indices[word_cursor] = word_source->getWordIndex(acBuffer);
if (word_source->isWordExist(acBuffer)) {
word_indices[word_cursor] = word_source->getWordIndex(acBuffer);
}
else {
er->addError(new ParamError("KeyPair::generateFromPassphrase", "word didn't exist", acBuffer));
er->sendErrorsAsEmail();
return false;
}
word_cursor++;
memset(acBuffer, 0, STR_BUFFER_SIZE);
@ -90,6 +97,9 @@ bool KeyPair::generateFromPassphrase(const char* passphrase, Mnemonic* word_sour
if (!mPrivateKey) {
//delete mPrivateKey;
mPrivateKey = mm->getFreeMemory(ed25519_privkey_SIZE);
if (!mPrivateKey) {
return false;
}
}
//mPrivateKey = new ObfusArray(ed25519_privkey_SIZE, prv_key_t.data);

View File

@ -15,19 +15,16 @@ Mnemonic::Mnemonic()
Mnemonic::~Mnemonic()
{
for (int i = 0; i < 2048; i++) {
if (mWords[i]) {
free(mWords[i]);
}
}
memset(mWords, 0, 2048);
mWordHashIndices.clear();
clear();
}
int Mnemonic::init(void(*fill_words_func)(unsigned char*), unsigned int original_size, unsigned int compressed_size)
{
Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
clear();
unsigned char* buffer = (unsigned char*)malloc(compressed_size);
unsigned char* uncompressed_buffer = (unsigned char*)malloc(original_size + 1);
memset(uncompressed_buffer, 0, original_size + 1);
@ -102,3 +99,29 @@ int Mnemonic::init(void(*fill_words_func)(unsigned char*), unsigned int original
free(buffer);
}
void Mnemonic::clear()
{
//Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
for (int i = 0; i < 2048; i++) {
if (mWords[i]) {
free(mWords[i]);
}
}
memset(mWords, 0, 2048);
mWordHashIndices.clear();
}
std::string Mnemonic::getCompleteWordList()
{
std::string result("");
for (int i = 0; i < 2048; i++) {
if (mWords[i]) {
result += std::to_string(i) + ": " + mWords[i] + "\n";
}
else {
result += std::to_string(i) + ": <word empty>\n";
}
}
return result;
}

View File

@ -12,6 +12,7 @@
*/
#include "../lib/DRHashList.h"
#include "Poco/Mutex.h"
#include <string>
#include <map>
@ -26,15 +27,22 @@ public:
int init(void(*fill_words_func)(unsigned char*), unsigned int original_size, unsigned int compressed_size);
inline const char* getWord(unsigned int index) { if (index < 2048) return mWords[index]; return nullptr; }
inline unsigned short getWordIndex(const char* word) { DHASH word_hash = DRMakeStringHash(word); return mWordHashIndices.find(word_hash)->second; }
inline bool isWordExist(const std::string& word) { DHASH word_hash = DRMakeStringHash(word.data()); return mWordHashIndices.find(word_hash) != mWordHashIndices.end(); }
inline const char* getWord(unsigned int index) { Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500); if (index < 2048 && index >= 0) return mWords[index]; return nullptr; }
inline unsigned short getWordIndex(const char* word) { Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500); DHASH word_hash = DRMakeStringHash(word); return mWordHashIndices.find(word_hash)->second; }
inline bool isWordExist(const std::string& word) { Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500); DHASH word_hash = DRMakeStringHash(word.data()); return mWordHashIndices.find(word_hash) != mWordHashIndices.end(); }
// using only for debugging
std::string getCompleteWordList();
protected:
void clear();
char* mWords[2048];
//DRHashList mWordHashIndices;
typedef std::pair<DHASH, unsigned short> WordHashEntry;
std::map<DHASH, unsigned short> mWordHashIndices;
Poco::Mutex mWorkingMutex;
};
#endif //DR_MNEMONIC_H

File diff suppressed because one or more lines are too long

View File

@ -148,6 +148,8 @@ int Gradido_LoginServer::main(const std::vector<std::string>& args)
errorLog.error("[Gradido_LoginServer::main] error loading mnemonic Word List");
return Application::EXIT_CONFIG;
}
//printf("show mnemonic list: \n");
//printf(ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER].getCompleteWordList().data());
if (!ServerConfig::initServerCrypto(config())) {
//printf("[Gradido_LoginServer::%s] error init server crypto\n", __FUNCTION__);
errorLog.error("[Gradido_LoginServer::main] error init server crypto");

View File

@ -53,6 +53,7 @@ namespace ServerConfig {
bool initServerCrypto(const Poco::Util::LayeredConfiguration& cfg);
bool initEMailAccount(const Poco::Util::LayeredConfiguration& cfg);
bool initSSLClientContext();
void writeToFile(std::istream& datas, std::string fileName);

View File

@ -28,7 +28,7 @@ int main(int argc, char** argv)
testUser->validatePwd("haz27Newpassword", nullptr);
ServerConfig::g_FakeLoginSleepTime = (int)std::round(timeUsed.millis());
delete testUser;
Gradido_LoginServer app;
return app.run(argc, argv);

View File

@ -740,6 +740,7 @@ bool Session::useOrGeneratePassphrase(const std::string& passphase)
bool Session::generatePassphrase()
{
mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER]);
updateState(SESSION_STATE_PASSPHRASE_GENERATED);
return true;
}

View File

@ -3,11 +3,13 @@
#include <sodium.h>
#include "ed25519/ed25519.h"
#include "Poco/Util/Application.h"
#include "Poco/RegularExpression.h"
#include "../ServerConfig.h"
#include "../SingletonManager/ConnectionManager.h"
#include "../SingletonManager/ErrorManager.h"
#include "../SingletonManager/SessionManager.h"
#include "../SingletonManager/LanguageManager.h"
#include "Poco/Data/Binding.h"
@ -46,7 +48,11 @@ int UserGenerateKeys::run()
{
// always return true, cannot fail (only if low on memory)
mKeys.generateFromPassphrase(mPassphrase.data(), &ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER]);
// !!! update: can no fail, if passphrase is invalid, for example if memory is corrupted
if (!mKeys.generateFromPassphrase(mPassphrase.data(), &ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER])) {
mUser->addError(new Error(mUser->gettext("User generate Keys"), mUser->gettext("invalid passphrase, please notice the server admin coin@gradido.net")));
return -1;
}
mUser->setPublicKeyHex(mKeys.getPubkeyHex());
mUser->setPublicKey(mKeys.getPublicKey());
@ -183,6 +189,7 @@ User::User(const char* email, const char* first_name, const char* last_name)
mLanguage(LANG_DE), mGradidoCurrentBalance(0), mCryptoKey(nullptr), mReferenceCount(1)
{
memset(mPublicKey, 0, crypto_sign_PUBLICKEYBYTES);
mLanguageCatalog = LanguageManager::getInstance()->getFreeCatalog(mLanguage);
}
// load from db
User::User(const char* email)
@ -210,6 +217,8 @@ User::User(const char* email)
if (result == 1) {
mState = USER_LOADED_FROM_DB;
mLanguage = LanguageManager::languageFromString(language_key);
mLanguageCatalog = LanguageManager::getInstance()->getFreeCatalog(mLanguage);
if (email_checked == 0) { mState = USER_EMAIL_NOT_ACTIVATED;}
else if (pubkey.isNull()) { mState = USER_NO_KEYS;}
else if (privkey.isNull()) { mState = USER_NO_PRIVATE_KEY; }
@ -269,6 +278,8 @@ User::User(int user_id)
if (result == 1) {
mState = USER_LOADED_FROM_DB;
mLanguage = LanguageManager::languageFromString(language_key);
mLanguageCatalog = LanguageManager::getInstance()->getFreeCatalog(mLanguage);
if (email_checked == 0) { mState = USER_EMAIL_NOT_ACTIVATED; }
else if (pubkey.isNull()) { mState = USER_NO_KEYS; }
else if (privkey.isNull()) { mState = USER_NO_PRIVATE_KEY; }
@ -330,6 +341,8 @@ User::User(const unsigned char* pubkey_array)
if (result == 1) {
mState = USER_LOADED_FROM_DB;
mLanguage = LanguageManager::languageFromString(language_key);
mLanguageCatalog = LanguageManager::getInstance()->getFreeCatalog(mLanguage);
if (email_checked == 0) { mState = USER_EMAIL_NOT_ACTIVATED; }
else if (privkey.isNull()) { mState = USER_NO_PRIVATE_KEY; }
else { mState = USER_COMPLETE; }
@ -378,25 +391,59 @@ User::~User()
}
}
void User::setLanguage(Languages lang)
{
lock("User::setLanguage");
if (mLanguage != lang) {
mLanguageCatalog = LanguageManager::getInstance()->getFreeCatalog(lang);
}
mLanguage = lang;
unlock();
}
std::string User::generateNewPassphrase(Mnemonic* word_source)
{
auto em = ErrorManager::getInstance();
static const char* errorMessageForUser = "Ein Fehler, bitte wende dich an den Server-Admin (coin@gradido.net). | An error occured, please ask the server admin (coin@gradido.net).";
unsigned int random_indices[PHRASE_WORD_COUNT];
unsigned int str_sizes[PHRASE_WORD_COUNT];
unsigned int phrase_buffer_size = 0;
bool errorReloadingMnemonicWordList = false;
int loopTrys = 0;
Poco::RegularExpression checkValidWord("^[a-zäöüß]*$");
// TODO: make sure words didn't double
for (int i = 0; i < PHRASE_WORD_COUNT; i++) {
random_indices[i] = randombytes_random() % 2048;
auto word = word_source->getWord(random_indices[i]);
if (loopTrys > 10 || errorReloadingMnemonicWordList) {
return errorMessageForUser;
}
if (!word) {
em->addError(new ParamError("User::generateNewPassphrase", "empty word get for index", random_indices[i]));
em->sendErrorsAsEmail();
random_indices[i] = randombytes_random() % 2048;
word = word_source->getWord(random_indices[i]);
if (!word) return "Ein Fehler, bitte wende dich an den Server-Admin.";
if (!word) return errorMessageForUser;
}
else {
if (!checkValidWord.match(word, 0, Poco::RegularExpression::RE_NOTEMPTY)) {
em->addError(new ParamError("User::generateNewPassphrase", "invalid word", word));
em->addError(new Error("User::generateNewPassphrase", "try to reload mnemonic word list, but this error is maybe evidence for a serious memory problem!!!"));
if (!ServerConfig::loadMnemonicWordLists()) {
em->addError(new Error("User::generateNewPassphrase", "error reloading mnemonic word lists"));
errorReloadingMnemonicWordList = true;
}
else {
i = 0;
loopTrys++;
}
em->sendErrorsAsEmail();
//return "Server Fehler, bitte frage den Admin coin@gradido.net | Server error, please ask the admin coin@gradido.net";
}
}
str_sizes[i] = strlen(word);
phrase_buffer_size += str_sizes[i];
@ -423,7 +470,7 @@ bool User::validatePassphrase(const std::string& passphrase)
std::vector<std::string> results(std::istream_iterator<std::string>{iss},
std::istream_iterator<std::string>());
for (int i = 0; i < ServerConfig::Mnemonic_Types::MNEMONIC_MAX; i++) {
auto m = ServerConfig::g_Mnemonic_WordLists[i];
Mnemonic& m = ServerConfig::g_Mnemonic_WordLists[i];
bool existAll = true;
for (auto it = results.begin(); it != results.end(); it++) {
if (!m.isWordExist(*it)) {

View File

@ -90,9 +90,11 @@ public:
inline void setPublicKeyHex(const std::string& publicKeyHex) { lock(); mPublicHex = publicKeyHex; unlock(); }
inline void setPublicKey(const unsigned char* key) { lock(); memcpy(mPublicKey, key, crypto_sign_PUBLICKEYBYTES); unlock();}
inline const char* gettext(const char* text) { if (mLanguageCatalog.isNull()) return text; return mLanguageCatalog->gettext(text); }
UserStates getUserState();
void setLanguage(Languages lang) { lock(); mLanguage = lang; unlock(); }
void setLanguage(Languages lang);
inline void setBalance(int balance) { lock(); mGradidoCurrentBalance = balance; unlock(); }
void setEmailChecked();
bool isEmptyPassword();
@ -154,6 +156,7 @@ private:
// ************************ DB FIELDS END ******************************
int mGradidoCurrentBalance;
Poco::AutoPtr<LanguageCatalog> mLanguageCatalog;
// crypto key as obfus array
// only in memory, if user has typed in password