未验证 提交 6759c79e 编写于 作者: M Matt Witherspoon 提交者: GitHub

Merge pull request #4244 from EOSIO/se_wallet

Secure Enclave wallet support
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" )
#pragma once
#include <CoreFoundation/CoreFoundation.h>
//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, CFStringRef message);
\ No newline at end of file
#pragma once
#include <eosio/chain/types.hpp>
#include <eosio/wallet_plugin/wallet_api.hpp>
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<public_key_type, private_key_type> list_keys() override;
flat_set<public_key_type> list_public_keys() override;
bool import_key(string wif_key) override;
string create_key(string key_type) override;
bool remove_key(string key) override;
optional<signature_type> try_sign_digest(const digest_type digest, const public_key_type public_key) override;
private:
std::unique_ptr<detail::se_wallet_impl> my;
};
}}
\ No newline at end of file
......@@ -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;
......
#import <LocalAuthentication/LocalAuthentication.h>
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:(NSString*)message reply:^(BOOL success, NSError* error) {
cb(success, cb_userdata);
}];
}
\ No newline at end of file
/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
#include <eosio/wallet_plugin/se_wallet.hpp>
#include <eosio/wallet_plugin/macos_user_auth.h>
#include <eosio/chain/exceptions.hpp>
#include <fc/crypto/openssl.hpp>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <Security/Security.h>
#include <future>
namespace eosio { namespace wallet {
using namespace fc::crypto::r1;
namespace detail {
static void auth_callback(int success, void* data) {
promise<bool>* prom = (promise<bool>*)data;
prom->set_value(success);
}
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);
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<const char *> 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(attrKeys[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<signature_type> 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<signature_type>{};
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<const char *> ds(serialized_signature, sizeof(serialized_signature));
fc::raw::unpack(ds, final_signature);
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<bool> prom;
future<bool> 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);
}
map<public_key_type,SecKeyRef> _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();
//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) {
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() {
}
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;
}
void se_wallet::unlock(string password) {
promise<bool> prom;
future<bool> fut = prom.get_future();
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;
}
void se_wallet::check_password(string 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");
}
map<public_key_type, private_key_type> se_wallet::list_keys() {
FC_THROW_EXCEPTION(chain::wallet_exception, "Getting the private keys from the Secure Enclave wallet is impossible");
}
flat_set<public_key_type> se_wallet::list_public_keys() {
flat_set<public_key_type> 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();
}
bool se_wallet::remove_key(string key) {
FC_ASSERT(!is_locked());
return my->remove_key(key);
}
optional<signature_type> 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
......@@ -4,6 +4,7 @@
*/
#include <eosio/wallet_plugin/wallet_manager.hpp>
#include <eosio/wallet_plugin/wallet.hpp>
#include <eosio/wallet_plugin/se_wallet.hpp>
#include <eosio/chain/exceptions.hpp>
#include <boost/algorithm/string.hpp>
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<se_wallet>());
} catch(fc::exception& ) {}
#endif
}
void wallet_manager::set_timeout(const std::chrono::seconds& t) {
timeout = t;
timeout_time = std::chrono::system_clock::now() + timeout;
......
......@@ -16,9 +16,7 @@ namespace eosio {
static appbase::abstract_plugin& _wallet_plugin = app().register_plugin<wallet_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;
......@@ -38,6 +36,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<wallet_manager>();
if (options.count("wallet-dir")) {
auto dir = options.at("wallet-dir").as<boost::filesystem::path>();
if (dir.is_relative())
......
......@@ -248,6 +248,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));
......@@ -2188,12 +2197,7 @@ int main( int argc, char** argv ) {
unlockWallet->add_option("-n,--name", wallet_name, localized("The name of the wallet to unlock"));
unlockWallet->add_option("--password", wallet_pw, localized("The password returned by wallet create"));
unlockWallet->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)};
call(wallet_url, wallet_unlock, vs);
......@@ -2226,12 +2230,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 );
......@@ -2275,12 +2274,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;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册