From 8399161a8e9b4f0cf3a38e14f5c83e566aad21c2 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 21 May 2015 23:29:27 +0200 Subject: [PATCH] Nicer password dialog. Cleanups and fixes for secretstore and keyman. --- alethzero/CMakeLists.txt | 3 +- alethzero/GetPassword.ui | 123 ++++++++++++++++++++++++++++ alethzero/Main.ui | 8 +- alethzero/MainWin.cpp | 98 ++++++++++++++++++---- alethzero/MainWin.h | 3 +- alethzero/Transact.cpp | 27 +++++-- alethzero/Transact.h | 1 + libdevcore/Common.h | 3 + libdevcrypto/Common.cpp | 14 ++-- libdevcrypto/Common.h | 16 +++- libdevcrypto/SecretStore.cpp | 152 ++++++++++++++++++++++------------- libdevcrypto/SecretStore.h | 2 +- libethcore/ICAP.h | 2 - libethereum/AccountDiff.h | 2 +- libethereum/KeyManager.cpp | 14 +++- libethereum/KeyManager.h | 12 ++- 16 files changed, 380 insertions(+), 100 deletions(-) create mode 100644 alethzero/GetPassword.ui diff --git a/alethzero/CMakeLists.txt b/alethzero/CMakeLists.txt index 41d9ea10f..595668cd1 100644 --- a/alethzero/CMakeLists.txt +++ b/alethzero/CMakeLists.txt @@ -23,6 +23,7 @@ qt5_wrap_ui(ui_Connect.h Connect.ui) qt5_wrap_ui(ui_Debugger.h Debugger.ui) qt5_wrap_ui(ui_Transact.h Transact.ui) qt5_wrap_ui(ui_ExportState.h ExportState.ui) +qt5_wrap_ui(ui_GetPassword.h GetPassword.ui) file(GLOB HEADERS "*.h") @@ -35,7 +36,7 @@ endif () # eth_add_executable is defined in cmake/EthExecutableHelper.cmake eth_add_executable(${EXECUTABLE} ICON alethzero - UI_RESOURCES alethzero.icns Main.ui Connect.ui Debugger.ui Transact.ui ExportState.ui + UI_RESOURCES alethzero.icns Main.ui Connect.ui Debugger.ui Transact.ui ExportState.ui GetPassword.ui WIN_RESOURCES alethzero.rc ) diff --git a/alethzero/GetPassword.ui b/alethzero/GetPassword.ui new file mode 100644 index 000000000..753bca565 --- /dev/null +++ b/alethzero/GetPassword.ui @@ -0,0 +1,123 @@ + + + GetPassword + + + + 0 + 0 + 400 + 187 + + + + Enter Password + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Qt::RichText + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QLineEdit::Password + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + GetPassword + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + GetPassword + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/alethzero/Main.ui b/alethzero/Main.ui index b97ed08ed..ecdc07ab6 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -159,6 +159,7 @@ + @@ -1754,7 +1755,12 @@ font-size: 14pt - &Re-encrypt Key + &Re-Encrypt Key + + + + + Re-Encrypt All Keys... diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index a2d7a3dde..1dcdcb67f 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -75,6 +75,7 @@ #include "WebPage.h" #include "ExportState.h" #include "ui_Main.h" +#include "ui_GetPassword.h" using namespace std; using namespace dev; using namespace dev::p2p; @@ -467,7 +468,9 @@ void Main::load(QString _s) void Main::on_newTransaction_triggered() { m_transact.setEnvironment(m_keyManager.accounts(), ethereum(), &m_natSpecDB); - m_transact.exec(); + m_transact.setWindowFlags(Qt::Dialog); + m_transact.setWindowModality(Qt::WindowModal); + m_transact.show(); } void Main::on_loadJS_triggered() @@ -698,12 +701,17 @@ Secret Main::retrieveSecret(Address const& _a) const auto info = m_keyManager.accountDetails()[_a]; while (true) { - if (Secret s = m_keyManager.secret(_a, [&](){ - return QInputDialog::getText(const_cast(this), "Import Account Key", QString("Enter the password for the account %2 (%1). The hint is:\n%3").arg(QString::fromStdString(_a.abridged())).arg(QString::fromStdString(info.first)).arg(QString::fromStdString(info.second)), QLineEdit::Password).toStdString(); - })) + Secret s = m_keyManager.secret(_a, [&](){ + QDialog d; + Ui_GetPassword gp; + gp.setupUi(&d); + d.setWindowTitle("Unlock Account"); + gp.label->setText(QString("Enter the password for the account %2 (%1).").arg(QString::fromStdString(_a.abridged())).arg(QString::fromStdString(info.first))); + gp.entry->setPlaceholderText("Hint: " + QString::fromStdString(info.second)); + return d.exec() == QDialog::Accepted ? gp.entry->text().toStdString() : string(); + }); + if (s || QMessageBox::warning(nullptr, "Unlock Account", "The password you gave is incorrect for this key.", QMessageBox::Retry, QMessageBox::Cancel) == QMessageBox::Cancel) return s; - else if (QMessageBox::warning(const_cast(this), "Incorrect Password", "The password you gave is incorrect for this key.", QMessageBox::Retry, QMessageBox::Cancel) == QMessageBox::Cancel) - return Secret(); } } @@ -771,17 +779,32 @@ void Main::readSettings(bool _skipGeometry) on_urlEdit_returnPressed(); } -std::string Main::getPassword(std::string const& _title, std::string const& _for) +std::string Main::getPassword(std::string const& _title, std::string const& _for, std::string* _hint, bool* _ok) { QString password; while (true) { - password = QInputDialog::getText(nullptr, QString::fromStdString(_title), QString::fromStdString(_for), QLineEdit::Password, QString()); + bool ok; + password = QInputDialog::getText(nullptr, QString::fromStdString(_title), QString::fromStdString(_for), QLineEdit::Password, QString(), &ok); + if (!ok) + { + if (_ok) + *_ok = false; + return string(); + } + if (password.isEmpty()) + break; QString confirm = QInputDialog::getText(nullptr, QString::fromStdString(_title), "Confirm this password by typing it again", QLineEdit::Password, QString()); if (password == confirm) break; QMessageBox::warning(nullptr, QString::fromStdString(_title), "You entered two different passwords - please enter the same password twice.", QMessageBox::Ok); } + + if (!password.isEmpty() && _hint && !m_keyManager.haveHint(password.toStdString())) + *_hint = QInputDialog::getText(this, "Create Account", "Enter a hint to help you remember this password.").toStdString(); + + if (_ok) + *_ok = true; return password.toStdString(); } @@ -797,8 +820,11 @@ void Main::on_importKey_triggered() QString s = QInputDialog::getText(this, "Import Account Key", "Enter this account's name"); if (QMessageBox::question(this, "Additional Security?", "Would you like to use additional security for this key? This lets you protect it with a different password to other keys, but also means you must re-enter the key's password every time you wish to use the account.", QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { - std::string password = getPassword("Import Account Key", "Enter the password you would like to use for this key. Don't forget it!"); - std::string hint = QInputDialog::getText(this, "Import Account Key", "Enter a hint to help you remember this password.").toStdString(); + bool ok; + std::string hint; + std::string password = getPassword("Import Account Key", "Enter the password you would like to use for this key. Don't forget it!", &hint, &ok); + if (!ok) + return; m_keyManager.import(k.secret(), s.toStdString(), password, hint); } else @@ -2003,8 +2029,11 @@ void Main::on_newAccount_triggered() QString s = QInputDialog::getText(this, "Create Account", "Enter this account's name"); if (QMessageBox::question(this, "Create Account", "Would you like to use additional security for this key? This lets you protect it with a different password to other keys, but also means you must re-enter the key's password every time you wish to use the account.", QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { - std::string password = getPassword("Create Account", "Enter the password you would like to use for this key. Don't forget it!"); - std::string hint = QInputDialog::getText(this, "Create Account", "Enter a hint to help you remember this password.").toStdString(); + bool ok = false; + std::string hint; + std::string password = getPassword("Create Account", "Enter the password you would like to use for this key. Don't forget it!", &hint, &ok); + if (!ok) + return; m_keyManager.import(p.secret(), s.toStdString(), password, hint); } else @@ -2043,11 +2072,48 @@ void Main::on_reencryptKey_triggered() auto hba = ui->ourAccounts->currentItem()->data(Qt::UserRole).toByteArray(); Address a((byte const*)hba.data(), Address::ConstructFromPointer); QStringList kdfs = {"PBKDF2-SHA256", "Scrypt"}; - QString kdf = QInputDialog::getItem(this, "Re-Encrypt Key", "Select a key derivation function to use for storing your key:", kdfs); - m_keyManager.reencode(a, [&](){ - return QInputDialog::getText(nullptr, "Re-Encrypt Key", "Enter the password for this key to re-encrypt it.", QLineEdit::Password, QString()).toStdString(); - }, (KDF)kdfs.indexOf(kdf)); + bool ok = true; + KDF kdf = (KDF)kdfs.indexOf(QInputDialog::getItem(this, "Re-Encrypt Key", "Select a key derivation function to use for storing your key:", kdfs, kdfs.size() - 1, false, &ok)); + if (!ok) + return; + std::string hint; + std::string password = getPassword("Create Account", "Enter the password you would like to use for this key. Don't forget it!\nEnter nothing to use your Master password.", &hint, &ok); + if (!ok) + return; + try { + auto pw = [&](){ + auto p = QInputDialog::getText(this, "Re-Encrypt Key", "Enter the original password for this key.\nHint: " + QString::fromStdString(m_keyManager.hint(a)), QLineEdit::Password, QString()).toStdString(); + if (p.empty()) + throw UnknownPassword(); + return p; + }; + while (!(password.empty() ? m_keyManager.recode(a, SemanticPassword::Master, pw, kdf) : m_keyManager.recode(a, password, hint, pw, kdf))) + if (QMessageBox::question(this, "Re-Encrypt Key", "Password given is incorrect. Would you like to try again?", QMessageBox::Retry, QMessageBox::Cancel) == QMessageBox::Cancel) + return; + } + catch (UnknownPassword&) {} + } +} + +void Main::on_reencryptAll_triggered() +{ + QStringList kdfs = {"PBKDF2-SHA256", "Scrypt"}; + bool ok = false; + QString kdf = QInputDialog::getItem(this, "Re-Encrypt Key", "Select a key derivation function to use for storing your keys:", kdfs, kdfs.size() - 1, false, &ok); + if (!ok) + return; + try { + for (Address const& a: m_keyManager.accounts()) + while (!m_keyManager.recode(a, SemanticPassword::Existing, [&](){ + auto p = QInputDialog::getText(nullptr, "Re-Encrypt Key", QString("Enter the original password for key %1.\nHint: %2").arg(QString::fromStdString(pretty(a))).arg(QString::fromStdString(m_keyManager.hint(a))), QLineEdit::Password, QString()).toStdString(); + if (p.empty()) + throw UnknownPassword(); + return p; + }, (KDF)kdfs.indexOf(kdf))) + if (QMessageBox::question(this, "Re-Encrypt Key", "Password given is incorrect. Would you like to try again?", QMessageBox::Retry, QMessageBox::Cancel) == QMessageBox::Cancel) + return; } + catch (UnknownPassword&) {} } void Main::on_go_triggered() diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 6628c649b..0e74d8f69 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -138,6 +138,7 @@ private slots: void on_killAccount_triggered(); void on_importKey_triggered(); void on_reencryptKey_triggered(); + void on_reencryptAll_triggered(); void on_importKeyFile_triggered(); void on_claimPresale_triggered(); void on_exportKey_triggered(); @@ -248,7 +249,7 @@ private: void refreshBalances(); void setBeneficiary(dev::Address const& _b); - std::string getPassword(std::string const& _title, std::string const& _for); + std::string getPassword(std::string const& _title, std::string const& _for, std::string* _hint = nullptr, bool* _ok = nullptr); std::unique_ptr ui; diff --git a/alethzero/Transact.cpp b/alethzero/Transact.cpp index 1336c3f05..fcca822fe 100644 --- a/alethzero/Transact.cpp +++ b/alethzero/Transact.cpp @@ -76,6 +76,7 @@ void Transact::setEnvironment(AddressHash const& _accounts, dev::eth::Client* _e m_ethereum = _eth; m_natSpecDB = _natSpecDB; + auto old = ui->from->currentIndex(); ui->from->clear(); for (auto const& i: m_accounts) { @@ -84,6 +85,10 @@ void Transact::setEnvironment(AddressHash const& _accounts, dev::eth::Client* _e QString s = QString("%4 %2: %1").arg(formatBalance(b).c_str()).arg(QString::fromStdString(m_context->render(i))).arg(QString::fromStdString(d.first)); ui->from->addItem(s); } + if (old > -1 && old < ui->from->count()) + ui->from->setCurrentIndex(old); + else if (ui->from->count()) + ui->from->setCurrentIndex(0); } bool Transact::isCreation() const @@ -301,6 +306,9 @@ void Transact::rejigData() // Determine how much balance we have to play with... //findSecret(value() + ethereum()->gasLimitRemaining() * gasPrice()); auto s = fromAccount(); + if (!s) + return; + auto b = ethereum()->balanceAt(s, PendingBlock); m_allGood = true; @@ -344,7 +352,7 @@ void Transact::rejigData() if (b < value() + baseGas * gasPrice()) { // Not enough - bail. - bail("
ERROR No single account contains enough for paying even the basic amount of gas required.
"); + bail("
ERROR Account doesn't contain enough for paying even the basic amount of gas required.
"); return; } else @@ -417,6 +425,8 @@ Secret Transact::findSecret(u256 _totalReq) const Address Transact::fromAccount() { + if (ui->from->currentIndex() < 0 || ui->from->currentIndex() >= (int)m_accounts.size()) + return Address(); auto it = m_accounts.begin(); std::advance(it, ui->from->currentIndex()); return *it; @@ -425,14 +435,19 @@ Address Transact::fromAccount() void Transact::on_send_clicked() { // Secret s = findSecret(value() + fee()); - Secret s = m_context->retrieveSecret(fromAccount()); - auto b = ethereum()->balanceAt(KeyPair(s).address(), PendingBlock); - if (!s || b < value() + fee()) + auto a = fromAccount(); + auto b = ethereum()->balanceAt(a, PendingBlock); + + if (!a || b < value() + fee()) { - QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount."); + QMessageBox::critical(nullptr, "Transaction Failed", "Couldn't make transaction: account doesn't contain at least the required amount.", QMessageBox::Ok); return; } + Secret s = m_context->retrieveSecret(a); + if (!s) + return; + if (isCreation()) { // If execution is a contract creation, add Natspec to @@ -467,7 +482,7 @@ void Transact::on_debug_clicked() auto b = ethereum()->balanceAt(from, PendingBlock); if (!from || b < value() + fee()) { - QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount."); + QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: account doesn't contain at least the required amount."); return; } diff --git a/alethzero/Transact.h b/alethzero/Transact.h index cd62c0e20..8c079a2fe 100644 --- a/alethzero/Transact.h +++ b/alethzero/Transact.h @@ -44,6 +44,7 @@ public: void setEnvironment(dev::AddressHash const& _accounts, dev::eth::Client* _eth, NatSpecFace* _natSpecDB); private slots: + void on_from_currentIndexChanged(int) { rejigData(); } void on_destination_currentTextChanged(QString); void on_value_valueChanged(int) { updateFee(); } void on_gas_valueChanged(int) { updateFee(); } diff --git a/libdevcore/Common.h b/libdevcore/Common.h index ac4d89103..ae98861c1 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #pragma warning(push) @@ -63,6 +64,8 @@ namespace dev extern char const* Version; +static const std::string EmptyString; + // Binary data types. using bytes = std::vector; using bytesRef = vector_ref; diff --git a/libdevcrypto/Common.cpp b/libdevcrypto/Common.cpp index 2a3561314..814f8309e 100644 --- a/libdevcrypto/Common.cpp +++ b/libdevcrypto/Common.cpp @@ -120,10 +120,11 @@ std::pair dev::encryptSymNoAuth(h128 const& _k, bytesConstRef _plai return make_pair(encryptSymNoAuth(_k, iv, _plain), iv); } -bytes dev::encryptSymNoAuth(h128 const& _k, h128 const& _iv, bytesConstRef _plain) +bytes dev::encryptAES128CTR(bytesConstRef _k, h128 const& _iv, bytesConstRef _plain) { - const int c_aesKeyLen = 16; - SecByteBlock key(_k.data(), c_aesKeyLen); + if (_k.size() != 16 && _k.size() != 24 && _k.size() != 32) + return bytes(); + SecByteBlock key(_k.data(), _k.size()); try { CTR_Mode::Encryption e; @@ -139,10 +140,11 @@ bytes dev::encryptSymNoAuth(h128 const& _k, h128 const& _iv, bytesConstRef _plai } } -bytes dev::decryptSymNoAuth(h128 const& _k, h128 const& _iv, bytesConstRef _cipher) +bytes dev::decryptAES128CTR(bytesConstRef _k, h128 const& _iv, bytesConstRef _cipher) { - const size_t c_aesKeyLen = 16; - SecByteBlock key(_k.data(), c_aesKeyLen); + if (_k.size() != 16 && _k.size() != 24 && _k.size() != 32) + return bytes(); + SecByteBlock key(_k.data(), _k.size()); try { CTR_Mode::Decryption d; diff --git a/libdevcrypto/Common.h b/libdevcrypto/Common.h index a0f894a25..10bcdd067 100644 --- a/libdevcrypto/Common.h +++ b/libdevcrypto/Common.h @@ -98,18 +98,26 @@ bool decryptSym(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext); /// Encrypt payload using ECIES standard with AES128-CTR. void encryptECIES(Public const& _k, bytesConstRef _plain, bytes& o_cipher); - + /// Decrypt payload using ECIES standard with AES128-CTR. bool decryptECIES(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext); - + /// Encrypts payload with random IV/ctr using AES128-CTR. std::pair encryptSymNoAuth(h128 const& _k, bytesConstRef _plain); /// Encrypts payload with specified IV/ctr using AES128-CTR. -bytes encryptSymNoAuth(h128 const& _k, h128 const& _iv, bytesConstRef _plain); +bytes encryptAES128CTR(bytesConstRef _k, h128 const& _iv, bytesConstRef _plain); + +/// Decrypts payload with specified IV/ctr using AES128-CTR. +bytes decryptAES128CTR(bytesConstRef _k, h128 const& _iv, bytesConstRef _cipher); + +/// Encrypts payload with specified IV/ctr using AES128-CTR. +inline bytes encryptSymNoAuth(h128 const& _k, h128 const& _iv, bytesConstRef _plain) { return encryptAES128CTR(_k.ref(), _iv, _plain); } +inline bytes encryptSymNoAuth(h256 const& _k, h128 const& _iv, bytesConstRef _plain) { return encryptAES128CTR(_k.ref(), _iv, _plain); } /// Decrypts payload with specified IV/ctr using AES128-CTR. -bytes decryptSymNoAuth(h128 const& _k, h128 const& _iv, bytesConstRef _cipher); +inline bytes decryptSymNoAuth(h128 const& _k, h128 const& _iv, bytesConstRef _cipher) { return decryptAES128CTR(_k.ref(), _iv, _cipher); } +inline bytes decryptSymNoAuth(h256 const& _k, h128 const& _iv, bytesConstRef _cipher) { return decryptAES128CTR(_k.ref(), _iv, _cipher); } /// Recovers Public key from signed message hash. Public recover(Signature const& _sig, h256 const& _hash); diff --git a/libdevcrypto/SecretStore.cpp b/libdevcrypto/SecretStore.cpp index 6a975525b..982b7bae6 100644 --- a/libdevcrypto/SecretStore.cpp +++ b/libdevcrypto/SecretStore.cpp @@ -34,6 +34,56 @@ using namespace dev; namespace js = json_spirit; namespace fs = boost::filesystem; +static const int c_keyFileVersion = 3; + +static js::mValue upgraded(std::string const& _s) +{ + js::mValue v; + js::read_string(_s, v); + if (v.type() != js::obj_type) + return js::mValue(); + js::mObject ret = v.get_obj(); + unsigned version = ret.count("Version") ? stoi(ret["Version"].get_str()) : ret.count("version") ? ret["version"].get_int() : 0; + if (version == 1) + { + // upgrade to version 2 + js::mObject old; + swap(old, ret); + + ret["id"] = old["Id"]; + js::mObject c; + c["ciphertext"] = old["Crypto"].get_obj()["CipherText"]; + c["cipher"] = "aes-128-cbc"; + { + js::mObject cp; + cp["iv"] = old["Crypto"].get_obj()["IV"]; + c["cipherparams"] = cp; + } + c["kdf"] = old["Crypto"].get_obj()["KeyHeader"].get_obj()["Kdf"]; + { + js::mObject kp; + kp["salt"] = old["Crypto"].get_obj()["Salt"]; + for (auto const& i: old["Crypto"].get_obj()["KeyHeader"].get_obj()["KdfParams"].get_obj()) + if (i.first != "SaltLen") + kp[boost::to_lower_copy(i.first)] = i.second; + c["kdfparams"] = kp; + } + c["sillymac"] = old["Crypto"].get_obj()["MAC"]; + c["sillymacjson"] = _s; + ret["crypto"] = c; + version = 2; + } + if (version == 2) + { + ret["crypto"].get_obj()["cipher"] = "aes-128-ctr"; + ret["crypto"].get_obj()["compat"] = "2"; + version = 3; + } + if (version == c_keyFileVersion) + return ret; + return js::mValue(); +} + SecretStore::SecretStore() { load(); @@ -45,6 +95,7 @@ SecretStore::~SecretStore() bytes SecretStore::secret(h128 const& _uuid, function const& _pass, bool _useCache) const { + (void)_pass; auto rit = m_cached.find(_uuid); if (_useCache && rit != m_cached.end()) return rit->second; @@ -94,7 +145,7 @@ void SecretStore::save(std::string const& _keysPath) js::read_string(k.second.first, crypto); v["crypto"] = crypto; v["id"] = uuid; - v["version"] = 2; + v["version"] = c_keyFileVersion; writeFile(filename, js::write_string(js::mValue(v), true)); if (!k.second.second.empty() && k.second.second != filename) boost::filesystem::remove(k.second.second); @@ -102,48 +153,6 @@ void SecretStore::save(std::string const& _keysPath) } } -static js::mValue upgraded(std::string const& _s) -{ - js::mValue v; - js::read_string(_s, v); - if (v.type() != js::obj_type) - return js::mValue(); - js::mObject ret = v.get_obj(); - unsigned version = ret.count("Version") ? stoi(ret["Version"].get_str()) : ret.count("version") ? ret["version"].get_int() : 0; - if (version == 1) - { - // upgrade to version 2 - js::mObject old; - swap(old, ret); - - ret["id"] = old["Id"]; - js::mObject c; - c["ciphertext"] = old["Crypto"].get_obj()["CipherText"]; - c["cipher"] = "aes-128-cbc"; - { - js::mObject cp; - cp["iv"] = old["Crypto"].get_obj()["IV"]; - c["cipherparams"] = cp; - } - c["kdf"] = old["Crypto"].get_obj()["KeyHeader"].get_obj()["Kdf"]; - { - js::mObject kp; - kp["salt"] = old["Crypto"].get_obj()["Salt"]; - for (auto const& i: old["Crypto"].get_obj()["KeyHeader"].get_obj()["KdfParams"].get_obj()) - if (i.first != "SaltLen") - kp[boost::to_lower_copy(i.first)] = i.second; - c["kdfparams"] = kp; - } - c["sillymac"] = old["Crypto"].get_obj()["MAC"]; - c["sillymacjson"] = _s; - ret["crypto"] = c; - version = 2; - } - if (version == 2) - return ret; - return js::mValue(); -} - void SecretStore::load(std::string const& _keysPath) { fs::path p(_keysPath); @@ -169,10 +178,17 @@ h128 SecretStore::readKey(std::string const& _file, bool _deleteFile) return h128(); } -void SecretStore::recode(h128 const& _uuid, string const& _pass, KDF _kdf) +bool SecretStore::recode(h128 const& _uuid, string const& _newPass, std::function const& _pass, KDF _kdf) { - m_keys[_uuid].first = encrypt(secret(_uuid, [&](){ return _pass; }), _pass, _kdf); + cdebug << "recode:" << toUUID(_uuid); + cdebug << "newPass:" << _newPass; + bytes s = secret(_uuid, _pass, true); + if (s.empty()) + return false; + cdebug << "secret:" << toHex(s); + m_keys[_uuid].first = encrypt(s, _newPass, _kdf); save(); + return true; } std::string SecretStore::encrypt(bytes const& _v, std::string const& _pass, KDF _kdf) @@ -180,25 +196,26 @@ std::string SecretStore::encrypt(bytes const& _v, std::string const& _pass, KDF js::mObject ret; // KDF info - unsigned dklen = 16; + unsigned dklen = 32; bytes salt = h256::random().asBytes(); bytes derivedKey; if (_kdf == KDF::Scrypt) { unsigned iterations = 262144; - unsigned p = 262144; - unsigned r = 262144; + unsigned p = 1; + unsigned r = 8; ret["kdf"] = "scrypt"; { js::mObject params; - params["n"] = (int)iterations; - params["p"] = 1; - params["r"] = 8; + params["n"] = (int64_t)iterations; + params["p"] = (int)p; + params["r"] = (int)r; params["dklen"] = (int)dklen; params["salt"] = toHex(salt); ret["kdfparams"] = params; } derivedKey = scrypt(_pass, salt, iterations, p, r, dklen); + cdebug << "derivedKey" << toHex(derivedKey); } else { @@ -213,11 +230,13 @@ std::string SecretStore::encrypt(bytes const& _v, std::string const& _pass, KDF ret["kdfparams"] = params; } derivedKey = pbkdf2(_pass, salt, iterations, dklen); + cdebug << "derivedKey" << toHex(derivedKey); } // cipher info - ret["cipher"] = "aes-128-cbc"; - h128 key(sha3(h128(derivedKey, h128::AlignRight)), h128::AlignRight); + ret["cipher"] = "aes-128-ctr"; + h128 key(derivedKey, h128::AlignLeft); + cdebug << "cipherKey" << key.hex(); h128 iv = h128::random(); { js::mObject params; @@ -230,7 +249,9 @@ std::string SecretStore::encrypt(bytes const& _v, std::string const& _pass, KDF ret["ciphertext"] = toHex(cipherText); // and mac. - h256 mac = sha3(bytesConstRef(&derivedKey).cropped(derivedKey.size() - 16).toBytes() + cipherText); + h256 mac = sha3(ref(derivedKey).cropped(16, 16).toBytes() + cipherText); + cdebug << "macBody" << toHex(ref(derivedKey).cropped(16, 16).toBytes() + cipherText); + cdebug << "mac" << mac.hex(); ret["mac"] = toHex(mac.ref()); return js::write_string((js::mValue)ret, true); @@ -270,13 +291,23 @@ bytes SecretStore::decrypt(std::string const& _v, std::string const& _pass) return bytes(); } + if (derivedKey.size() < 32 && !(o.count("compat") && o["compat"].get_str() == "2")) + { + cwarn << "Derived key's length too short (<32 bytes)"; + return bytes(); + } + bytes cipherText = fromHex(o["ciphertext"].get_str()); // check MAC if (o.count("mac")) { h256 mac(o["mac"].get_str()); - h256 macExp = sha3(bytesConstRef(&derivedKey).cropped(derivedKey.size() - 16).toBytes() + cipherText); + h256 macExp; + if (o.count("compat") && o["compat"].get_str() == "2") + macExp = sha3(bytesConstRef(&derivedKey).cropped(derivedKey.size() - 16).toBytes() + cipherText); + else + macExp = sha3(bytesConstRef(&derivedKey).cropped(16, 16).toBytes() + cipherText); if (mac != macExp) { cwarn << "Invalid key - MAC mismatch; expected" << toString(macExp) << ", got" << toString(mac); @@ -297,12 +328,17 @@ bytes SecretStore::decrypt(std::string const& _v, std::string const& _pass) cwarn << "No MAC. Proceeding anyway."; // decrypt - if (o["cipher"].get_str() == "aes-128-cbc") + if (o["cipher"].get_str() == "aes-128-ctr") { auto params = o["cipherparams"].get_obj(); - h128 key(sha3(h128(derivedKey, h128::AlignRight)), h128::AlignRight); h128 iv(params["iv"].get_str()); - return decryptSymNoAuth(key, iv, &cipherText); + if (o.count("compat") && o["compat"].get_str() == "2") + { + h128 key(sha3(h128(derivedKey, h128::AlignRight)), h128::AlignRight); + return decryptSymNoAuth(key, iv, &cipherText); + } + else + return decryptSymNoAuth(h128(derivedKey, h128::AlignLeft), iv, &cipherText); } else { diff --git a/libdevcrypto/SecretStore.h b/libdevcrypto/SecretStore.h index 6e66ce967..6a62147b8 100644 --- a/libdevcrypto/SecretStore.h +++ b/libdevcrypto/SecretStore.h @@ -44,7 +44,7 @@ public: bytes secret(h128 const& _uuid, std::function const& _pass, bool _useCache = true) const; h128 importKey(std::string const& _file) { auto ret = readKey(_file, false); if (ret) save(); return ret; } h128 importSecret(bytes const& _s, std::string const& _pass); - void recode(h128 const& _uuid, std::string const& _pass, KDF _kdf = KDF::Scrypt); + bool recode(h128 const& _uuid, std::string const& _newPass, std::function const& _pass, KDF _kdf = KDF::Scrypt); void kill(h128 const& _uuid); // Clear any cached keys. diff --git a/libethcore/ICAP.h b/libethcore/ICAP.h index b4229e1f5..a2456bd40 100644 --- a/libethcore/ICAP.h +++ b/libethcore/ICAP.h @@ -38,8 +38,6 @@ namespace eth struct InvalidICAP: virtual public dev::Exception {}; -static const std::string EmptyString; - /** * @brief Encapsulation of an ICAP address. * Can be encoded, decoded, looked-up and inspected. diff --git a/libethereum/AccountDiff.h b/libethereum/AccountDiff.h index 22107b958..dd494c0a5 100644 --- a/libethereum/AccountDiff.h +++ b/libethereum/AccountDiff.h @@ -62,7 +62,7 @@ struct AccountDiff Diff exist; ///< The account's existance; was it created/deleted or not? Diff balance; ///< The account's balance; did it alter? Diff nonce; ///< The account's nonce; did it alter? - std::unordered_map> storage; ///< The account's storage addresses; each has its own Diff. + std::map> storage; ///< The account's storage addresses; each has its own Diff. Diff code; ///< The account's code; in general this should only have changed if exist also changed. }; diff --git a/libethereum/KeyManager.cpp b/libethereum/KeyManager.cpp index 687e13991..4a03d8435 100644 --- a/libethereum/KeyManager.cpp +++ b/libethereum/KeyManager.cpp @@ -49,10 +49,20 @@ void KeyManager::create(std::string const& _pass) write(_pass, m_keysFile); } -void KeyManager::reencode(Address const& _address, std::function const& _pass, KDF _kdf) +bool KeyManager::recode(Address const& _address, std::string const& _newPass, std::string const& _hint, std::function const& _pass, KDF _kdf) +{ + noteHint(_newPass, _hint); + return store().recode(uuid(_address), _newPass, _pass, _kdf); +} + +bool KeyManager::recode(Address const& _address, SemanticPassword _newPass, std::function const& _pass, KDF _kdf) { h128 u = uuid(_address); - store().recode(u, getPassword(u, _pass), _kdf); + if (_newPass == SemanticPassword::Existing) + return store().recode(u, getPassword(u, _pass), _pass, _kdf); + else if (_newPass == SemanticPassword::Master) + return store().recode(u, defaultPassword(), _pass, _kdf); + return false; } bool KeyManager::load(std::string const& _pass) diff --git a/libethereum/KeyManager.h b/libethereum/KeyManager.h index 2cacf7bfe..2fc47e9b5 100644 --- a/libethereum/KeyManager.h +++ b/libethereum/KeyManager.h @@ -42,6 +42,12 @@ struct KeyInfo static const auto DontKnowThrow = [](){ throw UnknownPassword(); return std::string(); }; +enum class SemanticPassword +{ + Existing, + Master +}; + // TODO: This one is specifically for Ethereum, but we can make it generic in due course. // TODO: hidden-partition style key-store. /** @@ -67,9 +73,12 @@ public: void save(std::string const& _pass) const { write(_pass, m_keysFile); } void notePassword(std::string const& _pass) { m_cachedPasswords[hashPassword(_pass)] = _pass; } + void noteHint(std::string const& _pass, std::string const& _hint) { if (!_hint.empty()) m_passwordInfo[hashPassword(_pass)] = _hint; } + bool haveHint(std::string const& _pass) const { auto h = hashPassword(_pass); return m_cachedPasswords.count(h) && !m_cachedPasswords.at(h).empty(); } AddressHash accounts() const; std::unordered_map> accountDetails() const; + std::string const& hint(Address const& _a) const { try { return m_passwordInfo.at(m_keyInfo.at(m_addrLookup.at(_a)).passHash); } catch (...) { return EmptyString; } } h128 uuid(Address const& _a) const; Address address(h128 const& _uuid) const; @@ -84,7 +93,8 @@ public: Secret secret(Address const& _address, std::function const& _pass = DontKnowThrow) const; Secret secret(h128 const& _uuid, std::function const& _pass = DontKnowThrow) const; - void reencode(Address const& _address, std::function const& _pass = DontKnowThrow, KDF _kdf = KDF::Scrypt); + bool recode(Address const& _address, SemanticPassword _newPass, std::function const& _pass = DontKnowThrow, KDF _kdf = KDF::Scrypt); + bool recode(Address const& _address, std::string const& _newPass, std::string const& _hint, std::function const& _pass = DontKnowThrow, KDF _kdf = KDF::Scrypt); void kill(h128 const& _id) { kill(address(_id)); } void kill(Address const& _a);