Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
se_wallet.cpp
Go to the documentation of this file.
4
6
7#include <boost/range/adaptor/map.hpp>
8#include <boost/range/algorithm/copy.hpp>
9
10#include <Security/Security.h>
11
12#include <future>
13
14namespace sysio { namespace wallet {
15
16using namespace fc::crypto::r1;
17
18namespace detail {
19
20static void auth_callback(int success, void* data) {
21 promise<bool>* prom = (promise<bool>*)data;
22 prom->set_value(success);
23}
24
26
27 static public_key_data get_public_key_data(SecKeyRef key) {
28 SecKeyRef pubkey = SecKeyCopyPublicKey(key);
29
30 CFErrorRef error = nullptr;
31 CFDataRef keyrep = nullptr;
32 keyrep = SecKeyCopyExternalRepresentation(pubkey, &error);
33
34 public_key_data pub_key_data;
35 if(!error) {
36 const UInt8* cfdata = CFDataGetBytePtr(keyrep);
37 memcpy(pub_key_data.data+1, cfdata+1, 32);
38 pub_key_data.data[0] = 0x02 + (cfdata[64]&1);
39 }
40
41 CFRelease(keyrep);
42 CFRelease(pubkey);
43
44 if(error) {
45 string error_string = string_for_cferror(error);
46 CFRelease(error);
47 FC_THROW_EXCEPTION(chain::wallet_exception, "Failed to get public key from Secure Enclave: ${m}", ("m", error_string));
48 }
49
50 return pub_key_data;
51 }
52
53 static public_key_type get_public_key(SecKeyRef key) {
54 char serialized_pub_key[sizeof(public_key_data) + 1];
55 serialized_pub_key[0] = 0x01;
56
57 public_key_data pub_key_data = get_public_key_data(key);
58 memcpy(serialized_pub_key+1, pub_key_data.data, sizeof(pub_key_data));
59
60 public_key_type pub_key;
61 fc::datastream<const char *> ds(serialized_pub_key, sizeof(serialized_pub_key));
62 fc::raw::unpack(ds, pub_key);
63
64 return pub_key;
65 }
66
67 static string string_for_cferror(CFErrorRef error) {
68 CFStringRef errorString = CFCopyDescription(error);
69 char buff[CFStringGetLength(errorString) + 1];
70 string ret;
71 if(CFStringGetCString(errorString, buff, sizeof(buff), kCFStringEncodingUTF8))
72 ret = buff;
73 else
74 ret = "Unknown";
75 CFRelease(errorString);
76 return ret;
77 }
78
79#define XSTR(A) STR(A)
80#define STR(A) #A
81
83 const void* keyAttrKeys[] = {
84 kSecClass,
85 kSecAttrKeyClass,
86 kSecMatchLimit,
87 kSecReturnRef,
88 kSecAttrTokenID,
89 kSecAttrAccessGroup
90 };
91 const void* keyAttrValues[] = {
92 kSecClassKey,
93 kSecAttrKeyClassPrivate,
94 kSecMatchLimitAll,
95 kCFBooleanTrue,
96 kSecAttrTokenIDSecureEnclave,
97#ifdef MAS_KEYCHAIN_GROUP
98 CFSTR(XSTR(MAS_KEYCHAIN_GROUP))
99#endif
100 };
101 CFDictionaryRef keyAttrDic = CFDictionaryCreate(nullptr, keyAttrKeys, keyAttrValues, sizeof(keyAttrValues)/sizeof(keyAttrValues[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
102
103 CFArrayRef keyRefs = nullptr;
104 if(SecItemCopyMatching(keyAttrDic, (CFTypeRef*)&keyRefs) || !keyRefs) {
105 CFRelease(keyAttrDic);
106 return;
107 }
108
109 CFIndex count = CFArrayGetCount(keyRefs);
110 for(long i = 0; i < count; ++i) {
112 try {
113 SecKeyRef key = (SecKeyRef)CFRetain(CFArrayGetValueAtIndex(keyRefs, i));
114 _keys[get_public_key(key)] = key;
115 }
116 catch(chain::wallet_exception&) {}
117 }
118 CFRelease(keyRefs);
119 CFRelease(keyAttrDic);
120 }
121
123 SecAccessControlRef accessControlRef = SecAccessControlCreateWithFlags(nullptr, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAccessControlPrivateKeyUsage, nullptr);
124
125 int keySizeValue = 256;
126 CFNumberRef keySizeNumber = CFNumberCreate(NULL, kCFNumberIntType, &keySizeValue);
127
128 const void* keyAttrKeys[] = {
129 kSecAttrIsPermanent,
130 kSecAttrAccessControl,
131 kSecAttrAccessGroup
132 };
133 const void* keyAttrValues[] = {
134 kCFBooleanTrue,
135 accessControlRef,
136#ifdef MAS_KEYCHAIN_GROUP
137 CFSTR(XSTR(MAS_KEYCHAIN_GROUP))
138#endif
139 };
140 CFDictionaryRef keyAttrDic = CFDictionaryCreate(NULL, keyAttrKeys, keyAttrValues, sizeof(keyAttrValues)/sizeof(keyAttrValues[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
141
142 const void* attrKeys[] = {
143 kSecAttrKeyType,
144 kSecAttrKeySizeInBits,
145 kSecAttrTokenID,
146 kSecPrivateKeyAttrs
147 };
148 const void* atrrValues[] = {
149 kSecAttrKeyTypeECSECPrimeRandom,
150 keySizeNumber,
151 kSecAttrTokenIDSecureEnclave,
152 keyAttrDic
153 };
154 CFDictionaryRef attributesDic = CFDictionaryCreate(NULL, attrKeys, atrrValues, sizeof(attrKeys)/sizeof(attrKeys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
155
156 CFErrorRef error = NULL;
157 SecKeyRef privateKey = SecKeyCreateRandomKey(attributesDic, &error);
158 string error_string;
159 if(error) {
160 error_string = string_for_cferror(error);
161 CFRelease(error);
162 }
163
164 CFRelease(attributesDic);
165 CFRelease(keyAttrDic);
166 CFRelease(keySizeNumber);
167 CFRelease(accessControlRef);
168
169 if(error_string.size())
170 FC_THROW_EXCEPTION(chain::wallet_exception, "Failed to create key in Secure Enclave: ${m}", ("m", error_string));
171
173 try {
174 pub = get_public_key(privateKey);
175 }
176 catch(chain::wallet_exception&) {
177 //possibly we should delete the key here?
178 CFRelease(privateKey);
179 throw;
180 }
181 _keys[pub] = privateKey;
182 return pub;
183 }
184
185 std::optional<signature_type> try_sign_digest(const digest_type d, const public_key_type public_key) {
186 auto it = _keys.find(public_key);
187 if(it == _keys.end())
188 return std::optional<signature_type>{};
189
190 fc::ecdsa_sig sig = ECDSA_SIG_new();
191 CFErrorRef error = nullptr;
192
193 CFDataRef digestData = CFDataCreateWithBytesNoCopy(nullptr, (UInt8*)d.data(), d.data_size(), kCFAllocatorNull);
194 CFDataRef signature = SecKeyCreateSignature(it->second, kSecKeyAlgorithmECDSASignatureDigestX962SHA256, digestData, &error);
195 if(error) {
196 string error_string = string_for_cferror(error);
197 CFRelease(error);
198 CFRelease(digestData);
199 FC_THROW_EXCEPTION(chain::wallet_exception, "Failed to sign digest in Secure Enclave: ${m}", ("m", error_string));
200 }
201
202 const UInt8* der_bytes = CFDataGetBytePtr(signature);
203 long derSize = CFDataGetLength(signature);
204 d2i_ECDSA_SIG(&sig.obj, &der_bytes, derSize);
205
207 compact_signature compact_sig;
208 try {
209 kd = get_public_key_data(it->second);
210 compact_sig = signature_from_ecdsa(key, kd, sig, d);
211 } catch(chain::wallet_exception&) {
212 CFRelease(signature);
213 CFRelease(digestData);
214 throw;
215 }
216
217 CFRelease(signature);
218 CFRelease(digestData);
219
220 char serialized_signature[sizeof(compact_sig) + 1];
221 serialized_signature[0] = 0x01;
222 memcpy(serialized_signature+1, compact_sig.data, sizeof(compact_sig));
223
224 signature_type final_signature;
225 fc::datastream<const char *> ds(serialized_signature, sizeof(serialized_signature));
226 fc::raw::unpack(ds, final_signature);
227 return final_signature;
228 }
229
230 bool remove_key(string public_key) {
231 auto it = _keys.find(public_key_type{public_key});
232 if(it == _keys.end())
233 FC_THROW_EXCEPTION(chain::wallet_exception, "Given key to delete not found in Secure Enclave wallet");
234
235 promise<bool> prom;
236 future<bool> fut = prom.get_future();
237 macos_user_auth(auth_callback, &prom, CFSTR("remove a key from your SYSIO wallet"));
238 if(!fut.get())
239 FC_THROW_EXCEPTION(chain::wallet_invalid_password_exception, "Local user authentication failed");
240
241 CFDictionaryRef deleteDic = CFDictionaryCreate(nullptr, (const void**)&kSecValueRef, (const void**)&it->second, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
242
243 OSStatus ret = SecItemDelete(deleteDic);
244 CFRelease(deleteDic);
245
246 if(ret)
247 FC_THROW_EXCEPTION(chain::wallet_exception, "Failed to getremove key from Secure Enclave");
248
249 CFRelease(it->second);
250 _keys.erase(it);
251
252 return true;
253 }
254
256 for(auto& k : _keys)
257 CFRelease(k.second);
258 }
259
260 map<public_key_type,SecKeyRef> _keys;
261 fc::ec_key key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
262 bool locked = true;
263};
264
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);
273 CFRelease(code);
274 }
275 CFRelease(piddict);
276 CFRelease(pidnumber);
277
278 if(is_valid != errSecSuccess) {
279 wlog("Application does not have a valid signature; Secure Enclave support disabled");
280 SYS_THROW(secure_enclave_exception, "");
281 }
282}
283
284}
285
286se_wallet::se_wallet() : my(new detail::se_wallet_impl()) {
287 detail::check_signed();
288
289 //How to figure out of SE is available?!
290 char model[256];
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();
295 return;
296 }
297 unsigned int major, minor;
298 if(sscanf(model, "MacBookPro%u,%u", &major, &minor) == 2) {
299 if((major >= 15) || (major >= 13 && minor >= 2)) {
300 my->populate_existing_keys();
301 return;
302 }
303 }
304 if(sscanf(model, "Macmini%u", &major) == 1 && major >= 8) {
305 my->populate_existing_keys();
306 return;
307 }
308 if(sscanf(model, "MacBookAir%u", &major) == 1 && major >= 8) {
309 my->populate_existing_keys();
310 return;
311 }
312 }
313
314 SYS_THROW(secure_enclave_exception, "Secure Enclave not supported on this hardware");
315}
316
319
321 FC_THROW_EXCEPTION(chain::wallet_exception, "Obtaining private key for a key stored in Secure Enclave is impossible");
322}
323
325 return my->locked;
326}
328 SYS_ASSERT(!is_locked(), wallet_locked_exception, "You can not lock an already locked wallet");
329 my->locked = true;
330}
331
332void se_wallet::unlock(string password) {
333 promise<bool> prom;
334 future<bool> fut = prom.get_future();
335 macos_user_auth(detail::auth_callback, &prom, CFSTR("unlock your SYSIO wallet"));
336 if(!fut.get())
337 FC_THROW_EXCEPTION(chain::wallet_invalid_password_exception, "Local user authentication failed");
338 my->locked = false;
339}
340void se_wallet::check_password(string password) {
341 //just leave this as a noop for now; remove_key from wallet_mgr calls through here
342}
343void se_wallet::set_password(string password) {
344 FC_THROW_EXCEPTION(chain::wallet_exception, "Secure Enclave wallet cannot have a password set");
345}
346
347map<public_key_type, private_key_type> se_wallet::list_keys() {
348 FC_THROW_EXCEPTION(chain::wallet_exception, "Getting the private keys from the Secure Enclave wallet is impossible");
349}
350flat_set<public_key_type> se_wallet::list_public_keys() {
351 flat_set<public_key_type> keys;
352 boost::copy(my->_keys | boost::adaptors::map_keys, std::inserter(keys, keys.end()));
353 return keys;
354}
355
356bool se_wallet::import_key(string wif_key) {
357 FC_THROW_EXCEPTION(chain::wallet_exception, "It is not possible to import a key in to the Secure Enclave wallet");
358}
359
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();
363}
364
365bool se_wallet::remove_key(string key) {
366 SYS_ASSERT(!is_locked(), wallet_locked_exception, "You can not remove a key from a locked wallet");
367 return my->remove_key(key);
368}
369
370std::optional<signature_type> se_wallet::try_sign_digest(const digest_type digest, const public_key_type public_key) {
371 return my->try_sign_digest(digest, public_key);
372}
373
374}}
#define SYS_THROW(exc_type, FORMAT,...)
#define SYS_ASSERT(expr, exc_type, FORMAT,...)
Definition exceptions.hpp:7
T data[N]
Definition array.hpp:37
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,...)
int * count
#define wlog(FORMAT,...)
Definition logger.hpp:124
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)
Definition raw.hpp:540
fc::sha256 digest(const T &value)
Definition digest.hpp:9
#define XSTR(A)
Definition se_wallet.cpp:79
static public_key_data get_public_key_data(SecKeyRef key)
Definition se_wallet.cpp:27
map< public_key_type, SecKeyRef > _keys
static public_key_type get_public_key(SecKeyRef key)
Definition se_wallet.cpp:53
static string string_for_cferror(CFErrorRef error)
Definition se_wallet.cpp:67
bool remove_key(string public_key)
std::optional< signature_type > try_sign_digest(const digest_type d, const public_key_type public_key)
CK_ULONG d
bool pub
CK_RV ret
uint8_t major
CK_BYTE_PTR pubkey
uint8_t minor
memcpy((char *) pInfo->slotDescription, s, l)
struct @108 key_type