Browse Source

Nicer password dialog.

Cleanups and fixes for secretstore and keyman.
cl-refactor
Gav Wood 10 years ago
parent
commit
8399161a8e
  1. 3
      alethzero/CMakeLists.txt
  2. 123
      alethzero/GetPassword.ui
  3. 8
      alethzero/Main.ui
  4. 98
      alethzero/MainWin.cpp
  5. 3
      alethzero/MainWin.h
  6. 27
      alethzero/Transact.cpp
  7. 1
      alethzero/Transact.h
  8. 3
      libdevcore/Common.h
  9. 14
      libdevcrypto/Common.cpp
  10. 12
      libdevcrypto/Common.h
  11. 152
      libdevcrypto/SecretStore.cpp
  12. 2
      libdevcrypto/SecretStore.h
  13. 2
      libethcore/ICAP.h
  14. 2
      libethereum/AccountDiff.h
  15. 14
      libethereum/KeyManager.cpp
  16. 12
      libethereum/KeyManager.h

3
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_Debugger.h Debugger.ui)
qt5_wrap_ui(ui_Transact.h Transact.ui) qt5_wrap_ui(ui_Transact.h Transact.ui)
qt5_wrap_ui(ui_ExportState.h ExportState.ui) qt5_wrap_ui(ui_ExportState.h ExportState.ui)
qt5_wrap_ui(ui_GetPassword.h GetPassword.ui)
file(GLOB HEADERS "*.h") file(GLOB HEADERS "*.h")
@ -35,7 +36,7 @@ endif ()
# eth_add_executable is defined in cmake/EthExecutableHelper.cmake # eth_add_executable is defined in cmake/EthExecutableHelper.cmake
eth_add_executable(${EXECUTABLE} eth_add_executable(${EXECUTABLE}
ICON alethzero 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 WIN_RESOURCES alethzero.rc
) )

123
alethzero/GetPassword.ui

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GetPassword</class>
<widget class="QDialog" name="GetPassword">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>187</height>
</rect>
</property>
<property name="windowTitle">
<string>Enter Password</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLineEdit" name="entry">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>GetPassword</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>GetPassword</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

8
alethzero/Main.ui

@ -159,6 +159,7 @@
<addaction name="importKeyFile"/> <addaction name="importKeyFile"/>
<addaction name="claimPresale"/> <addaction name="claimPresale"/>
<addaction name="exportKey"/> <addaction name="exportKey"/>
<addaction name="reencryptAll"/>
<addaction name="reencryptKey"/> <addaction name="reencryptKey"/>
<addaction name="killAccount"/> <addaction name="killAccount"/>
<addaction name="separator"/> <addaction name="separator"/>
@ -1754,7 +1755,12 @@ font-size: 14pt</string>
</action> </action>
<action name="reencryptKey"> <action name="reencryptKey">
<property name="text"> <property name="text">
<string>&amp;Re-encrypt Key</string> <string>&amp;Re-Encrypt Key</string>
</property>
</action>
<action name="reencryptAll">
<property name="text">
<string>Re-Encrypt All Keys...</string>
</property> </property>
</action> </action>
</widget> </widget>

98
alethzero/MainWin.cpp

@ -75,6 +75,7 @@
#include "WebPage.h" #include "WebPage.h"
#include "ExportState.h" #include "ExportState.h"
#include "ui_Main.h" #include "ui_Main.h"
#include "ui_GetPassword.h"
using namespace std; using namespace std;
using namespace dev; using namespace dev;
using namespace dev::p2p; using namespace dev::p2p;
@ -467,7 +468,9 @@ void Main::load(QString _s)
void Main::on_newTransaction_triggered() void Main::on_newTransaction_triggered()
{ {
m_transact.setEnvironment(m_keyManager.accounts(), ethereum(), &m_natSpecDB); 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() void Main::on_loadJS_triggered()
@ -698,12 +701,17 @@ Secret Main::retrieveSecret(Address const& _a) const
auto info = m_keyManager.accountDetails()[_a]; auto info = m_keyManager.accountDetails()[_a];
while (true) while (true)
{ {
if (Secret s = m_keyManager.secret(_a, [&](){ Secret s = m_keyManager.secret(_a, [&](){
return QInputDialog::getText(const_cast<Main*>(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(); 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; return s;
else if (QMessageBox::warning(const_cast<Main*>(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(); 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; QString password;
while (true) 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()); QString confirm = QInputDialog::getText(nullptr, QString::fromStdString(_title), "Confirm this password by typing it again", QLineEdit::Password, QString());
if (password == confirm) if (password == confirm)
break; break;
QMessageBox::warning(nullptr, QString::fromStdString(_title), "You entered two different passwords - please enter the same password twice.", QMessageBox::Ok); 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(); 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"); 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) 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!"); bool ok;
std::string hint = QInputDialog::getText(this, "Import Account Key", "Enter a hint to help you remember this password.").toStdString(); 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); m_keyManager.import(k.secret(), s.toStdString(), password, hint);
} }
else else
@ -2003,8 +2029,11 @@ void Main::on_newAccount_triggered()
QString s = QInputDialog::getText(this, "Create Account", "Enter this account's name"); 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) 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!"); bool ok = false;
std::string hint = QInputDialog::getText(this, "Create Account", "Enter a hint to help you remember this password.").toStdString(); 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); m_keyManager.import(p.secret(), s.toStdString(), password, hint);
} }
else else
@ -2043,11 +2072,48 @@ void Main::on_reencryptKey_triggered()
auto hba = ui->ourAccounts->currentItem()->data(Qt::UserRole).toByteArray(); auto hba = ui->ourAccounts->currentItem()->data(Qt::UserRole).toByteArray();
Address a((byte const*)hba.data(), Address::ConstructFromPointer); Address a((byte const*)hba.data(), Address::ConstructFromPointer);
QStringList kdfs = {"PBKDF2-SHA256", "Scrypt"}; 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); bool ok = true;
m_keyManager.reencode(a, [&](){ 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));
return QInputDialog::getText(nullptr, "Re-Encrypt Key", "Enter the password for this key to re-encrypt it.", QLineEdit::Password, QString()).toStdString(); if (!ok)
}, (KDF)kdfs.indexOf(kdf)); 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() void Main::on_go_triggered()

3
alethzero/MainWin.h

@ -138,6 +138,7 @@ private slots:
void on_killAccount_triggered(); void on_killAccount_triggered();
void on_importKey_triggered(); void on_importKey_triggered();
void on_reencryptKey_triggered(); void on_reencryptKey_triggered();
void on_reencryptAll_triggered();
void on_importKeyFile_triggered(); void on_importKeyFile_triggered();
void on_claimPresale_triggered(); void on_claimPresale_triggered();
void on_exportKey_triggered(); void on_exportKey_triggered();
@ -248,7 +249,7 @@ private:
void refreshBalances(); void refreshBalances();
void setBeneficiary(dev::Address const& _b); 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::Main> ui; std::unique_ptr<Ui::Main> ui;

27
alethzero/Transact.cpp

@ -76,6 +76,7 @@ void Transact::setEnvironment(AddressHash const& _accounts, dev::eth::Client* _e
m_ethereum = _eth; m_ethereum = _eth;
m_natSpecDB = _natSpecDB; m_natSpecDB = _natSpecDB;
auto old = ui->from->currentIndex();
ui->from->clear(); ui->from->clear();
for (auto const& i: m_accounts) 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)); 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); 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 bool Transact::isCreation() const
@ -301,6 +306,9 @@ void Transact::rejigData()
// Determine how much balance we have to play with... // Determine how much balance we have to play with...
//findSecret(value() + ethereum()->gasLimitRemaining() * gasPrice()); //findSecret(value() + ethereum()->gasLimitRemaining() * gasPrice());
auto s = fromAccount(); auto s = fromAccount();
if (!s)
return;
auto b = ethereum()->balanceAt(s, PendingBlock); auto b = ethereum()->balanceAt(s, PendingBlock);
m_allGood = true; m_allGood = true;
@ -344,7 +352,7 @@ void Transact::rejigData()
if (b < value() + baseGas * gasPrice()) if (b < value() + baseGas * gasPrice())
{ {
// Not enough - bail. // Not enough - bail.
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> No single account contains enough for paying even the basic amount of gas required.</div>"); bail("<div class=\"error\"><span class=\"icon\">ERROR</span> Account doesn't contain enough for paying even the basic amount of gas required.</div>");
return; return;
} }
else else
@ -417,6 +425,8 @@ Secret Transact::findSecret(u256 _totalReq) const
Address Transact::fromAccount() Address Transact::fromAccount()
{ {
if (ui->from->currentIndex() < 0 || ui->from->currentIndex() >= (int)m_accounts.size())
return Address();
auto it = m_accounts.begin(); auto it = m_accounts.begin();
std::advance(it, ui->from->currentIndex()); std::advance(it, ui->from->currentIndex());
return *it; return *it;
@ -425,14 +435,19 @@ Address Transact::fromAccount()
void Transact::on_send_clicked() void Transact::on_send_clicked()
{ {
// Secret s = findSecret(value() + fee()); // Secret s = findSecret(value() + fee());
Secret s = m_context->retrieveSecret(fromAccount()); auto a = fromAccount();
auto b = ethereum()->balanceAt(KeyPair(s).address(), PendingBlock); auto b = ethereum()->balanceAt(a, PendingBlock);
if (!s || b < value() + fee())
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; return;
} }
Secret s = m_context->retrieveSecret(a);
if (!s)
return;
if (isCreation()) if (isCreation())
{ {
// If execution is a contract creation, add Natspec to // If execution is a contract creation, add Natspec to
@ -467,7 +482,7 @@ void Transact::on_debug_clicked()
auto b = ethereum()->balanceAt(from, PendingBlock); auto b = ethereum()->balanceAt(from, PendingBlock);
if (!from || b < value() + fee()) 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; return;
} }

1
alethzero/Transact.h

@ -44,6 +44,7 @@ public:
void setEnvironment(dev::AddressHash const& _accounts, dev::eth::Client* _eth, NatSpecFace* _natSpecDB); void setEnvironment(dev::AddressHash const& _accounts, dev::eth::Client* _eth, NatSpecFace* _natSpecDB);
private slots: private slots:
void on_from_currentIndexChanged(int) { rejigData(); }
void on_destination_currentTextChanged(QString); void on_destination_currentTextChanged(QString);
void on_value_valueChanged(int) { updateFee(); } void on_value_valueChanged(int) { updateFee(); }
void on_gas_valueChanged(int) { updateFee(); } void on_gas_valueChanged(int) { updateFee(); }

3
libdevcore/Common.h

@ -39,6 +39,7 @@
#include <set> #include <set>
#include <unordered_set> #include <unordered_set>
#include <functional> #include <functional>
#include <string>
#include <boost/timer.hpp> #include <boost/timer.hpp>
#include <boost/functional/hash.hpp> #include <boost/functional/hash.hpp>
#pragma warning(push) #pragma warning(push)
@ -63,6 +64,8 @@ namespace dev
extern char const* Version; extern char const* Version;
static const std::string EmptyString;
// Binary data types. // Binary data types.
using bytes = std::vector<byte>; using bytes = std::vector<byte>;
using bytesRef = vector_ref<byte>; using bytesRef = vector_ref<byte>;

14
libdevcrypto/Common.cpp

@ -120,10 +120,11 @@ std::pair<bytes, h128> dev::encryptSymNoAuth(h128 const& _k, bytesConstRef _plai
return make_pair(encryptSymNoAuth(_k, iv, _plain), iv); 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; if (_k.size() != 16 && _k.size() != 24 && _k.size() != 32)
SecByteBlock key(_k.data(), c_aesKeyLen); return bytes();
SecByteBlock key(_k.data(), _k.size());
try try
{ {
CTR_Mode<AES>::Encryption e; CTR_Mode<AES>::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; if (_k.size() != 16 && _k.size() != 24 && _k.size() != 32)
SecByteBlock key(_k.data(), c_aesKeyLen); return bytes();
SecByteBlock key(_k.data(), _k.size());
try try
{ {
CTR_Mode<AES>::Decryption d; CTR_Mode<AES>::Decryption d;

12
libdevcrypto/Common.h

@ -106,10 +106,18 @@ bool decryptECIES(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext);
std::pair<bytes, h128> encryptSymNoAuth(h128 const& _k, bytesConstRef _plain); std::pair<bytes, h128> encryptSymNoAuth(h128 const& _k, bytesConstRef _plain);
/// Encrypts payload with specified IV/ctr using AES128-CTR. /// 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. /// Decrypts payload with specified IV/ctr using AES128-CTR.
bytes decryptSymNoAuth(h128 const& _k, h128 const& _iv, bytesConstRef _cipher); 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.
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. /// Recovers Public key from signed message hash.
Public recover(Signature const& _sig, h256 const& _hash); Public recover(Signature const& _sig, h256 const& _hash);

152
libdevcrypto/SecretStore.cpp

@ -34,6 +34,56 @@ using namespace dev;
namespace js = json_spirit; namespace js = json_spirit;
namespace fs = boost::filesystem; 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() SecretStore::SecretStore()
{ {
load(); load();
@ -45,6 +95,7 @@ SecretStore::~SecretStore()
bytes SecretStore::secret(h128 const& _uuid, function<std::string()> const& _pass, bool _useCache) const bytes SecretStore::secret(h128 const& _uuid, function<std::string()> const& _pass, bool _useCache) const
{ {
(void)_pass;
auto rit = m_cached.find(_uuid); auto rit = m_cached.find(_uuid);
if (_useCache && rit != m_cached.end()) if (_useCache && rit != m_cached.end())
return rit->second; return rit->second;
@ -94,7 +145,7 @@ void SecretStore::save(std::string const& _keysPath)
js::read_string(k.second.first, crypto); js::read_string(k.second.first, crypto);
v["crypto"] = crypto; v["crypto"] = crypto;
v["id"] = uuid; v["id"] = uuid;
v["version"] = 2; v["version"] = c_keyFileVersion;
writeFile(filename, js::write_string(js::mValue(v), true)); writeFile(filename, js::write_string(js::mValue(v), true));
if (!k.second.second.empty() && k.second.second != filename) if (!k.second.second.empty() && k.second.second != filename)
boost::filesystem::remove(k.second.second); 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) void SecretStore::load(std::string const& _keysPath)
{ {
fs::path p(_keysPath); fs::path p(_keysPath);
@ -169,10 +178,17 @@ h128 SecretStore::readKey(std::string const& _file, bool _deleteFile)
return h128(); return h128();
} }
void SecretStore::recode(h128 const& _uuid, string const& _pass, KDF _kdf) bool SecretStore::recode(h128 const& _uuid, string const& _newPass, std::function<std::string()> 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(); save();
return true;
} }
std::string SecretStore::encrypt(bytes const& _v, std::string const& _pass, KDF _kdf) 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; js::mObject ret;
// KDF info // KDF info
unsigned dklen = 16; unsigned dklen = 32;
bytes salt = h256::random().asBytes(); bytes salt = h256::random().asBytes();
bytes derivedKey; bytes derivedKey;
if (_kdf == KDF::Scrypt) if (_kdf == KDF::Scrypt)
{ {
unsigned iterations = 262144; unsigned iterations = 262144;
unsigned p = 262144; unsigned p = 1;
unsigned r = 262144; unsigned r = 8;
ret["kdf"] = "scrypt"; ret["kdf"] = "scrypt";
{ {
js::mObject params; js::mObject params;
params["n"] = (int)iterations; params["n"] = (int64_t)iterations;
params["p"] = 1; params["p"] = (int)p;
params["r"] = 8; params["r"] = (int)r;
params["dklen"] = (int)dklen; params["dklen"] = (int)dklen;
params["salt"] = toHex(salt); params["salt"] = toHex(salt);
ret["kdfparams"] = params; ret["kdfparams"] = params;
} }
derivedKey = scrypt(_pass, salt, iterations, p, r, dklen); derivedKey = scrypt(_pass, salt, iterations, p, r, dklen);
cdebug << "derivedKey" << toHex(derivedKey);
} }
else else
{ {
@ -213,11 +230,13 @@ std::string SecretStore::encrypt(bytes const& _v, std::string const& _pass, KDF
ret["kdfparams"] = params; ret["kdfparams"] = params;
} }
derivedKey = pbkdf2(_pass, salt, iterations, dklen); derivedKey = pbkdf2(_pass, salt, iterations, dklen);
cdebug << "derivedKey" << toHex(derivedKey);
} }
// cipher info // cipher info
ret["cipher"] = "aes-128-cbc"; ret["cipher"] = "aes-128-ctr";
h128 key(sha3(h128(derivedKey, h128::AlignRight)), h128::AlignRight); h128 key(derivedKey, h128::AlignLeft);
cdebug << "cipherKey" << key.hex();
h128 iv = h128::random(); h128 iv = h128::random();
{ {
js::mObject params; js::mObject params;
@ -230,7 +249,9 @@ std::string SecretStore::encrypt(bytes const& _v, std::string const& _pass, KDF
ret["ciphertext"] = toHex(cipherText); ret["ciphertext"] = toHex(cipherText);
// and mac. // 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()); ret["mac"] = toHex(mac.ref());
return js::write_string((js::mValue)ret, true); 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(); 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()); bytes cipherText = fromHex(o["ciphertext"].get_str());
// check MAC // check MAC
if (o.count("mac")) if (o.count("mac"))
{ {
h256 mac(o["mac"].get_str()); 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) if (mac != macExp)
{ {
cwarn << "Invalid key - MAC mismatch; expected" << toString(macExp) << ", got" << toString(mac); 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."; cwarn << "No MAC. Proceeding anyway.";
// decrypt // decrypt
if (o["cipher"].get_str() == "aes-128-cbc") if (o["cipher"].get_str() == "aes-128-ctr")
{ {
auto params = o["cipherparams"].get_obj(); auto params = o["cipherparams"].get_obj();
h128 key(sha3(h128(derivedKey, h128::AlignRight)), h128::AlignRight);
h128 iv(params["iv"].get_str()); 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 else
{ {

2
libdevcrypto/SecretStore.h

@ -44,7 +44,7 @@ public:
bytes secret(h128 const& _uuid, std::function<std::string()> const& _pass, bool _useCache = true) const; bytes secret(h128 const& _uuid, std::function<std::string()> const& _pass, bool _useCache = true) const;
h128 importKey(std::string const& _file) { auto ret = readKey(_file, false); if (ret) save(); return ret; } 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); 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<std::string()> const& _pass, KDF _kdf = KDF::Scrypt);
void kill(h128 const& _uuid); void kill(h128 const& _uuid);
// Clear any cached keys. // Clear any cached keys.

2
libethcore/ICAP.h

@ -38,8 +38,6 @@ namespace eth
struct InvalidICAP: virtual public dev::Exception {}; struct InvalidICAP: virtual public dev::Exception {};
static const std::string EmptyString;
/** /**
* @brief Encapsulation of an ICAP address. * @brief Encapsulation of an ICAP address.
* Can be encoded, decoded, looked-up and inspected. * Can be encoded, decoded, looked-up and inspected.

2
libethereum/AccountDiff.h

@ -62,7 +62,7 @@ struct AccountDiff
Diff<bool> exist; ///< The account's existance; was it created/deleted or not? Diff<bool> exist; ///< The account's existance; was it created/deleted or not?
Diff<u256> balance; ///< The account's balance; did it alter? Diff<u256> balance; ///< The account's balance; did it alter?
Diff<u256> nonce; ///< The account's nonce; did it alter? Diff<u256> nonce; ///< The account's nonce; did it alter?
std::unordered_map<u256, Diff<u256>> storage; ///< The account's storage addresses; each has its own Diff. std::map<u256, Diff<u256>> storage; ///< The account's storage addresses; each has its own Diff.
Diff<bytes> code; ///< The account's code; in general this should only have changed if exist also changed. Diff<bytes> code; ///< The account's code; in general this should only have changed if exist also changed.
}; };

14
libethereum/KeyManager.cpp

@ -49,10 +49,20 @@ void KeyManager::create(std::string const& _pass)
write(_pass, m_keysFile); write(_pass, m_keysFile);
} }
void KeyManager::reencode(Address const& _address, std::function<string()> const& _pass, KDF _kdf) bool KeyManager::recode(Address const& _address, std::string const& _newPass, std::string const& _hint, std::function<string()> 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<string()> const& _pass, KDF _kdf)
{ {
h128 u = uuid(_address); 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) bool KeyManager::load(std::string const& _pass)

12
libethereum/KeyManager.h

@ -42,6 +42,12 @@ struct KeyInfo
static const auto DontKnowThrow = [](){ throw UnknownPassword(); return std::string(); }; 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: This one is specifically for Ethereum, but we can make it generic in due course.
// TODO: hidden-partition style key-store. // TODO: hidden-partition style key-store.
/** /**
@ -67,9 +73,12 @@ public:
void save(std::string const& _pass) const { write(_pass, m_keysFile); } void save(std::string const& _pass) const { write(_pass, m_keysFile); }
void notePassword(std::string const& _pass) { m_cachedPasswords[hashPassword(_pass)] = _pass; } 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; AddressHash accounts() const;
std::unordered_map<Address, std::pair<std::string, std::string>> accountDetails() const; std::unordered_map<Address, std::pair<std::string, std::string>> 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; h128 uuid(Address const& _a) const;
Address address(h128 const& _uuid) const; Address address(h128 const& _uuid) const;
@ -84,7 +93,8 @@ public:
Secret secret(Address const& _address, std::function<std::string()> const& _pass = DontKnowThrow) const; Secret secret(Address const& _address, std::function<std::string()> const& _pass = DontKnowThrow) const;
Secret secret(h128 const& _uuid, std::function<std::string()> const& _pass = DontKnowThrow) const; Secret secret(h128 const& _uuid, std::function<std::string()> const& _pass = DontKnowThrow) const;
void reencode(Address const& _address, std::function<std::string()> const& _pass = DontKnowThrow, KDF _kdf = KDF::Scrypt); bool recode(Address const& _address, SemanticPassword _newPass, std::function<std::string()> const& _pass = DontKnowThrow, KDF _kdf = KDF::Scrypt);
bool recode(Address const& _address, std::string const& _newPass, std::string const& _hint, std::function<std::string()> const& _pass = DontKnowThrow, KDF _kdf = KDF::Scrypt);
void kill(h128 const& _id) { kill(address(_id)); } void kill(h128 const& _id) { kill(address(_id)); }
void kill(Address const& _a); void kill(Address const& _a);

Loading…
Cancel
Save