7#include <boost/range/adaptor/map.hpp>
8#include <boost/range/algorithm/copy.hpp>
10#include <Security/Security.h>
14namespace sysio {
namespace wallet {
20static void auth_callback(
int success,
void* data) {
21 promise<bool>* prom = (promise<bool>*)data;
22 prom->set_value(success);
28 SecKeyRef
pubkey = SecKeyCopyPublicKey(key);
30 CFErrorRef error =
nullptr;
31 CFDataRef keyrep =
nullptr;
32 keyrep = SecKeyCopyExternalRepresentation(
pubkey, &error);
36 const UInt8* cfdata = CFDataGetBytePtr(keyrep);
38 pub_key_data.
data[0] = 0x02 + (cfdata[64]&1);
47 FC_THROW_EXCEPTION(chain::wallet_exception,
"Failed to get public key from Secure Enclave: ${m}", (
"m", error_string));
55 serialized_pub_key[0] = 0x01;
58 memcpy(serialized_pub_key+1, pub_key_data.
data,
sizeof(pub_key_data));
68 CFStringRef errorString = CFCopyDescription(error);
69 char buff[CFStringGetLength(errorString) + 1];
71 if(CFStringGetCString(errorString, buff,
sizeof(buff), kCFStringEncodingUTF8))
75 CFRelease(errorString);
83 const void* keyAttrKeys[] = {
91 const void* keyAttrValues[] = {
93 kSecAttrKeyClassPrivate,
96 kSecAttrTokenIDSecureEnclave,
97#ifdef MAS_KEYCHAIN_GROUP
98 CFSTR(
XSTR(MAS_KEYCHAIN_GROUP))
101 CFDictionaryRef keyAttrDic = CFDictionaryCreate(
nullptr, keyAttrKeys, keyAttrValues,
sizeof(keyAttrValues)/
sizeof(keyAttrValues[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
103 CFArrayRef keyRefs =
nullptr;
104 if(SecItemCopyMatching(keyAttrDic, (CFTypeRef*)&keyRefs) || !keyRefs) {
105 CFRelease(keyAttrDic);
109 CFIndex
count = CFArrayGetCount(keyRefs);
110 for(
long i = 0; i <
count; ++i) {
113 SecKeyRef key = (SecKeyRef)CFRetain(CFArrayGetValueAtIndex(keyRefs, i));
116 catch(chain::wallet_exception&) {}
119 CFRelease(keyAttrDic);
123 SecAccessControlRef accessControlRef = SecAccessControlCreateWithFlags(
nullptr, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAccessControlPrivateKeyUsage,
nullptr);
125 int keySizeValue = 256;
126 CFNumberRef keySizeNumber = CFNumberCreate(NULL, kCFNumberIntType, &keySizeValue);
128 const void* keyAttrKeys[] = {
130 kSecAttrAccessControl,
133 const void* keyAttrValues[] = {
136#ifdef MAS_KEYCHAIN_GROUP
137 CFSTR(
XSTR(MAS_KEYCHAIN_GROUP))
140 CFDictionaryRef keyAttrDic = CFDictionaryCreate(NULL, keyAttrKeys, keyAttrValues,
sizeof(keyAttrValues)/
sizeof(keyAttrValues[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
142 const void* attrKeys[] = {
144 kSecAttrKeySizeInBits,
148 const void* atrrValues[] = {
149 kSecAttrKeyTypeECSECPrimeRandom,
151 kSecAttrTokenIDSecureEnclave,
154 CFDictionaryRef attributesDic = CFDictionaryCreate(NULL, attrKeys, atrrValues,
sizeof(attrKeys)/
sizeof(attrKeys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
156 CFErrorRef error = NULL;
157 SecKeyRef privateKey = SecKeyCreateRandomKey(attributesDic, &error);
164 CFRelease(attributesDic);
165 CFRelease(keyAttrDic);
166 CFRelease(keySizeNumber);
167 CFRelease(accessControlRef);
169 if(error_string.size())
170 FC_THROW_EXCEPTION(chain::wallet_exception,
"Failed to create key in Secure Enclave: ${m}", (
"m", error_string));
176 catch(chain::wallet_exception&) {
178 CFRelease(privateKey);
187 if(it ==
_keys.end())
188 return std::optional<signature_type>{};
190 fc::ecdsa_sig sig = ECDSA_SIG_new();
191 CFErrorRef error =
nullptr;
193 CFDataRef digestData = CFDataCreateWithBytesNoCopy(
nullptr, (UInt8*)
d.data(),
d.data_size(), kCFAllocatorNull);
194 CFDataRef
signature = SecKeyCreateSignature(it->second, kSecKeyAlgorithmECDSASignatureDigestX962SHA256, digestData, &error);
198 CFRelease(digestData);
199 FC_THROW_EXCEPTION(chain::wallet_exception,
"Failed to sign digest in Secure Enclave: ${m}", (
"m", error_string));
202 const UInt8* der_bytes = CFDataGetBytePtr(
signature);
203 long derSize = CFDataGetLength(
signature);
204 d2i_ECDSA_SIG(&sig.obj, &der_bytes, derSize);
211 }
catch(chain::wallet_exception&) {
213 CFRelease(digestData);
218 CFRelease(digestData);
220 char serialized_signature[
sizeof(compact_sig) + 1];
221 serialized_signature[0] = 0x01;
222 memcpy(serialized_signature+1, compact_sig.
data,
sizeof(compact_sig));
227 return final_signature;
232 if(it ==
_keys.end())
233 FC_THROW_EXCEPTION(chain::wallet_exception,
"Given key to delete not found in Secure Enclave wallet");
236 future<bool> fut = prom.get_future();
237 macos_user_auth(auth_callback, &prom, CFSTR(
"remove a key from your SYSIO wallet"));
239 FC_THROW_EXCEPTION(chain::wallet_invalid_password_exception,
"Local user authentication failed");
241 CFDictionaryRef deleteDic = CFDictionaryCreate(
nullptr, (
const void**)&kSecValueRef, (
const void**)&it->second, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
243 OSStatus
ret = SecItemDelete(deleteDic);
244 CFRelease(deleteDic);
247 FC_THROW_EXCEPTION(chain::wallet_exception,
"Failed to getremove key from Secure Enclave");
249 CFRelease(it->second);
260 map<public_key_type,SecKeyRef>
_keys;
261 fc::ec_key key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
265static void check_signed() {
266 OSStatus is_valid{0};
267 pid_t pid = getpid();
268 SecCodeRef code =
nullptr;
269 CFNumberRef pidnumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pid);
270 CFDictionaryRef piddict = CFDictionaryCreate(kCFAllocatorDefault, (
const void**)&kSecGuestAttributePid, (
const void**)&pidnumber, 1,
nullptr,
nullptr);
271 if(!SecCodeCopyGuestWithAttributes(
nullptr, piddict, kSecCSDefaultFlags, &code)) {
272 is_valid = SecCodeCheckValidity(code, kSecCSDefaultFlags, 0);
276 CFRelease(pidnumber);
278 if(is_valid != errSecSuccess) {
279 wlog(
"Application does not have a valid signature; Secure Enclave support disabled");
287 detail::check_signed();
291 size_t model_size =
sizeof(model);
292 if(sysctlbyname(
"hw.model", model, &model_size,
nullptr, 0) == 0) {
293 if(strncmp(model,
"iMacPro", strlen(
"iMacPro")) == 0) {
294 my->populate_existing_keys();
298 if(sscanf(model,
"MacBookPro%u,%u", &
major, &
minor) == 2) {
300 my->populate_existing_keys();
304 if(sscanf(model,
"Macmini%u", &
major) == 1 &&
major >= 8) {
305 my->populate_existing_keys();
308 if(sscanf(model,
"MacBookAir%u", &
major) == 1 &&
major >= 8) {
309 my->populate_existing_keys();
314 SYS_THROW(secure_enclave_exception,
"Secure Enclave not supported on this hardware");
321 FC_THROW_EXCEPTION(chain::wallet_exception,
"Obtaining private key for a key stored in Secure Enclave is impossible");
328 SYS_ASSERT(!
is_locked(), wallet_locked_exception,
"You can not lock an already locked wallet");
334 future<bool> fut = prom.get_future();
335 macos_user_auth(detail::auth_callback, &prom, CFSTR(
"unlock your SYSIO wallet"));
337 FC_THROW_EXCEPTION(chain::wallet_invalid_password_exception,
"Local user authentication failed");
344 FC_THROW_EXCEPTION(chain::wallet_exception,
"Secure Enclave wallet cannot have a password set");
348 FC_THROW_EXCEPTION(chain::wallet_exception,
"Getting the private keys from the Secure Enclave wallet is impossible");
351 flat_set<public_key_type> keys;
352 boost::copy(my->_keys | boost::adaptors::map_keys, std::inserter(keys, keys.end()));
357 FC_THROW_EXCEPTION(chain::wallet_exception,
"It is not possible to import a key in to the Secure Enclave wallet");
361 SYS_ASSERT(
key_type.empty() ||
key_type ==
"R1", chain::unsupported_key_type_exception,
"Secure Enclave wallet only supports R1 keys");
362 return my->create().to_string();
366 SYS_ASSERT(!
is_locked(), wallet_locked_exception,
"You can not remove a key from a locked wallet");
367 return my->remove_key(key);
#define SYS_THROW(exc_type, FORMAT,...)
#define SYS_ASSERT(expr, exc_type, FORMAT,...)
contains only the public point of an elliptic curve key.
void unlock(string password) override
private_key_type get_private_key(public_key_type pubkey) const override
void set_password(string password) override
std::optional< signature_type > try_sign_digest(const digest_type digest, const public_key_type public_key) override
map< public_key_type, private_key_type > list_keys() override
flat_set< public_key_type > list_public_keys() override
bool remove_key(string key) override
void check_password(string password) override
string create_key(string key_type) override
bool is_locked() const override
bool import_key(string wif_key) override
#define FC_THROW_EXCEPTION(EXCEPTION, FORMAT,...)
void macos_user_auth(void(*cb)(int, void *), void *cb_userdata, CFStringRef message)
fc::array< char, 33 > public_key_data
compact_signature signature_from_ecdsa(const EC_KEY *key, const public_key_data &pub, fc::ecdsa_sig &sig, const fc::sha256 &d)
void unpack(Stream &s, std::deque< T > &value)
fc::sha256 digest(const T &value)
static public_key_data get_public_key_data(SecKeyRef key)
map< public_key_type, SecKeyRef > _keys
void populate_existing_keys()
static public_key_type get_public_key(SecKeyRef key)
static string string_for_cferror(CFErrorRef error)
bool remove_key(string public_key)
std::optional< signature_type > try_sign_digest(const digest_type d, const public_key_type public_key)
memcpy((char *) pInfo->slotDescription, s, l)