#pragma once /* This file is part of cpp-ethereum. cpp-ethereum is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. cpp-ethereum is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ /** @file KeyAux.cpp * @author Gav Wood * @date 2014 * CLI module for key management. */ #include #include #include #include #include #include #include #include #include #include #include #include "BuildInfo.h" using namespace std; using namespace dev; using namespace dev::eth; using namespace boost::algorithm; #undef RETURN class BadArgument: public Exception {}; string getAccountPassword(KeyManager& keyManager, Address const& a) { return getPassword("Enter password for address " + keyManager.accountName(a) + " (" + a.abridged() + "; hint:" + keyManager.passwordHint(a) + "): "); } string createPassword(std::string const& _prompt) { string ret; while (true) { ret = getPassword(_prompt); string confirm = getPassword("Please confirm the password by entering it again: "); if (ret == confirm) break; cout << "Passwords were different. Try again." << endl; } return ret; // cout << "Enter a hint to help you remember this password: " << flush; // cin >> hint; // return make_pair(ret, hint); } pair createPassword(KeyManager& _keyManager, std::string const& _prompt, std::string const& _pass = std::string(), std::string const& _hint = std::string()) { string pass = _pass; if (pass.empty()) while (true) { pass = getPassword(_prompt); string confirm = getPassword("Please confirm the password by entering it again: "); if (pass == confirm) break; cout << "Passwords were different. Try again." << endl; } string hint = _hint; if (hint.empty() && !pass.empty() && !_keyManager.haveHint(pass)) { cout << "Enter a hint to help you remember this password: " << flush; getline(cin, hint); } return make_pair(pass, hint); } class KeyCLI { public: enum class OperationMode { None, ListBare, NewBare, ImportBare, ExportBare, RecodeBare, KillBare, InspectBare, CreateWallet, List, New, Import, ImportWithAddress, Export, Recode, Kill, SignTx, DecodeTx, }; KeyCLI(OperationMode _mode = OperationMode::None): m_mode(_mode) {} bool interpretOption(int& i, int argc, char** argv) { string arg = argv[i]; if (arg == "--wallet-path" && i + 1 < argc) m_walletPath = argv[++i]; else if (arg == "--secrets-path" && i + 1 < argc) m_secretsPath = argv[++i]; else if ((arg == "-m" || arg == "--master") && i + 1 < argc) m_masterPassword = argv[++i]; else if (arg == "--unlock" && i + 1 < argc) m_unlocks.push_back(argv[++i]); else if (arg == "--lock" && i + 1 < argc) m_lock = argv[++i]; else if (arg == "--kdf" && i + 1 < argc) m_kdf = argv[++i]; else if (arg == "--kdf-param" && i + 2 < argc) { auto n = argv[++i]; auto v = argv[++i]; m_kdfParams[n] = v; } else if (arg == "--sign-tx" && i + 1 < argc) { m_mode = OperationMode::SignTx; m_signKey = argv[++i]; } else if (arg == "--decode-tx") m_mode = OperationMode::DecodeTx; else if (arg == "--import-bare") m_mode = OperationMode::ImportBare; else if (arg == "--list-bare") m_mode = OperationMode::ListBare; else if (arg == "--export-bare") m_mode = OperationMode::ExportBare; else if (arg == "--inspect-bare") m_mode = OperationMode::InspectBare; else if (arg == "--recode-bare") m_mode = OperationMode::RecodeBare; else if (arg == "--kill-bare") m_mode = OperationMode::KillBare; else if (arg == "--create-wallet") m_mode = OperationMode::CreateWallet; else if (arg == "--list") m_mode = OperationMode::List; else if ((arg == "-n" || arg == "--new") && i + 1 < argc) { m_mode = OperationMode::New; m_name = argv[++i]; } else if ((arg == "-i" || arg == "--import") && i + 2 < argc) { m_mode = OperationMode::Import; m_inputs = strings(1, argv[++i]); m_name = argv[++i]; } else if ((arg == "-i" || arg == "--import-with-address") && i + 3 < argc) { m_mode = OperationMode::ImportWithAddress; m_inputs = strings(1, argv[++i]); m_address = Address(argv[++i]); m_name = argv[++i]; } else if (arg == "--export") m_mode = OperationMode::Export; else if (arg == "--recode") m_mode = OperationMode::Recode; else if (arg == "--no-icap") m_icap = false; else if (m_mode == OperationMode::DecodeTx || m_mode == OperationMode::SignTx || m_mode == OperationMode::ImportBare || m_mode == OperationMode::InspectBare || m_mode == OperationMode::KillBare || m_mode == OperationMode::Recode || m_mode == OperationMode::Export || m_mode == OperationMode::RecodeBare || m_mode == OperationMode::ExportBare) m_inputs.push_back(arg); else return false; return true; } KeyPair makeKey() const { KeyPair k(Secret::random()); while (m_icap && k.address()[0]) k = KeyPair(sha3(k.secret())); return k; } void execute() { if (m_mode == OperationMode::CreateWallet) { KeyManager wallet(m_walletPath, m_secretsPath); if (m_masterPassword.empty()) m_masterPassword = createPassword("Please enter a MASTER password to protect your key store (make it strong!): "); if (m_masterPassword.empty()) cerr << "Aborted (empty password not allowed)." << endl; else { try { wallet.create(m_masterPassword); } catch (Exception const& _e) { cerr << "unable to create wallet" << endl << boost::diagnostic_information(_e); } } } else if (m_mode == OperationMode::DecodeTx) { string const& i = m_inputs[0]; bytes b = fromHex(i); if (b.empty()) { std::string s = contentsString(i); b = fromHex(s); if (b.empty()) b = asBytes(s); } if (b.empty()) cerr << "Unknown file or bad hex: " << i << endl; else try { TransactionBase t(b, CheckTransaction::Everything); cout << "Transaction " << t.sha3().hex() << endl; if (t.isCreation()) { cout << " type: creation" << endl; cout << " code: " << toHex(t.data()) << endl; } else { cout << " type: message" << endl; cout << " to: " << t.to().hex() << endl; cout << " data: " << (t.data().empty() ? "none" : toHex(t.data())) << endl; } cout << " from: " << t.from().hex() << endl; cout << " value: " << formatBalance(t.value()) << " (" << t.value() << " wei)" << endl; cout << " nonce: " << t.nonce() << endl; cout << " gas: " << t.gas() << endl; cout << " gas price: " << formatBalance(t.gasPrice()) << " (" << t.gasPrice() << " wei)" << endl; cout << " signing hash: " << t.sha3(WithoutSignature).hex() << endl; cout << " v: " << (int)t.signature().v << endl; cout << " r: " << t.signature().r << endl; cout << " s: " << t.signature().s << endl; } catch (Exception& ex) { cerr << "Invalid transaction: " << ex.what() << endl; } } else if (m_mode == OperationMode::SignTx) { Secret s; string json = contentsString(m_signKey); if (!json.empty()) { SecretStore store(m_secretsPath); s = Secret(store.secret(store.readKeyContent(json), [&](){ return getPassword("Enter password for key: "); })); } else { if (h128 u = fromUUID(m_signKey)) { SecretStore store(m_secretsPath); s = Secret(store.secret(u, [&](){ return getPassword("Enter password for key: "); })); } else if (Address a = Address(m_signKey)) { KeyManager wallet(m_walletPath, m_secretsPath); if (wallet.exists()) { openWallet(wallet); s = wallet.secret(a, [&](){ return getPassword("Enter password for key: "); }); } else { cerr << "Wallet doesn't exist." << endl; exit(-1); } } else { cerr << "Bad file, UUID and address: " << m_signKey << endl; exit(-1); } } if (!s) { cerr << "UUID/address not found: " << m_signKey << endl; exit(-1); } for (string const& i: m_inputs) { bytes b = fromHex(i); bool isFile = false; if (b.empty()) { isFile = true; std::string s = contentsString(i); b = fromHex(s); if (b.empty()) b = asBytes(s); } if (b.empty()) cerr << "Unknown file or bad hex: " << i << endl; else try { TransactionBase t(b, CheckTransaction::None); t.sign(s); cout << t.sha3() << ": "; if (isFile) { writeFile(i + ".signed", t.data()); cout << i + ".signed" << endl; } else cout << toHex(t.data()) << endl; } catch (Exception& ex) { cerr << "Invalid transaction: " << ex.what() << endl; } } } else if (m_mode < OperationMode::CreateWallet) { SecretStore store(m_secretsPath); switch (m_mode) { case OperationMode::ListBare: for (h128 const& u: std::set() + store.keys()) cout << toUUID(u) << endl; break; case OperationMode::NewBare: { if (m_lock.empty()) m_lock = createPassword("Enter a password with which to secure this account: "); auto k = makeKey(); h128 u = store.importSecret(k.secret().asBytes(), m_lock); cout << "Created key " << toUUID(u) << endl; cout << " Address: " << k.address().hex() << endl; cout << " ICAP: " << ICAP(k.address()).encoded() << endl; break; } case OperationMode::ImportBare: for (string const& input: m_inputs) { h128 u; bytes b; b = fromHex(input); if (b.size() != 32) { std::string s = contentsString(input); b = fromHex(s); if (b.size() != 32) u = store.importKey(input); } if (!u && b.size() == 32) u = store.importSecret(b, lockPassword(toAddress(Secret(b)).abridged())); if (!u) { cerr << "Cannot import " << input << " not a file or secret." << endl; continue; } cout << "Successfully imported " << input << " as " << toUUID(u); } break; case OperationMode::InspectBare: for (auto const& i: m_inputs) if (!contents(i).empty()) { h128 u = store.readKey(i, false); bytes s = store.secret(u, [&](){ return getPassword("Enter password for key " + i + ": "); }); cout << "Key " << i << ":" << endl; cout << " UUID: " << toUUID(u) << ":" << endl; cout << " Address: " << toAddress(Secret(s)).hex() << endl; cout << " Secret: " << Secret(s).abridged() << endl; } else if (h128 u = fromUUID(i)) { bytes s = store.secret(u, [&](){ return getPassword("Enter password for key " + toUUID(u) + ": "); }); cout << "Key " << i << ":" << endl; cout << " Address: " << toAddress(Secret(s)).hex() << endl; cout << " Secret: " << Secret(s).abridged() << endl; } else cerr << "Couldn't inspect " << i << "; not found." << endl; break; case OperationMode::ExportBare: break; case OperationMode::RecodeBare: for (auto const& i: m_inputs) if (h128 u = fromUUID(i)) if (store.recode(u, lockPassword(toUUID(u)), [&](){ return getPassword("Enter password for key " + toUUID(u) + ": "); }, kdf())) cerr << "Re-encoded " << toUUID(u) << endl; else cerr << "Couldn't re-encode " << toUUID(u) << "; key corrupt or incorrect password supplied." << endl; else cerr << "Couldn't re-encode " << i << "; not found." << endl; case OperationMode::KillBare: for (auto const& i: m_inputs) if (h128 u = fromUUID(i)) store.kill(u); else cerr << "Couldn't kill " << i << "; not found." << endl; break; default: break; } } else { KeyManager wallet(m_walletPath, m_secretsPath); if (wallet.exists()) openWallet(wallet); else { cerr << "Couldn't open wallet. Does it exist?" << endl; exit(-1); } switch (m_mode) { case OperationMode::New: { tie(m_lock, m_lockHint) = createPassword(wallet, "Enter a password with which to secure this account (or nothing to use the master password): ", m_lock, m_lockHint); auto k = makeKey(); bool usesMaster = m_lock.empty(); h128 u = usesMaster ? wallet.import(k.secret(), m_name) : wallet.import(k.secret(), m_name, m_lock, m_lockHint); cout << "Created key " << toUUID(u) << endl; cout << " Name: " << m_name << endl; if (usesMaster) cout << " Uses master password." << endl; else cout << " Password hint: " << m_lockHint << endl; cout << " Address: " << k.address().hex() << endl; cout << " ICAP: " << ICAP(k.address()).encoded() << endl; break; } case OperationMode::ImportWithAddress: { string const& i = m_inputs[0]; h128 u; bytes b; b = fromHex(i); if (b.size() != 32) { std::string s = contentsString(i); b = fromHex(s); if (b.size() != 32) u = wallet.store().importKey(i); } if (!u && b.size() == 32) u = wallet.store().importSecret(b, lockPassword(toAddress(Secret(b)).abridged())); if (!u) { cerr << "Cannot import " << i << " not a file or secret." << endl; break; } wallet.importExisting(u, m_name, m_address); cout << "Successfully imported " << i << ":" << endl; cout << " Name: " << m_name << endl; cout << " Address: " << m_address << endl; cout << " UUID: " << toUUID(u) << endl; break; } case OperationMode::List: { vector bare; vector nonIcap; for (auto const& u: wallet.store().keys()) if (Address a = wallet.address(u)) if (a[0]) nonIcap.push_back(u); else { cout << toUUID(u) << " " << a.abridged(); cout << " " << ICAP(a).encoded(); cout << " " << wallet.accountName(a) << endl; } else bare.push_back(u); for (auto const& u: nonIcap) if (Address a = wallet.address(u)) { cout << toUUID(u) << " " << a.abridged(); cout << " (Not ICAP) "; cout << " " << wallet.accountName(a) << endl; } for (auto const& u: bare) cout << toUUID(u) << " (Bare)" << endl; } default: break; } } } std::string lockPassword(std::string const& _accountName) { return m_lock.empty() ? createPassword("Enter a password with which to secure account " + _accountName + ": ") : m_lock; } static void streamHelp(ostream& _out) { _out << "Secret-store (\"bare\") operation modes:" << endl << " --list-bare List all secret available in secret-store." << endl << " --new-bare Generate and output a key without interacting with wallet and dump the JSON." << endl << " --import-bare [ | , ... ] Import keys from given sources." << endl << " --recode-bare [ | , ... ] Decrypt and re-encrypt given keys." << endl // << " --export-bare [ , ... ] Export given keys." << endl << " --kill-bare [ , ... ] Delete given keys." << endl << "Secret-store configuration:" << endl << " --secrets-path Specify Web3 secret-store path (default: " << SecretStore::defaultPath() << ")" << endl << endl << "Wallet operating modes:" << endl << " -l,--list List all keys available in wallet." << endl << " -n,--new Create a new key with given name and add it in the wallet." << endl << " -i,--import [||] Import keys from given source and place in wallet." << endl << " --import-with-address [||]
Import keys from given source with given address and place in wallet." << endl << " -e,--export [
| , ... ] Export given keys." << endl << " -r,--recode [
|| , ... ] Decrypt and re-encrypt given keys." << endl << "Wallet configuration:" << endl << " --create-wallet Create an Ethereum master wallet." << endl << " --wallet-path Specify Ethereum wallet path (default: " << KeyManager::defaultPath() << ")" << endl << " -m, --master Specify wallet (master) password." << endl << endl << "Transaction operating modes:" << endl << " -d,--decode-tx [|] Decode given transaction." << endl << " -s,--sign-tx [
|| ] [ | , ... ] (Re-)Sign given transaction." << endl << endl << "Encryption configuration:" << endl << " --kdf Specify KDF to use when encrypting (default: sc rypt)" << endl << " --kdf-param Specify a parameter for the KDF." << endl // << " --cipher Specify cipher to use when encrypting (default: aes-128-ctr)" << endl // << " --cipher-param Specify a parameter for the cipher." << endl << " --lock Specify password for when encrypting a (the) key." << endl << " --hint Specify hint for the --lock password." << endl << endl << "Decryption configuration:" << endl << " --unlock Specify password for a (the) key." << endl << "Key generation configuration:" << endl << " --no-icap Don't bother to make a direct-ICAP capable key." << endl ; } static bool isTrue(std::string const& _m) { return _m == "on" || _m == "yes" || _m == "true" || _m == "1"; } static bool isFalse(std::string const& _m) { return _m == "off" || _m == "no" || _m == "false" || _m == "0"; } private: void openWallet(KeyManager& _w) { while (true) { if (_w.load(m_masterPassword)) break; if (!m_masterPassword.empty()) { cout << "Password invalid. Try again." << endl; m_masterPassword.clear(); } m_masterPassword = getPassword("Please enter your MASTER password: "); } } KDF kdf() const { return m_kdf == "pbkdf2" ? KDF::PBKDF2_SHA256 : KDF::Scrypt; } /// Operating mode. OperationMode m_mode; /// Wallet stuff string m_secretsPath = SecretStore::defaultPath(); string m_walletPath = KeyManager::defaultPath(); /// Wallet password stuff string m_masterPassword; strings m_unlocks; string m_lock; string m_lockHint; bool m_icap = true; /// Creating/importing string m_name; Address m_address; /// Importing strings m_inputs; /// Signing string m_signKey; string m_kdf = "scrypt"; map m_kdfParams; // string m_cipher; // map m_cipherParams; };