From 9e07a1505082e17660837d98acb4e3e53d7a4a12 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Thu, 14 Jun 2018 16:47:22 -0400 Subject: [PATCH 1/5] Secure Enclave wallet support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Secure Enclave wallet support. When Secure Enclave is available, a wallet named SecureEnclave is available for use with cleos’ wallet commands. Unlocking the wallet is based on local user authentication — for modern MacBook Pros unlocking the wallet can use your fingerprint. For other macOS devices, your local username/password is required. Once unlocked, you can issue wallet create_key -n SecureEnclave commands to create keys within the Secure Enclave wallet. You may not import keys. Secure Enslave support requires the application to be signed with “Mac App Store style signing”. This is technically possible to self-sign provided you have an AppleID however you will need to use Xcode to generate the provisioning profile. --- plugins/wallet_plugin/CMakeLists.txt | 18 +- .../eosio/wallet_plugin/macos_user_auth.h | 5 + .../include/eosio/wallet_plugin/se_wallet.hpp | 40 +++ .../eosio/wallet_plugin/wallet_manager.hpp | 2 +- plugins/wallet_plugin/macos_user_auth.m | 11 + plugins/wallet_plugin/se_wallet.cpp | 324 ++++++++++++++++++ plugins/wallet_plugin/wallet_manager.cpp | 9 + plugins/wallet_plugin/wallet_plugin.cpp | 6 +- programs/cleos/main.cpp | 2 +- 9 files changed, 411 insertions(+), 6 deletions(-) create mode 100644 plugins/wallet_plugin/include/eosio/wallet_plugin/macos_user_auth.h create mode 100644 plugins/wallet_plugin/include/eosio/wallet_plugin/se_wallet.hpp create mode 100644 plugins/wallet_plugin/macos_user_auth.m create mode 100644 plugins/wallet_plugin/se_wallet.cpp diff --git a/plugins/wallet_plugin/CMakeLists.txt b/plugins/wallet_plugin/CMakeLists.txt index 8eb70d332e4..879fb7e0a40 100644 --- a/plugins/wallet_plugin/CMakeLists.txt +++ b/plugins/wallet_plugin/CMakeLists.txt @@ -1,9 +1,25 @@ file(GLOB HEADERS "include/eosio/wallet_plugin/*.hpp") + +if(APPLE) + set(SE_WALLET_SOURCES se_wallet.cpp macos_user_auth.m) + set_source_files_properties(macos_user_presence.m PROPERTIES COMPILE_FLAGS "-x objective-c") + + find_library(security_framework security) + find_library(localauthentication_framework localauthentication) + find_library(corefoundation_framework corefoundation) + find_library(cocoa_framework cocoa) + + if(MAS_KEYCHAIN_GROUP) + add_definitions(-DMAS_KEYCHAIN_GROUP=${MAS_KEYCHAIN_GROUP}) + endif(MAS_KEYCHAIN_GROUP) +endif(APPLE) + add_library( wallet_plugin wallet.cpp wallet_plugin.cpp wallet_manager.cpp + ${SE_WALLET_SOURCES} ${HEADERS} ) -target_link_libraries( wallet_plugin eosio_chain appbase ) +target_link_libraries( wallet_plugin eosio_chain appbase ${security_framework} ${corefoundation_framework} ${localauthentication_framework} ${cocoa_framework}) target_include_directories( wallet_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/plugins/wallet_plugin/include/eosio/wallet_plugin/macos_user_auth.h b/plugins/wallet_plugin/include/eosio/wallet_plugin/macos_user_auth.h new file mode 100644 index 00000000000..90308a52bfb --- /dev/null +++ b/plugins/wallet_plugin/include/eosio/wallet_plugin/macos_user_auth.h @@ -0,0 +1,5 @@ +#pragma once + +//ask for user authentication and call callback with true/false once compelte. **Note that the callback +// will be done in a separate thread** +extern "C" void macos_user_auth(void(*cb)(int, void*), void* cb_userdata); \ No newline at end of file diff --git a/plugins/wallet_plugin/include/eosio/wallet_plugin/se_wallet.hpp b/plugins/wallet_plugin/include/eosio/wallet_plugin/se_wallet.hpp new file mode 100644 index 00000000000..5c96bc8e5a8 --- /dev/null +++ b/plugins/wallet_plugin/include/eosio/wallet_plugin/se_wallet.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +using namespace std; +using namespace eosio::chain; + +namespace eosio { namespace wallet { + +namespace detail { +struct se_wallet_impl; +} + +class se_wallet final : public wallet_api { + public: + se_wallet(); + ~se_wallet(); + + private_key_type get_private_key(public_key_type pubkey) const override; + + bool is_locked() const override; + void lock() override; + void unlock(string password) override; + void check_password(string password) override; + void set_password(string password) override; + + map list_keys() override; + flat_set list_public_keys() override; + + bool import_key(string wif_key) override; + string create_key(string key_type) override; + + optional try_sign_digest(const digest_type digest, const public_key_type public_key) override; + + private: + std::unique_ptr my; +}; + +}} \ No newline at end of file diff --git a/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_manager.hpp b/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_manager.hpp index 6aa5856f544..2603a6abb5c 100644 --- a/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_manager.hpp +++ b/plugins/wallet_plugin/include/eosio/wallet_plugin/wallet_manager.hpp @@ -19,7 +19,7 @@ namespace wallet { /// No const methods because timeout may cause lock_all() to be called. class wallet_manager { public: - wallet_manager() = default; + wallet_manager(); wallet_manager(const wallet_manager&) = delete; wallet_manager(wallet_manager&&) = delete; wallet_manager& operator=(const wallet_manager&) = delete; diff --git a/plugins/wallet_plugin/macos_user_auth.m b/plugins/wallet_plugin/macos_user_auth.m new file mode 100644 index 00000000000..801c51806fc --- /dev/null +++ b/plugins/wallet_plugin/macos_user_auth.m @@ -0,0 +1,11 @@ +#import + +void macos_user_auth(void(*cb)(int, void*), void* cb_userdata) { + static LAContext* ctx; + if(ctx) + [ctx dealloc]; + ctx = [[LAContext alloc] init]; + [ctx evaluatePolicy:kLAPolicyDeviceOwnerAuthentication localizedReason:@"unlock your EOSIO wallet" reply:^(BOOL success, NSError* error) { + cb(success, cb_userdata); + }]; +} \ No newline at end of file diff --git a/plugins/wallet_plugin/se_wallet.cpp b/plugins/wallet_plugin/se_wallet.cpp new file mode 100644 index 00000000000..225e8784eae --- /dev/null +++ b/plugins/wallet_plugin/se_wallet.cpp @@ -0,0 +1,324 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +namespace eosio { namespace wallet { + +using namespace fc::crypto::r1; + +namespace detail { + +struct se_wallet_impl { + + static public_key_data get_public_key_data(SecKeyRef key) { + SecKeyRef pubkey = SecKeyCopyPublicKey(key); + + CFErrorRef error = nullptr; + CFDataRef keyrep = nullptr; + keyrep = SecKeyCopyExternalRepresentation(pubkey, &error); + + public_key_data pub_key_data; + if(!error) { + const UInt8* cfdata = CFDataGetBytePtr(keyrep); + memcpy(pub_key_data.data+1, cfdata+1, 32); + pub_key_data.data[0] = 0x02 + (cfdata[64]&1); + } + + CFRelease(keyrep); + CFRelease(pubkey); + + if(error) { + string error_string = string_for_cferror(error); + CFRelease(error); + elog("Failed to get public key from Secure Enclave: ${m}", ("m", error_string)); + FC_THROW_EXCEPTION(chain::wallet_exception, "Failed to get public key from Secure Enclave: ${m}", ("m", error_string)); + } + + return pub_key_data; + } + + static public_key_type get_public_key(SecKeyRef key) { + char serialized_pub_key[sizeof(public_key_data) + 1]; + serialized_pub_key[0] = 0x01; + + public_key_data pub_key_data = get_public_key_data(key); + memcpy(serialized_pub_key+1, pub_key_data.data, sizeof(pub_key_data)); + + public_key_type pub_key; + fc::datastream ds(serialized_pub_key, sizeof(serialized_pub_key)); + fc::raw::unpack(ds, pub_key); + + return pub_key; + } + + static string string_for_cferror(CFErrorRef error) { + CFStringRef errorString = CFCopyDescription(error); + char buff[CFStringGetLength(errorString) + 1]; + string ret; + if(CFStringGetCString(errorString, buff, sizeof(buff), kCFStringEncodingUTF8)) + ret = buff; + else + ret = "Unknown"; + CFRelease(errorString); + return ret; + } + +#define XSTR(A) STR(A) +#define STR(A) #A + + void populate_existing_keys() { + const void* keyAttrKeys[] = { + kSecClass, + kSecAttrKeyClass, + kSecMatchLimit, + kSecReturnRef, + kSecAttrTokenID, + kSecAttrAccessGroup + }; + const void* keyAttrValues[] = { + kSecClassKey, + kSecAttrKeyClassPrivate, + kSecMatchLimitAll, + kCFBooleanTrue, + kSecAttrTokenIDSecureEnclave, +#ifdef MAS_KEYCHAIN_GROUP + CFSTR(XSTR(MAS_KEYCHAIN_GROUP)) +#endif + }; + CFDictionaryRef keyAttrDic = CFDictionaryCreate(nullptr, keyAttrKeys, keyAttrValues, sizeof(keyAttrValues)/sizeof(keyAttrValues[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFArrayRef keyRefs = nullptr; + if(SecItemCopyMatching(keyAttrDic, (CFTypeRef*)&keyRefs) || !keyRefs) { + CFRelease(keyAttrDic); + return; + } + + CFIndex count = CFArrayGetCount(keyRefs); + for(long i = 0; i < count; ++i) { + public_key_type pub; + try { + SecKeyRef key = (SecKeyRef)CFRetain(CFArrayGetValueAtIndex(keyRefs, i)); + _keys[get_public_key(key)] = key; + } + catch(chain::wallet_exception&) {} + } + CFRelease(keyRefs); + CFRelease(keyAttrDic); + } + + public_key_type create() { + SecAccessControlRef accessControlRef = SecAccessControlCreateWithFlags(nullptr, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAccessControlPrivateKeyUsage, nullptr); + + int keySizeValue = 256; + CFNumberRef keySizeNumber = CFNumberCreate(NULL, kCFNumberIntType, &keySizeValue); + + const void* keyAttrKeys[] = { + kSecAttrIsPermanent, + kSecAttrAccessControl, + kSecAttrAccessGroup + }; + const void* keyAttrValues[] = { + kCFBooleanTrue, + accessControlRef, +#ifdef MAS_KEYCHAIN_GROUP + CFSTR(XSTR(MAS_KEYCHAIN_GROUP)) +#endif + }; + CFDictionaryRef keyAttrDic = CFDictionaryCreate(NULL, keyAttrKeys, keyAttrValues, sizeof(keyAttrValues)/sizeof(keyAttrValues[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + const void* attrKeys[] = { + kSecAttrKeyType, + kSecAttrKeySizeInBits, + kSecAttrTokenID, + kSecPrivateKeyAttrs + }; + const void* atrrValues[] = { + kSecAttrKeyTypeECSECPrimeRandom, + keySizeNumber, + kSecAttrTokenIDSecureEnclave, + keyAttrDic + }; + CFDictionaryRef attributesDic = CFDictionaryCreate(NULL, attrKeys, atrrValues, sizeof(attrKeys)/sizeof(atrrValues[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + CFErrorRef error = NULL; + SecKeyRef privateKey = SecKeyCreateRandomKey(attributesDic, &error); + string error_string; + if(error) { + error_string = string_for_cferror(error); + CFRelease(error); + } + + CFRelease(attributesDic); + CFRelease(keyAttrDic); + CFRelease(keySizeNumber); + CFRelease(accessControlRef); + + if(error_string.size()) + FC_THROW_EXCEPTION(chain::wallet_exception, "Failed to create key in Secure Enclave: ${m}", ("m", error_string)); + + public_key_type pub; + try { + pub = get_public_key(privateKey); + } + catch(chain::wallet_exception&) { + //possibly we should delete the key here? + CFRelease(privateKey); + throw; + } + _keys[pub] = privateKey; + return pub; + } + + optional try_sign_digest(const digest_type d, const public_key_type public_key) { + auto it = _keys.find(public_key); + if(it == _keys.end()) + return optional{}; + + fc::ecdsa_sig sig = ECDSA_SIG_new(); + BIGNUM *r = BN_new(), *s = BN_new(); + CFErrorRef error = nullptr; + + CFDataRef digestData = CFDataCreateWithBytesNoCopy(nullptr, (UInt8*)d.data(), d.data_size(), kCFAllocatorNull); + CFDataRef signature = SecKeyCreateSignature(it->second, kSecKeyAlgorithmECDSASignatureDigestX962SHA256, digestData, &error); + if(error) { + string error_string = string_for_cferror(error); + CFRelease(error); + CFRelease(digestData); + FC_THROW_EXCEPTION(chain::wallet_exception, "Failed to sign digest in Secure Enclave: ${m}", ("m", error_string)); + } + + const UInt8* der_bytes = CFDataGetBytePtr(signature); + + BN_bin2bn(der_bytes+4, der_bytes[3], r); + BN_bin2bn(der_bytes+6+der_bytes[3], der_bytes[4+der_bytes[3]+1], s); + ECDSA_SIG_set0(sig, r, s); + + public_key_data kd; + compact_signature compact_sig; + try { + kd = get_public_key_data(it->second); + compact_sig = signature_from_ecdsa(key, kd, sig, d); + } catch(chain::wallet_exception&) { + CFRelease(signature); + CFRelease(digestData); + throw; + } + + CFRelease(signature); + CFRelease(digestData); + + char serialized_signature[sizeof(compact_sig) + 1]; + serialized_signature[0] = 0x01; + memcpy(serialized_signature+1, compact_sig.data, sizeof(compact_sig)); + + signature_type final_signature; + fc::datastream ds(serialized_signature, sizeof(serialized_signature)); + fc::raw::unpack(ds, final_signature); + return final_signature; + } + + ~se_wallet_impl() { + for(auto& k : _keys) + CFRelease(k.second); + } + + map _keys; + fc::ec_key key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + bool locked = true; +}; + +static void check_signed() { + OSStatus is_valid{0}; + pid_t pid = getpid(); + SecCodeRef code = nullptr; + CFNumberRef pidnumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pid); + CFDictionaryRef piddict = CFDictionaryCreate(kCFAllocatorDefault, (const void**)&kSecGuestAttributePid, (const void**)&pidnumber, 1, nullptr, nullptr); + if(!SecCodeCopyGuestWithAttributes(nullptr, piddict, kSecCSDefaultFlags, &code)) { + is_valid = SecCodeCheckValidity(code, kSecCSDefaultFlags, 0); + CFRelease(code); + } + CFRelease(piddict); + CFRelease(pidnumber); + + if(is_valid != errSecSuccess) { + wlog("Application does not have a valid signature; Secure Enclave support disabled"); + FC_THROW(""); + } +} + +} + +se_wallet::se_wallet() : my(new detail::se_wallet_impl()) { + detail::check_signed(); + my->populate_existing_keys(); +} + +se_wallet::~se_wallet() { +} + +private_key_type se_wallet::get_private_key(public_key_type pubkey) const { + FC_THROW_EXCEPTION(chain::wallet_exception, "Obtaining private key for a key stored in Secure Enclave is impossible"); +} + +bool se_wallet::is_locked() const { + return my->locked; +} +void se_wallet::lock() { + FC_ASSERT(!is_locked()); + my->locked = true; +} + +static void auth_callback(int success, void* data) { + promise* prom = (promise*)data; + prom->set_value(success); +} +void se_wallet::unlock(string password) { + promise prom; + future fut = prom.get_future(); + macos_user_auth(auth_callback, &prom); + if(!fut.get()) + FC_THROW_EXCEPTION(chain::wallet_invalid_password_exception, "Local user authentication failed"); + my->locked = false; +} +void se_wallet::check_password(string password) { + FC_THROW_EXCEPTION(chain::wallet_exception, "Secure Enclave wallet has no password"); +} +void se_wallet::set_password(string password) { + FC_THROW_EXCEPTION(chain::wallet_exception, "Secure Enclave wallet cannot have a password set"); +} + +map se_wallet::list_keys() { + FC_THROW_EXCEPTION(chain::wallet_exception, "Getting the private keys from the Secure Enclave wallet is impossible"); +} +flat_set se_wallet::list_public_keys() { + flat_set keys; + boost::copy(my->_keys | boost::adaptors::map_keys, std::inserter(keys, keys.end())); + return keys; +} + +bool se_wallet::import_key(string wif_key) { + FC_THROW_EXCEPTION(chain::wallet_exception, "It is not possible to import a key in to the Secure Enclave wallet"); +} + +string se_wallet::create_key(string key_type) { + return (string)my->create(); +} + +optional se_wallet::try_sign_digest(const digest_type digest, const public_key_type public_key) { + return my->try_sign_digest(digest, public_key); +} + +}} \ No newline at end of file diff --git a/plugins/wallet_plugin/wallet_manager.cpp b/plugins/wallet_plugin/wallet_manager.cpp index 610a917b727..3e0a920b593 100644 --- a/plugins/wallet_plugin/wallet_manager.cpp +++ b/plugins/wallet_plugin/wallet_manager.cpp @@ -4,6 +4,7 @@ */ #include #include +#include #include #include namespace eosio { @@ -24,6 +25,14 @@ bool valid_filename(const string& name) { return boost::filesystem::path(name).filename().string() == name; } +wallet_manager::wallet_manager() { +#ifdef __APPLE__ + try { + wallets.emplace("SecureEnclave", std::make_unique()); + } catch(fc::exception& ) {} +#endif +} + void wallet_manager::set_timeout(const std::chrono::seconds& t) { timeout = t; timeout_time = std::chrono::system_clock::now() + timeout; diff --git a/plugins/wallet_plugin/wallet_plugin.cpp b/plugins/wallet_plugin/wallet_plugin.cpp index 6159a2e53eb..09f7f71dcbd 100644 --- a/plugins/wallet_plugin/wallet_plugin.cpp +++ b/plugins/wallet_plugin/wallet_plugin.cpp @@ -16,9 +16,7 @@ namespace eosio { static appbase::abstract_plugin& _wallet_plugin = app().register_plugin(); -wallet_plugin::wallet_plugin() - : wallet_manager_ptr(new wallet_manager()) { -} +wallet_plugin::wallet_plugin() {} wallet_manager& wallet_plugin::get_wallet_manager() { return *wallet_manager_ptr; @@ -40,6 +38,8 @@ void wallet_plugin::set_program_options(options_description& cli, options_descri void wallet_plugin::plugin_initialize(const variables_map& options) { ilog("initializing wallet plugin"); + wallet_manager_ptr = std::make_unique(); + if (options.count("wallet-dir")) { auto dir = options.at("wallet-dir").as(); if (dir.is_relative()) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index c44e38ae26c..07ccb085ba8 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2185,7 +2185,7 @@ int main( int argc, char** argv ) { unlockWallet->add_option("--password", wallet_pw, localized("The password returned by wallet create")); unlockWallet->add_option( "--unlock-timeout", wallet_unlock_timeout, localized("The timeout for unlocked wallet in seconds")); unlockWallet->set_callback([&wallet_name, &wallet_pw] { - if( wallet_pw.size() == 0 ) { + if( wallet_pw.size() == 0 && wallet_name != "SecureEnclave") { std::cout << localized("password: "); fc::set_console_echo(false); std::getline( std::cin, wallet_pw, '\n' ); From 013ce6c9c836fdb8f2a79edc4a92c2713d0c8049 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Thu, 14 Jun 2018 22:29:27 -0400 Subject: [PATCH 2/5] Clean up the conditional password prompt for SecureEnclave --- programs/cleos/main.cpp | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index eb55bb05687..aa16ea284b8 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -249,6 +249,15 @@ chain::action generate_nonce_action() { return chain::action( {}, config::null_account_name, "nonce", fc::raw::pack(fc::time_point::now().time_since_epoch().count())); } +void prompt_for_wallet_password(string& pw, const string& name) { + if(pw.size() == 0 && name != "SecureEnclave") { + std::cout << localized("password: "); + fc::set_console_echo(false); + std::getline( std::cin, pw, '\n' ); + fc::set_console_echo(true); + } +} + fc::variant determine_required_keys(const signed_transaction& trx) { // TODO better error checking //wdump((trx)); @@ -2185,12 +2194,7 @@ int main( int argc, char** argv ) { unlockWallet->add_option("--password", wallet_pw, localized("The password returned by wallet create")); unlockWallet->add_option( "--unlock-timeout", wallet_unlock_timeout, localized("The timeout for unlocked wallet in seconds")); unlockWallet->set_callback([&wallet_name, &wallet_pw] { - if( wallet_pw.size() == 0 && wallet_name != "SecureEnclave") { - std::cout << localized("password: "); - fc::set_console_echo(false); - std::getline( std::cin, wallet_pw, '\n' ); - fc::set_console_echo(true); - } + prompt_for_wallet_password(wallet_pw, wallet_name); fc::variants vs = {fc::variant(wallet_name), fc::variant(wallet_pw)}; call(wallet_url, wallet_unlock, vs); @@ -2223,12 +2227,7 @@ int main( int argc, char** argv ) { removeKeyWallet->add_option("key", wallet_rm_key_str, localized("Public key in WIF format to remove"))->required(); removeKeyWallet->add_option("--password", wallet_pw, localized("The password returned by wallet create")); removeKeyWallet->set_callback([&wallet_name, &wallet_pw, &wallet_rm_key_str] { - if( wallet_pw.size() == 0 ) { - std::cout << localized("password: "); - fc::set_console_echo(false); - std::getline( std::cin, wallet_pw, '\n' ); - fc::set_console_echo(true); - } + prompt_for_wallet_password(wallet_pw, wallet_name); public_key_type pubkey; try { pubkey = public_key_type( wallet_rm_key_str ); @@ -2272,12 +2271,7 @@ int main( int argc, char** argv ) { listPrivKeys->add_option("-n,--name", wallet_name, localized("The name of the wallet to list keys from"), true); listPrivKeys->add_option("--password", wallet_pw, localized("The password returned by wallet create")); listPrivKeys->set_callback([&wallet_name, &wallet_pw] { - if( wallet_pw.size() == 0 ) { - std::cout << localized("password: "); - fc::set_console_echo(false); - std::getline( std::cin, wallet_pw, '\n' ); - fc::set_console_echo(true); - } + prompt_for_wallet_password(wallet_pw, wallet_name); fc::variants vs = {fc::variant(wallet_name), fc::variant(wallet_pw)}; const auto& v = call(wallet_url, wallet_list_keys, vs); std::cout << fc::json::to_pretty_string(v) << std::endl; From 0092b4ffbfdbc563199227ed2071e181e1488bf1 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Thu, 14 Jun 2018 22:30:07 -0400 Subject: [PATCH 3/5] Stub remove_key support for se_wallet Can't get this working yet; just disallow it for now --- .../include/eosio/wallet_plugin/se_wallet.hpp | 1 + plugins/wallet_plugin/se_wallet.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/wallet_plugin/include/eosio/wallet_plugin/se_wallet.hpp b/plugins/wallet_plugin/include/eosio/wallet_plugin/se_wallet.hpp index 5c96bc8e5a8..e5c70f1a307 100644 --- a/plugins/wallet_plugin/include/eosio/wallet_plugin/se_wallet.hpp +++ b/plugins/wallet_plugin/include/eosio/wallet_plugin/se_wallet.hpp @@ -30,6 +30,7 @@ class se_wallet final : public wallet_api { bool import_key(string wif_key) override; string create_key(string key_type) override; + bool remove_key(string key) override; optional try_sign_digest(const digest_type digest, const public_key_type public_key) override; diff --git a/plugins/wallet_plugin/se_wallet.cpp b/plugins/wallet_plugin/se_wallet.cpp index 225e8784eae..e26d93395f6 100644 --- a/plugins/wallet_plugin/se_wallet.cpp +++ b/plugins/wallet_plugin/se_wallet.cpp @@ -151,7 +151,7 @@ struct se_wallet_impl { kSecAttrTokenIDSecureEnclave, keyAttrDic }; - CFDictionaryRef attributesDic = CFDictionaryCreate(NULL, attrKeys, atrrValues, sizeof(attrKeys)/sizeof(atrrValues[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFDictionaryRef attributesDic = CFDictionaryCreate(NULL, attrKeys, atrrValues, sizeof(attrKeys)/sizeof(attrKeys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFErrorRef error = NULL; SecKeyRef privateKey = SecKeyCreateRandomKey(attributesDic, &error); @@ -294,7 +294,7 @@ void se_wallet::unlock(string password) { my->locked = false; } void se_wallet::check_password(string password) { - FC_THROW_EXCEPTION(chain::wallet_exception, "Secure Enclave wallet has no password"); + //just leave this as a noop for now; remove_key from wallet_mgr calls through here } void se_wallet::set_password(string password) { FC_THROW_EXCEPTION(chain::wallet_exception, "Secure Enclave wallet cannot have a password set"); @@ -317,6 +317,10 @@ string se_wallet::create_key(string key_type) { return (string)my->create(); } +bool se_wallet::remove_key(string key) { + FC_THROW_EXCEPTION(chain::wallet_exception, "Secure Enclave wallet does not support removing keys yet"); +} + optional se_wallet::try_sign_digest(const digest_type digest, const public_key_type public_key) { return my->try_sign_digest(digest, public_key); } From 1708c759dbb912ae2bbdcd7a124b23edfc40bd44 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Sun, 17 Jun 2018 13:25:39 -0400 Subject: [PATCH 4/5] Only enable Secure Enclave on hardware that has it ... But there appears to be no way to query this beyond waiting for a tricky error message from one of the find/add APIs??? For now just use the model number of the system which of coruse is totally bogus and fragile --- plugins/wallet_plugin/se_wallet.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/plugins/wallet_plugin/se_wallet.cpp b/plugins/wallet_plugin/se_wallet.cpp index e26d93395f6..a93172d4b3f 100644 --- a/plugins/wallet_plugin/se_wallet.cpp +++ b/plugins/wallet_plugin/se_wallet.cpp @@ -263,7 +263,25 @@ static void check_signed() { se_wallet::se_wallet() : my(new detail::se_wallet_impl()) { detail::check_signed(); - my->populate_existing_keys(); + + //How to figure out of SE is available?! Totally bogus. But no other blessed way?!?! + char model[256]; + size_t model_size = sizeof(model); + if(sysctlbyname("hw.model", model, &model_size, nullptr, 0) == 0) { + if(strncmp(model, "iMacPro", strlen("iMacPro")) == 0) { + my->populate_existing_keys(); + return; + } + unsigned int major, minor; + if(sscanf(model, "MacBookPro%u,%u", &major, &minor) == 2) { + if(major >= 13 && minor >= 2) { + my->populate_existing_keys(); + return; + } + } + } + + FC_THROW("Secure Enclave not supported on this hardware"); } se_wallet::~se_wallet() { From fa9c4952195a4903af28822558cbc4b04af5eaa4 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Wed, 20 Jun 2018 12:55:27 -0400 Subject: [PATCH 5/5] support for removing keys from SecureEnclave wallet --- .../eosio/wallet_plugin/macos_user_auth.h | 4 +- plugins/wallet_plugin/macos_user_auth.m | 4 +- plugins/wallet_plugin/se_wallet.cpp | 42 +++++++++++++++---- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/plugins/wallet_plugin/include/eosio/wallet_plugin/macos_user_auth.h b/plugins/wallet_plugin/include/eosio/wallet_plugin/macos_user_auth.h index 90308a52bfb..4deab7f3d8a 100644 --- a/plugins/wallet_plugin/include/eosio/wallet_plugin/macos_user_auth.h +++ b/plugins/wallet_plugin/include/eosio/wallet_plugin/macos_user_auth.h @@ -1,5 +1,7 @@ #pragma once +#include + //ask for user authentication and call callback with true/false once compelte. **Note that the callback // will be done in a separate thread** -extern "C" void macos_user_auth(void(*cb)(int, void*), void* cb_userdata); \ No newline at end of file +extern "C" void macos_user_auth(void(*cb)(int, void*), void* cb_userdata, CFStringRef message); \ No newline at end of file diff --git a/plugins/wallet_plugin/macos_user_auth.m b/plugins/wallet_plugin/macos_user_auth.m index 801c51806fc..75c319aeae4 100644 --- a/plugins/wallet_plugin/macos_user_auth.m +++ b/plugins/wallet_plugin/macos_user_auth.m @@ -1,11 +1,11 @@ #import -void macos_user_auth(void(*cb)(int, void*), void* cb_userdata) { +void macos_user_auth(void(*cb)(int, void*), void* cb_userdata, CFStringRef message) { static LAContext* ctx; if(ctx) [ctx dealloc]; ctx = [[LAContext alloc] init]; - [ctx evaluatePolicy:kLAPolicyDeviceOwnerAuthentication localizedReason:@"unlock your EOSIO wallet" reply:^(BOOL success, NSError* error) { + [ctx evaluatePolicy:kLAPolicyDeviceOwnerAuthentication localizedReason:(NSString*)message reply:^(BOOL success, NSError* error) { cb(success, cb_userdata); }]; } \ No newline at end of file diff --git a/plugins/wallet_plugin/se_wallet.cpp b/plugins/wallet_plugin/se_wallet.cpp index a93172d4b3f..d874093a302 100644 --- a/plugins/wallet_plugin/se_wallet.cpp +++ b/plugins/wallet_plugin/se_wallet.cpp @@ -21,6 +21,11 @@ using namespace fc::crypto::r1; namespace detail { +static void auth_callback(int success, void* data) { + promise* prom = (promise*)data; + prom->set_value(success); +} + struct se_wallet_impl { static public_key_data get_public_key_data(SecKeyRef key) { @@ -43,7 +48,6 @@ struct se_wallet_impl { if(error) { string error_string = string_for_cferror(error); CFRelease(error); - elog("Failed to get public key from Secure Enclave: ${m}", ("m", error_string)); FC_THROW_EXCEPTION(chain::wallet_exception, "Failed to get public key from Secure Enclave: ${m}", ("m", error_string)); } @@ -230,6 +234,31 @@ struct se_wallet_impl { return final_signature; } + bool remove_key(string public_key) { + auto it = _keys.find(public_key_type{public_key}); + if(it == _keys.end()) + FC_THROW_EXCEPTION(chain::wallet_exception, "Given key to delete not found in Secure Enclave wallet"); + + promise prom; + future fut = prom.get_future(); + macos_user_auth(auth_callback, &prom, CFSTR("remove a key from your EOSIO wallet")); + if(!fut.get()) + FC_THROW_EXCEPTION(chain::wallet_invalid_password_exception, "Local user authentication failed"); + + CFDictionaryRef deleteDic = CFDictionaryCreate(nullptr, (const void**)&kSecValueRef, (const void**)&it->second, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + OSStatus ret = SecItemDelete(deleteDic); + CFRelease(deleteDic); + + if(ret) + FC_THROW_EXCEPTION(chain::wallet_exception, "Failed to getremove key from Secure Enclave"); + + CFRelease(it->second); + _keys.erase(it); + + return true; + } + ~se_wallet_impl() { for(auto& k : _keys) CFRelease(k.second); @@ -264,7 +293,7 @@ static void check_signed() { se_wallet::se_wallet() : my(new detail::se_wallet_impl()) { detail::check_signed(); - //How to figure out of SE is available?! Totally bogus. But no other blessed way?!?! + //How to figure out of SE is available?! char model[256]; size_t model_size = sizeof(model); if(sysctlbyname("hw.model", model, &model_size, nullptr, 0) == 0) { @@ -299,14 +328,10 @@ void se_wallet::lock() { my->locked = true; } -static void auth_callback(int success, void* data) { - promise* prom = (promise*)data; - prom->set_value(success); -} void se_wallet::unlock(string password) { promise prom; future fut = prom.get_future(); - macos_user_auth(auth_callback, &prom); + macos_user_auth(detail::auth_callback, &prom, CFSTR("unlock your EOSIO wallet")); if(!fut.get()) FC_THROW_EXCEPTION(chain::wallet_invalid_password_exception, "Local user authentication failed"); my->locked = false; @@ -336,7 +361,8 @@ string se_wallet::create_key(string key_type) { } bool se_wallet::remove_key(string key) { - FC_THROW_EXCEPTION(chain::wallet_exception, "Secure Enclave wallet does not support removing keys yet"); + FC_ASSERT(!is_locked()); + return my->remove_key(key); } optional se_wallet::try_sign_digest(const digest_type digest, const public_key_type public_key) {