Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Commit

Permalink
Merge pull request #8730 from EOSIO/nodeos_se
Browse files Browse the repository at this point in the history
Add integrated Secure Enclave block signing for nodeos
  • Loading branch information
spoonincode authored May 8, 2020
2 parents 76cccfc + 3c09b75 commit 9c89016
Show file tree
Hide file tree
Showing 17 changed files with 618 additions and 305 deletions.
30 changes: 24 additions & 6 deletions CMakeModules/MASSigning.cmake
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
macro(mas_sign target)
if(APPLE AND MAS_CERT_FINGERPRINT AND MAS_BASE_APPID AND MAS_PROVISIONING_PROFILE)
set(MAS_ENABLED ON)
if(MAS_KEYCHAIN_GROUP)
set(MAS_LEGACY ON)
endif()
endif()

macro(mas_sign target) #optional argument that forces keygroup when MAS_KEYCHAIN_GROUP not set

#example values:
# MAS_CERT_FINGERPRINT=C5139C2C4D7FA071EFBFD86CE44B652631C9376A
# MAS_BASE_APPID=5A4683969Z.com.example. <<note the trailing period on this
# MAS_PROVISIONING_PROFILE="/Users/spoon/Library/MobileDevice/Provisioning Profiles/b1d57761-e5b8-4e58-b412-f1cd0f1924a1.provisionprofile"
# MAS_KEYCHAIN_GROUP=5A4683969Z.com.example.keyz
#Given the above, the executable will be signed via the certificate in the keystore matching the fingerprint and bundled with the
# specified provisioning profile. The appid will the base plus the name of the target, 5A4683969Z.com.example.keosd for example. And
# the entitlements file will have a keychain sharing group of 5A4683969Z.com.example.keyz
# specified provisioning profile. The appid will the base plus the name of the target, 5A4683969Z.com.example.keosd for example.
#
#Additionally, it is possible to specify a keychain group to place the keys in. By default this will be 5A4683969Z.com.example.keosd in
# the above example. But, it is possible to manually specify a different key group. Specifying a keychain group was required before 3.0 and
# users who have keys created in the SE before 3.0 will need to continue using the manually specified keychain group to have access to those
# keys. To specify a keychain group manually define MAS_KEYCHAIN_GROUP, for example:
# MAS_KEYCHAIN_GROUP=5A4683969Z.com.example.keyz

if(APPLE AND MAS_CERT_FINGERPRINT AND MAS_BASE_APPID AND MAS_PROVISIONING_PROFILE AND MAS_KEYCHAIN_GROUP)
if(MAS_ENABLED)
if(MAS_KEYCHAIN_GROUP)
set(COMPUTED_KEYCHAIN ${MAS_KEYCHAIN_GROUP})
elseif(${ARGC} EQUAL 2)
set(COMPUTED_KEYCHAIN ${MAS_BASE_APPID}${ARGV1})
else()
set(COMPUTED_KEYCHAIN ${MAS_BASE_APPID}$<TARGET_FILE_NAME:${target}>)
endif()

add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${CMAKE_SOURCE_DIR}/tools/mas_sign.sh ${MAS_CERT_FINGERPRINT} ${MAS_BASE_APPID}$<TARGET_FILE_NAME:${target}> ${MAS_PROVISIONING_PROFILE} ${MAS_KEYCHAIN_GROUP} $<TARGET_FILE_NAME:${target}>
COMMAND ${CMAKE_SOURCE_DIR}/tools/mas_sign.sh ${MAS_CERT_FINGERPRINT} ${MAS_BASE_APPID}$<TARGET_FILE_NAME:${target}> ${MAS_PROVISIONING_PROFILE} ${COMPUTED_KEYCHAIN} $<TARGET_FILE_NAME:${target}>
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
VERBATIM
)
Expand Down
1 change: 1 addition & 0 deletions libraries/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ add_subdirectory( state_history )
add_subdirectory( abieos )
add_subdirectory( rocksdb EXCLUDE_FROM_ALL )
add_subdirectory( chain_kv )
add_subdirectory( se-helpers )

set(USE_EXISTING_SOFTFLOAT ON CACHE BOOL "use pre-exisiting softfloat lib")
set(ENABLE_TOOLS OFF CACHE BOOL "Build tools")
Expand Down
7 changes: 7 additions & 0 deletions libraries/se-helpers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
if(NOT APPLE)
return()
endif()

add_library(se-helpers se-helpers.cpp)
target_include_directories(se-helpers PUBLIC include)
target_link_libraries(se-helpers fc)
42 changes: 42 additions & 0 deletions libraries/se-helpers/include/eosio/se-helpers/se-helpers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include <fc/crypto/public_key.hpp>
#include <fc/crypto/signature.hpp>
#include <fc/crypto/sha256.hpp>

#include <set>

namespace eosio::secure_enclave {

class secure_enclave_key {
public:
const fc::crypto::public_key& public_key() const;
fc::crypto::signature sign(const fc::sha256& digest) const;

secure_enclave_key(const secure_enclave_key&);
secure_enclave_key(secure_enclave_key&&);

bool operator<(const secure_enclave_key& r) const {
return public_key() < r.public_key();
}

//only for use by get_all_keys()/create_key()
secure_enclave_key(void*);

~secure_enclave_key() = default;
private:
struct impl;
constexpr static size_t fwd_size = 128;
fc::fwd<impl,fwd_size> my;

friend void delete_key(secure_enclave_key&& key);
};

bool hardware_supports_secure_enclave();
bool application_signed();

std::set<secure_enclave_key> get_all_keys();
secure_enclave_key create_key();
void delete_key(secure_enclave_key&& key);

}
245 changes: 245 additions & 0 deletions libraries/se-helpers/se-helpers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
#include <eosio/se-helpers/se-helpers.hpp>

#include <fc/fwd_impl.hpp>
#include <fc/scoped_exit.hpp>

#include <Security/Security.h>

namespace eosio::secure_enclave {

static std::string string_for_cferror(CFErrorRef error) {
CFStringRef errorString = CFCopyDescription(error);
auto release_errorString = fc::make_scoped_exit([&errorString](){CFRelease(errorString);});

std::string ret(CFStringGetLength(errorString), '\0');
if(!CFStringGetCString(errorString, ret.data(), ret.size(), kCFStringEncodingUTF8))
ret = "Unknown";
return ret;
}

struct secure_enclave_key::impl {
SecKeyRef key_ref = NULL;
fc::crypto::public_key pub_key;

~impl() {
if(key_ref)
CFRelease(key_ref);
key_ref = NULL;
}

void populate_public_key() {
//without a good way to create fc::public_key direct, create a serialized version to create a public_key from
char serialized_public_key[1 + sizeof(fc::crypto::r1::public_key_data)] = {fc::crypto::public_key::storage_type::position<fc::crypto::r1::public_key_shim>()};

SecKeyRef pubkey = SecKeyCopyPublicKey(key_ref);

CFErrorRef error = NULL;
CFDataRef keyrep = NULL;
keyrep = SecKeyCopyExternalRepresentation(pubkey, &error);

if(!error) {
const UInt8 *cfdata = CFDataGetBytePtr(keyrep);
memcpy(serialized_public_key + 2, cfdata + 1, 32);
serialized_public_key[1] = 0x02u + (cfdata[64] & 1u);
}

CFRelease(keyrep);
CFRelease(pubkey);

if(error) {
auto release_error = fc::make_scoped_exit([&error](){CFRelease(error);});
FC_ASSERT(false, "Failed to get public key from Secure Enclave: ${m}", ("m", string_for_cferror(error)));
}

fc::datastream<const char*> ds(serialized_public_key, sizeof(serialized_public_key));
fc::raw::unpack(ds, pub_key);
}
};

secure_enclave_key::secure_enclave_key(void* seckeyref) {
static_assert(sizeof(impl) <= fwd_size);

my->key_ref = (SecKeyRef)(seckeyref);
my->populate_public_key();
CFRetain(my->key_ref);
}

secure_enclave_key::secure_enclave_key(const secure_enclave_key& o) {
my->key_ref = (SecKeyRef)CFRetain(o.my->key_ref);
my->pub_key = o.public_key();
}

secure_enclave_key::secure_enclave_key(secure_enclave_key&& o) {
my->key_ref = o.my->key_ref;
o.my->key_ref = NULL;
my->pub_key = o.my->pub_key;
}

const fc::crypto::public_key &secure_enclave_key::public_key() const {
return my->pub_key;
}

fc::crypto::signature secure_enclave_key::sign(const fc::sha256& digest) const {
fc::ecdsa_sig sig = ECDSA_SIG_new();
CFErrorRef error = NULL;

CFDataRef digestData = CFDataCreateWithBytesNoCopy(NULL, (UInt8*)digest.data(), digest.data_size(), kCFAllocatorNull);
CFDataRef signature = SecKeyCreateSignature(my->key_ref, kSecKeyAlgorithmECDSASignatureDigestX962SHA256, digestData, &error);

auto cleanup = fc::make_scoped_exit([&digestData, &signature]() {
CFRelease(digestData);
if(signature)
CFRelease(signature);
});

if(error) {
auto release_error = fc::make_scoped_exit([&error](){CFRelease(error);});
std::string error_string = string_for_cferror(error);
FC_ASSERT(false, "Failed to sign digest in Secure Enclave: ${m}", ("m", error_string));
}

const UInt8* der_bytes = CFDataGetBytePtr(signature);
long derSize = CFDataGetLength(signature);
d2i_ECDSA_SIG(&sig.obj, &der_bytes, derSize);

char serialized_signature[sizeof(fc::crypto::r1::compact_signature) + 1] = {fc::crypto::signature::storage_type::position<fc::crypto::r1::signature_shim>()};

fc::crypto::r1::compact_signature* compact_sig = (fc::crypto::r1::compact_signature *)(serialized_signature + 1);
fc::ec_key key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
*compact_sig = fc::crypto::r1::signature_from_ecdsa(key,my->pub_key._storage.get<fc::crypto::r1::public_key_shim>()._data, sig, digest);

fc::crypto::signature final_signature;
fc::datastream<const char*> ds(serialized_signature, sizeof(serialized_signature));
fc::raw::unpack(ds, final_signature);
return final_signature;
}

secure_enclave_key create_key() {
SecAccessControlRef accessControlRef = SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAccessControlPrivateKeyUsage, NULL);

int keySizeValue = 256;
CFNumberRef keySizeNumber = CFNumberCreate(NULL, kCFNumberIntType, &keySizeValue);

const void* keyAttrKeys[] = {
kSecAttrIsPermanent,
kSecAttrAccessControl
};
const void* keyAttrValues[] = {
kCFBooleanTrue,
accessControlRef
};
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);

auto cleanup = fc::make_scoped_exit([&attributesDic, &keyAttrDic, &keySizeNumber, &accessControlRef]() {
CFRelease(attributesDic);
CFRelease(keyAttrDic);
CFRelease(keySizeNumber);
CFRelease(accessControlRef);
});

CFErrorRef error = NULL;
SecKeyRef privateKey = SecKeyCreateRandomKey(attributesDic, &error);
if(error) {
auto release_error = fc::make_scoped_exit([&error](){CFRelease(error);});
FC_ASSERT(false, "Failed to create key in Secure Enclave: ${m}", ("m", string_for_cferror(error)));
}

return secure_enclave_key(privateKey);
}

std::set<secure_enclave_key> get_all_keys() {
const void* keyAttrKeys[] = {
kSecClass,
kSecAttrKeyClass,
kSecMatchLimit,
kSecReturnRef,
kSecAttrTokenID
};
const void* keyAttrValues[] = {
kSecClassKey,
kSecAttrKeyClassPrivate,
kSecMatchLimitAll,
kCFBooleanTrue,
kSecAttrTokenIDSecureEnclave,
};
CFDictionaryRef keyAttrDic = CFDictionaryCreate(NULL, keyAttrKeys, keyAttrValues, sizeof(keyAttrValues)/sizeof(keyAttrValues[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
auto cleanup_keyAttrDic = fc::make_scoped_exit([&keyAttrDic](){CFRelease(keyAttrDic);});

std::set<secure_enclave_key> ret;

CFArrayRef keyRefs = NULL;
if(SecItemCopyMatching(keyAttrDic, (CFTypeRef*)&keyRefs) || !keyRefs)
return ret;
auto cleanup_keyRefs = fc::make_scoped_exit([&keyRefs](){CFRelease(keyRefs);});

CFIndex count = CFArrayGetCount(keyRefs);
for(long i = 0; i < count; ++i)
ret.emplace((void*)CFArrayGetValueAtIndex(keyRefs, i));

return ret;
}

void delete_key(secure_enclave_key&& key) {
CFDictionaryRef deleteDic = CFDictionaryCreate(NULL, (const void**)&kSecValueRef, (const void**)&key.my->key_ref, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

OSStatus ret = SecItemDelete(deleteDic);
CFRelease(deleteDic);

FC_ASSERT(ret == 0, "Failed to remove key from Secure Enclave");
}

bool hardware_supports_secure_enclave() {
//How to figure out if 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)
return true;
unsigned int major, minor;
if(sscanf(model, "MacBookPro%u,%u", &major, &minor) == 2) {
if((major >= 15) || (major >= 13 && minor >= 2)) {
return true;
}
}
if(sscanf(model, "Macmini%u", &major) == 1 && major >= 8)
return true;
if(sscanf(model, "MacBookAir%u", &major) == 1 && major >= 8)
return true;
if(sscanf(model, "MacPro%u", &major) == 1 && major >= 7)
return true;
}

return false;
}

bool application_signed() {
OSStatus is_valid{-1};
pid_t pid = getpid();
SecCodeRef code = NULL;
CFNumberRef pidnumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pid);
CFDictionaryRef piddict = CFDictionaryCreate(kCFAllocatorDefault, (const void**)&kSecGuestAttributePid, (const void**)&pidnumber, 1, NULL, NULL);
if(!SecCodeCopyGuestWithAttributes(NULL, piddict, kSecCSDefaultFlags, &code)) {
is_valid = SecCodeCheckValidity(code, kSecCSDefaultFlags, 0);
CFRelease(code);
}
CFRelease(piddict);
CFRelease(pidnumber);

return is_valid == errSecSuccess;
}

}
3 changes: 3 additions & 0 deletions plugins/producer_plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@ add_library( producer_plugin
)

target_link_libraries( producer_plugin chain_plugin http_client_plugin appbase eosio_chain )
if(APPLE)
target_link_libraries( producer_plugin se-helpers )
endif()
target_include_directories( producer_plugin
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../chain_interface/include" )
Loading

0 comments on commit 9c89016

Please sign in to comment.