diff --git a/alethzero/Main.ui b/alethzero/Main.ui
index b5e22409d..f37f532a2 100644
--- a/alethzero/Main.ui
+++ b/alethzero/Main.ui
@@ -157,6 +157,7 @@
+
@@ -1656,7 +1657,7 @@ font-size: 14pt
&Enable Local Addresses
-
+
true
@@ -1745,6 +1746,11 @@ font-size: 14pt
Co&nfirm Transactions
+
+
+ Import &Secret Key File...
+
+
diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp
index 07f48c0bc..4f1da1c1a 100644
--- a/alethzero/MainWin.cpp
+++ b/alethzero/MainWin.cpp
@@ -813,6 +813,33 @@ void Main::on_importKey_triggered()
}
void Main::on_importKeyFile_triggered()
+{
+ QString s = QFileDialog::getOpenFileName(this, "Claim Account Contents", QDir::homePath(), "JSON Files (*.json);;All Files (*)");
+ h128 uuid = m_keyManager.store().importKey(s.toStdString());
+ if (!uuid)
+ {
+ QMessageBox::warning(this, "Key File Invalid", "Could not find secret key definition. This is probably not an Web3 key file.");
+ return;
+ }
+
+ QString info = QInputDialog::getText(this, "Import Key File", "Enter a description of this key to help you recognise it in the future.");
+
+ QString pass;
+ for (Secret s; !s;)
+ {
+ s = Secret(m_keyManager.store().secret(uuid, [&](){
+ pass = QInputDialog::getText(this, "Import Key File", "Enter the password for the key to complete the import.", QLineEdit::Password);
+ return pass.toStdString();
+ }, false));
+ if (!s && QMessageBox::question(this, "Import Key File", "The password you provided is incorrect. Would you like to try again?", QMessageBox::Retry, QMessageBox::Cancel) == QMessageBox::Cancel)
+ return;
+ }
+
+ QString hint = QInputDialog::getText(this, "Import Key File", "Enter a hint for this password to help you remember it.");
+ m_keyManager.importExisting(uuid, info.toStdString(), pass.toStdString(), hint.toStdString());
+}
+
+void Main::on_claimPresale_triggered()
{
QString s = QFileDialog::getOpenFileName(this, "Claim Account Contents", QDir::homePath(), "JSON Files (*.json);;All Files (*)");
try
diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h
index 9f2082dd0..fdf7a9a72 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_importKeyFile_triggered();
+ void on_claimPresale_triggered();
void on_exportKey_triggered();
// Account pane
diff --git a/libdevcrypto/SecretStore.cpp b/libdevcrypto/SecretStore.cpp
index 7bc870bcf..e422c32f8 100644
--- a/libdevcrypto/SecretStore.cpp
+++ b/libdevcrypto/SecretStore.cpp
@@ -22,6 +22,7 @@
#include "SecretStore.h"
#include
#include
+#include
#include
#include
#include
@@ -42,10 +43,10 @@ SecretStore::~SecretStore()
{
}
-bytes SecretStore::secret(h128 const& _uuid, function const& _pass) const
+bytes SecretStore::secret(h128 const& _uuid, function const& _pass, bool _useCache) const
{
auto rit = m_cached.find(_uuid);
- if (rit != m_cached.end())
+ if (_useCache && rit != m_cached.end())
return rit->second;
auto it = m_keys.find(_uuid);
if (it == m_keys.end())
@@ -101,28 +102,71 @@ 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);
boost::filesystem::create_directories(p);
- js::mValue v;
for (fs::directory_iterator it(p); it != fs::directory_iterator(); ++it)
if (is_regular_file(it->path()))
- {
- cdebug << "Reading" << it->path();
- js::read_string(contentsString(it->path().string()), v);
- if (v.type() == js::obj_type)
- {
- js::mObject o = v.get_obj();
- int version = o.count("Version") ? stoi(o["Version"].get_str()) : o.count("version") ? o["version"].get_int() : 0;
- if (version == 2)
- m_keys[fromUUID(o["id"].get_str())] = make_pair(js::write_string(o["crypto"], false), it->path().string());
- else
- cwarn << "Cannot read key version" << version;
- }
-// else
-// cwarn << "Invalid JSON in key file" << it->path().string();
- }
+ readKey(it->path().string(), true);
+}
+
+h128 SecretStore::readKey(std::string const& _file, bool _deleteFile)
+{
+ cdebug << "Reading" << _file;
+ js::mValue u = upgraded(contentsString(_file));
+ if (u.type() == js::obj_type)
+ {
+ js::mObject& o = u.get_obj();
+ auto uuid = fromUUID(o["id"].get_str());
+ m_keys[uuid] = make_pair(js::write_string(o["crypto"], false), _deleteFile ? _file : string());
+ return uuid;
+ }
+ else
+ cwarn << "Invalid JSON in key file" << _file;
+ return h128();
}
std::string SecretStore::encrypt(bytes const& _v, std::string const& _pass)
@@ -202,13 +246,28 @@ bytes SecretStore::decrypt(std::string const& _v, std::string const& _pass)
bytes cipherText = fromHex(o["ciphertext"].get_str());
// check MAC
- h256 mac(o["mac"].get_str());
- h256 macExp = sha3(bytesConstRef(&derivedKey).cropped(derivedKey.size() - 16).toBytes() + cipherText);
- if (mac != macExp)
+ if (o.count("mac"))
{
- cwarn << "Invalid key - MAC mismatch; expected" << toString(macExp) << ", got" << toString(mac);
- return bytes();
+ h256 mac(o["mac"].get_str());
+ h256 macExp = sha3(bytesConstRef(&derivedKey).cropped(derivedKey.size() - 16).toBytes() + cipherText);
+ if (mac != macExp)
+ {
+ cwarn << "Invalid key - MAC mismatch; expected" << toString(macExp) << ", got" << toString(mac);
+ return bytes();
+ }
}
+ else if (o.count("sillymac"))
+ {
+ h256 mac(o["sillymac"].get_str());
+ h256 macExp = sha3(asBytes(o["sillymacjson"].get_str()) + bytesConstRef(&derivedKey).cropped(derivedKey.size() - 16).toBytes() + cipherText);
+ if (mac != macExp)
+ {
+ cwarn << "Invalid key - MAC mismatch; expected" << toString(macExp) << ", got" << toString(mac);
+ return bytes();
+ }
+ }
+ else
+ cwarn << "No MAC. Proceeding anyway.";
// decrypt
if (o["cipher"].get_str() == "aes-128-cbc")
diff --git a/libdevcrypto/SecretStore.h b/libdevcrypto/SecretStore.h
index 18c290c1f..171603053 100644
--- a/libdevcrypto/SecretStore.h
+++ b/libdevcrypto/SecretStore.h
@@ -36,7 +36,8 @@ public:
SecretStore();
~SecretStore();
- bytes secret(h128 const& _uuid, std::function const& _pass) const;
+ 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 kill(h128 const& _uuid);
@@ -48,6 +49,7 @@ private:
void load(std::string const& _keysPath = getDataDir("web3") + "/keys");
static std::string encrypt(bytes const& _v, std::string const& _pass);
static bytes decrypt(std::string const& _v, std::string const& _pass);
+ h128 readKey(std::string const& _file, bool _deleteFile);
mutable std::unordered_map m_cached;
std::unordered_map> m_keys;