]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecKeybagSupport.c
88aeeb0000f9b958a1490340924426da8dcb0d6e
[apple/security.git] / OSX / sec / securityd / SecKeybagSupport.c
1 /*
2 * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 /*
25 * SecKeybagSupport.c - CoreFoundation-based constants and functions for
26 access to Security items (certificates, keys, identities, and
27 passwords.)
28 */
29
30 #include <securityd/SecKeybagSupport.h>
31
32 #include <securityd/SecItemServer.h>
33
34 #if USE_KEYSTORE
35 #include <IOKit/IOKitLib.h>
36 #include <libaks.h>
37 #include <libaks_acl_cf_keys.h>
38 #include <utilities/der_plist.h>
39 #include <corecrypto/ccder.h>
40 #include <ACMLib.h>
41 #if TARGET_OS_EMBEDDED
42 #include <MobileKeyBag/MobileKeyBag.h>
43 #endif
44 #endif /* USE_KEYSTORE */
45
46 #include <CommonCrypto/CommonCryptor.h>
47 #include <CommonCrypto/CommonCryptorSPI.h>
48
49
50 /* g_keychain_handle is the keybag handle used for encrypting item in the keychain.
51 For testing purposes, it can be set to something other than the default, with SecItemServerSetKeychainKeybag */
52 #if USE_KEYSTORE
53 #if TARGET_OS_MAC && !TARGET_OS_EMBEDDED
54 keybag_handle_t g_keychain_keybag = session_keybag_handle;
55 #else
56 keybag_handle_t g_keychain_keybag = device_keybag_handle;
57 #endif
58 #else /* !USE_KEYSTORE */
59 keybag_handle_t g_keychain_keybag = 0; /* 0 == device_keybag_handle, constant dictated by AKS */
60 #endif /* USE_KEYSTORE */
61
62 const CFStringRef kSecKSKeyData1 = CFSTR("d1");
63 const CFStringRef kSecKSKeyData2 = CFSTR("d2");
64
65 void SecItemServerSetKeychainKeybag(int32_t keybag)
66 {
67 g_keychain_keybag=keybag;
68 }
69
70 void SecItemServerResetKeychainKeybag(void)
71 {
72 #if USE_KEYSTORE
73 #if TARGET_OS_MAC && !TARGET_OS_EMBEDDED
74 g_keychain_keybag = session_keybag_handle;
75 #else
76 g_keychain_keybag = device_keybag_handle;
77 #endif
78 #else /* !USE_KEYSTORE */
79 g_keychain_keybag = 0; /* 0 == device_keybag_handle, constant dictated by AKS */
80 #endif /* USE_KEYSTORE */
81 }
82
83 #if USE_KEYSTORE
84
85 static bool hwaes_key_available(void)
86 {
87 keybag_handle_t handle = bad_keybag_handle;
88 keybag_handle_t special_handle = bad_keybag_handle;
89 #if TARGET_OS_MAC && !TARGET_OS_EMBEDDED
90 special_handle = session_keybag_handle;
91 #elif TARGET_OS_EMBEDDED
92 special_handle = device_keybag_handle;
93 #endif
94 kern_return_t kr = aks_get_system(special_handle, &handle);
95 if (kr != kIOReturnSuccess) {
96 #if TARGET_OS_EMBEDDED
97 /* TODO: Remove this once the kext runs the daemon on demand if
98 there is no system keybag. */
99 int kb_state = MKBGetDeviceLockState(NULL);
100 asl_log(NULL, NULL, ASL_LEVEL_INFO, "AppleKeyStore lock state: %d", kb_state);
101 #endif
102 }
103 return true;
104 }
105
106 #else /* !USE_KEYSTORE */
107
108 static bool hwaes_key_available(void)
109 {
110 return false;
111 }
112
113 #endif /* USE_KEYSTORE */
114
115 /* Wrap takes a 128 - 256 bit key as input and returns output of
116 inputsize + 64 bits.
117 In bytes this means that a
118 16 byte (128 bit) key returns a 24 byte wrapped key
119 24 byte (192 bit) key returns a 32 byte wrapped key
120 32 byte (256 bit) key returns a 40 byte wrapped key */
121 bool ks_crypt(CFTypeRef operation, keybag_handle_t keybag,
122 keyclass_t keyclass, uint32_t textLength, const uint8_t *source, keyclass_t *actual_class, CFMutableDataRef dest, CFErrorRef *error) {
123 #if USE_KEYSTORE
124 kern_return_t kernResult = kIOReturnBadArgument;
125
126 int dest_len = (int)CFDataGetLength(dest);
127 if (CFEqual(operation, kAKSKeyOpEncrypt)) {
128 kernResult = aks_wrap_key(source, textLength, keyclass, keybag, CFDataGetMutableBytePtr(dest), &dest_len, actual_class);
129 } else if (CFEqual(operation, kAKSKeyOpDecrypt) || CFEqual(operation, kAKSKeyOpDelete)) {
130 kernResult = aks_unwrap_key(source, textLength, keyclass, keybag, CFDataGetMutableBytePtr(dest), &dest_len);
131 }
132
133 if (kernResult != KERN_SUCCESS) {
134 if ((kernResult == kIOReturnNotPermitted) || (kernResult == kIOReturnNotPrivileged)) {
135 const char *substatus = "";
136 if (keyclass == key_class_ck || keyclass == key_class_cku)
137 substatus = " (hiberation ?)";
138 /* Access to item attempted while keychain is locked. */
139 return SecError(errSecInteractionNotAllowed, error, CFSTR("ks_crypt: %x failed to '%@' item (class %"PRId32", bag: %"PRId32") Access to item attempted while keychain is locked%s."),
140 kernResult, operation, keyclass, keybag, substatus);
141 } else if (kernResult == kIOReturnError) {
142 /* Item can't be decrypted on this device, ever, so drop the item. */
143 return SecError(errSecDecode, error, CFSTR("ks_crypt: %x failed to '%@' item (class %"PRId32", bag: %"PRId32") Item can't be decrypted on this device, ever, so drop the item."),
144 kernResult, operation, keyclass, keybag);
145 } else {
146 return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: %x failed to '%@' item (class %"PRId32", bag: %"PRId32")"),
147 kernResult, operation, keyclass, keybag);
148 }
149 }
150 else
151 CFDataSetLength(dest, dest_len);
152 return true;
153 #else /* !USE_KEYSTORE */
154 uint32_t dest_len = (uint32_t)CFDataGetLength(dest);
155 if (CFEqual(operation, kAKSKeyOpEncrypt)) {
156 /* The no encryption case. */
157 if (dest_len >= textLength + 8) {
158 memcpy(CFDataGetMutableBytePtr(dest), source, textLength);
159 memset(CFDataGetMutableBytePtr(dest) + textLength, 8, 8);
160 CFDataSetLength(dest, textLength + 8);
161 *actual_class = keyclass;
162 } else
163 return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: failed to wrap item (class %"PRId32")"), keyclass);
164 } else if (CFEqual(operation, kAKSKeyOpDecrypt) || CFEqual(operation, kAKSKeyOpDelete)) {
165 if (dest_len + 8 >= textLength) {
166 memcpy(CFDataGetMutableBytePtr(dest), source, textLength - 8);
167 CFDataSetLength(dest, textLength - 8);
168 } else
169 return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: failed to unwrap item (class %"PRId32")"), keyclass);
170 }
171 return true;
172 #endif /* USE_KEYSTORE */
173 }
174
175 #if USE_KEYSTORE
176 static bool ks_access_control_needed_error(CFErrorRef *error, CFDataRef access_control_data, CFTypeRef operation) {
177 if (error == NULL)
178 return false;
179
180 if (*error && CFErrorGetCode(*error) != errSecAuthNeeded) {
181 // If we already had an error there, just leave it, no access_control specific error is needed here.
182 return false;
183 }
184
185 // Create new error instance which adds new access control data appended to existing
186 CFMutableDictionaryRef user_info;
187 if (*error) {
188 CFDictionaryRef old_user_info = CFErrorCopyUserInfo(*error);
189 user_info = CFDictionaryCreateMutableCopy(NULL, 0, old_user_info);
190 CFRelease(old_user_info);
191 CFReleaseNull(*error);
192 } else {
193 user_info = CFDictionaryCreateMutableForCFTypes(NULL);
194 }
195
196 if (access_control_data) {
197 CFNumberRef key = CFNumberCreateWithCFIndex(NULL, errSecAuthNeeded);
198 CFMutableArrayRef acls;
199 CFArrayRef old_acls = CFDictionaryGetValue(user_info, key);
200 if (old_acls)
201 acls = CFArrayCreateMutableCopy(NULL, 0, old_acls);
202 else
203 acls = CFArrayCreateMutableForCFTypes(NULL);
204
205 CFArrayRef pair = CFArrayCreateForCFTypes(NULL, access_control_data, operation, NULL);
206 CFArrayAppendValue(acls, pair);
207 CFRelease(pair);
208
209 CFDictionarySetValue(user_info, key, acls);
210 CFRelease(key);
211 CFRelease(acls);
212
213 *error = CFErrorCreate(NULL, kSecErrorDomain, errSecAuthNeeded, user_info);
214 }
215 else
216 *error = CFErrorCreate(NULL, kSecErrorDomain, errSecAuthNeeded, NULL);
217
218 CFReleaseSafe(user_info);
219 return false;
220 }
221
222 static bool merge_der_in_to_data(const void *ed_blob, size_t ed_blob_len, const void *key_blob, size_t key_blob_len, CFMutableDataRef mergedData)
223 {
224 bool ok = false;
225 CFDataRef ed_data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, ed_blob, ed_blob_len, kCFAllocatorNull);
226 CFDataRef key_data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, key_blob, key_blob_len, kCFAllocatorNull);
227
228 if (ed_data && key_data) {
229 CFDictionaryRef result_dict = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, kSecKSKeyData1, ed_data, kSecKSKeyData2, key_data, NULL);
230
231 CFDataSetLength(mergedData, 0);
232 CFDataRef der_data = CFPropertyListCreateDERData(kCFAllocatorDefault, result_dict, NULL);
233 if (der_data) {
234 CFDataAppend(mergedData, der_data);
235 CFRelease(der_data);
236 ok = CFDataGetLength(mergedData) > 0;
237 }
238 CFRelease(result_dict);
239 }
240
241 CFReleaseSafe(ed_data);
242 CFReleaseSafe(key_data);
243 return ok;
244 }
245
246 bool ks_separate_data_and_key(CFDictionaryRef blob_dict, CFDataRef *ed_data, CFDataRef *key_data)
247 {
248 bool ok = false;
249 CFDataRef tmp_ed_data = CFDictionaryGetValue(blob_dict, kSecKSKeyData1);
250 CFDataRef tmp_key_data = CFDictionaryGetValue(blob_dict, kSecKSKeyData2);
251
252 if (tmp_ed_data && tmp_key_data &&
253 CFDataGetTypeID() == CFGetTypeID(tmp_ed_data) &&
254 CFDataGetTypeID() == CFGetTypeID(tmp_key_data)) {
255 *ed_data = CFRetain(tmp_ed_data);
256 *key_data = CFRetain(tmp_key_data);
257 ok = true;
258 }
259
260 return ok;
261 }
262
263 static bool create_cferror_from_aks(int aks_return, CFTypeRef operation, keybag_handle_t keybag, keyclass_t keyclass, CFDataRef access_control_data, CFDataRef acm_context_data, CFErrorRef *error)
264 {
265 const char *operation_string = "";
266 if (CFEqual(operation, kAKSKeyOpDecrypt)) {
267 operation_string = "decrypt";
268 } else if (CFEqual(operation, kAKSKeyOpEncrypt)) {
269 operation_string = "encrypt";
270 } if (CFEqual(operation, kAKSKeyOpDelete)) {
271 operation_string = "delete";
272 }
273
274 if (aks_return == kAKSReturnNoPermission) {
275 /* Keychain is locked. */
276 SecError(errSecInteractionNotAllowed, error, CFSTR("aks_ref_key: %x failed to '%s' item (class %"PRId32", bag: %"PRId32") Access to item attempted while keychain is locked."),
277 aks_return, operation_string, keyclass, keybag);
278 } else if (aks_return == kAKSReturnPolicyError || aks_return == kAKSReturnBadPassword) {
279 if (aks_return == kAKSReturnBadPassword) {
280 ACMContextRef acm_context_ref = ACMContextCreateWithExternalForm(CFDataGetBytePtr(acm_context_data), CFDataGetLength(acm_context_data));
281 if (acm_context_ref) {
282 ACMContextRemovePassphraseCredentialsByPurposeAndScope(acm_context_ref, kACMPassphrasePurposeGeneral, kACMScopeContext);
283 ACMContextDelete(acm_context_ref, false);
284 }
285 }
286
287 /* Item needed authentication. */
288 ks_access_control_needed_error(error, access_control_data, operation);
289 } else if (aks_return == kAKSReturnError || aks_return == kAKSReturnPolicyInvalid) {
290 /* Item can't be decrypted on this device, ever, so drop the item. */
291 SecError(errSecDecode, error, CFSTR("aks_ref_key: %x failed to '%s' item (class %"PRId32", bag: %"PRId32") Item can't be decrypted on this device, ever, so drop the item."),
292 aks_return, operation_string, keyclass, keybag);
293 } else {
294 SecError(errSecNotAvailable, error, CFSTR("aks_ref_key: %x failed to '%s' item (class %"PRId32", bag: %"PRId32")"),
295 aks_return, operation_string, keyclass, keybag);
296 }
297
298 return false;
299 }
300
301 bool ks_encrypt_acl(keybag_handle_t keybag, keyclass_t keyclass, uint32_t textLength, const uint8_t *source,
302 CFMutableDataRef dest, CFDataRef auth_data, CFDataRef acm_context,
303 SecAccessControlRef access_control, CFErrorRef *error) {
304 void *params = NULL, *der = NULL;
305 size_t params_len = 0, der_len = 0;
306 CFDataRef access_control_data = SecAccessControlCopyData(access_control);
307 int aks_return = kAKSReturnSuccess;
308 aks_ref_key_t key_handle = NULL;
309
310 /* Verify that we have credential handle, otherwise generate proper error containing ACL and operation requested. */
311 bool ok = false;
312 if (!acm_context || !SecAccessControlIsBound(access_control)) {
313 require_quiet(ok = ks_access_control_needed_error(error, access_control_data, SecAccessControlIsBound(access_control) ? kAKSKeyOpEncrypt : CFSTR("")), out);
314 }
315
316 aks_operation_optional_params(0, 0, CFDataGetBytePtr(auth_data), CFDataGetLength(auth_data), CFDataGetBytePtr(acm_context), (int)CFDataGetLength(acm_context), &params, &params_len);
317 require_noerr_action_quiet(aks_return = aks_ref_key_create(keybag, keyclass, key_type_sym, params, params_len, &key_handle), out,
318 create_cferror_from_aks(aks_return, kAKSKeyOpEncrypt, keybag, keyclass, access_control_data, acm_context, error));
319 require_noerr_action_quiet(aks_return = aks_ref_key_encrypt(key_handle, params, params_len, source, textLength, &der, &der_len), out,
320 create_cferror_from_aks(aks_return, kAKSKeyOpEncrypt, keybag, keyclass, access_control_data, acm_context, error));
321 size_t key_blob_len;
322 const void *key_blob = aks_ref_key_get_blob(key_handle, &key_blob_len);
323 require_action_quiet(key_blob, out, SecError(errSecDecode, error, CFSTR("ks_crypt_acl: %x failed to '%s' item (class %"PRId32", bag: %"PRId32") Item can't be encrypted due to invalid key data, so drop the item."),
324 aks_return, "encrypt", keyclass, keybag));
325
326 require_action_quiet(merge_der_in_to_data(der, der_len, key_blob, key_blob_len, dest), out,
327 SecError(errSecDecode, error, CFSTR("ks_crypt_acl: %x failed to '%s' item (class %"PRId32", bag: %"PRId32") Item can't be encrypted due to merge failed, so drop the item."),
328 aks_return, "encrypt", keyclass, keybag));
329
330 ok = true;
331
332 out:
333 if (key_handle)
334 aks_ref_key_free(&key_handle);
335 if(params)
336 free(params);
337 if(der)
338 free(der);
339 CFReleaseSafe(access_control_data);
340 return ok;
341 }
342
343 bool ks_decrypt_acl(aks_ref_key_t ref_key, CFDataRef encrypted_data, CFMutableDataRef dest,
344 CFDataRef acm_context, CFDataRef caller_access_groups,
345 SecAccessControlRef access_control, CFErrorRef *error) {
346 void *params = NULL, *der = NULL;
347 const uint8_t *access_groups = caller_access_groups?CFDataGetBytePtr(caller_access_groups):NULL;
348 size_t params_len = 0, der_len = 0, access_groups_len = caller_access_groups?CFDataGetLength(caller_access_groups):0;
349 CFDataRef access_control_data = SecAccessControlCopyData(access_control);
350 int aks_return = kAKSReturnSuccess;
351
352 /* Verify that we have credential handle, otherwise generate proper error containing ACL and operation requested. */
353 bool ok = false;
354 if (!acm_context) {
355 require_quiet(ok = ks_access_control_needed_error(error, NULL, NULL), out);
356 }
357
358 aks_operation_optional_params(access_groups, access_groups_len, 0, 0, CFDataGetBytePtr(acm_context), (int)CFDataGetLength(acm_context), &params, &params_len);
359 require_noerr_action_quiet(aks_return = aks_ref_key_decrypt(ref_key, params, params_len, CFDataGetBytePtr(encrypted_data), CFDataGetLength(encrypted_data), &der, &der_len), out,
360 create_cferror_from_aks(aks_return, kAKSKeyOpDecrypt, 0, 0, access_control_data, acm_context, error));
361 require_action_quiet(der, out, SecError(errSecDecode, error, CFSTR("ks_crypt_acl: %x failed to '%s' item, Item can't be decrypted due to invalid der data, so drop the item."),
362 aks_return, "decrypt"));
363
364 CFPropertyListRef decoded_data = NULL;
365 der_decode_plist(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_data, NULL, der, der + der_len);
366 require_action_quiet(decoded_data, out, SecError(errSecDecode, error, CFSTR("ks_crypt_acl: %x failed to '%s' item, Item can't be decrypted due to failed decode der, so drop the item."),
367 aks_return, "decrypt"));
368 if (CFGetTypeID(decoded_data) == CFDataGetTypeID()) {
369 CFDataSetLength(dest, 0);
370 CFDataAppend(dest, decoded_data);
371 CFRelease(decoded_data);
372 }
373 else {
374 CFRelease(decoded_data);
375 require_action_quiet(false, out, SecError(errSecDecode, error, CFSTR("ks_crypt_acl: %x failed to '%s' item, Item can't be decrypted due to wrong data, so drop the item."),
376 aks_return, "decrypt"));
377 }
378
379 ok = true;
380
381 out:
382 if(params)
383 free(params);
384 if(der)
385 free(der);
386 CFReleaseSafe(access_control_data);
387 return ok;
388 }
389
390 bool ks_delete_acl(aks_ref_key_t ref_key, CFDataRef encrypted_data,
391 CFDataRef acm_context, CFDataRef caller_access_groups,
392 SecAccessControlRef access_control, CFErrorRef *error) {
393 void *params = NULL;
394 CFDataRef access_control_data = NULL;
395 int aks_return = kAKSReturnSuccess;
396 bool ok = false;
397
398 nrequire_action_quiet(CFEqual(SecAccessControlGetConstraint(access_control, kAKSKeyOpDelete), kCFBooleanTrue), out, ok = true);
399
400 /* Verify that we have credential handle, otherwise generate proper error containing ACL and operation requested. */
401 if (!acm_context) {
402 require_quiet(ok = ks_access_control_needed_error(error, NULL, NULL), out);
403 }
404
405 access_control_data = SecAccessControlCopyData(access_control);
406 const uint8_t *access_groups = caller_access_groups?CFDataGetBytePtr(caller_access_groups):NULL;
407 size_t params_len = 0, access_groups_len = caller_access_groups?CFDataGetLength(caller_access_groups):0;
408 aks_operation_optional_params(access_groups, access_groups_len, 0, 0, CFDataGetBytePtr(acm_context), (int)CFDataGetLength(acm_context), &params, &params_len);
409 require_noerr_action_quiet(aks_return = aks_ref_key_delete(ref_key, params, params_len), out,
410 create_cferror_from_aks(aks_return, kAKSKeyOpDelete, 0, 0, access_control_data, acm_context, error));
411
412 ok = true;
413
414 out:
415 if(params)
416 free(params);
417 CFReleaseSafe(access_control_data);
418 return ok;
419 }
420
421 const void* ks_ref_key_get_external_data(keybag_handle_t keybag, CFDataRef key_data, aks_ref_key_t *ref_key, size_t *external_data_len, CFErrorRef *error) {
422 const void* result = NULL;
423 int aks_return = kAKSReturnSuccess;
424 require_noerr_action_quiet(aks_ref_key_create_with_blob(keybag, CFDataGetBytePtr(key_data), CFDataGetLength(key_data), ref_key), out,
425 SecError(errSecNotAvailable, error, CFSTR("aks_ref_key: %x failed to '%s' item (bag: %"PRId32")"), aks_return, "create ref key with blob", keybag));
426 result = aks_ref_key_get_external_data(*ref_key, external_data_len);
427
428 out:
429 return result;
430 }
431 #endif
432
433 bool use_hwaes(void) {
434 static bool use_hwaes;
435 static dispatch_once_t check_once;
436 dispatch_once(&check_once, ^{
437 use_hwaes = hwaes_key_available();
438 if (use_hwaes) {
439 asl_log(NULL, NULL, ASL_LEVEL_INFO, "using hwaes key");
440 } else {
441 asl_log(NULL, NULL, ASL_LEVEL_ERR, "unable to access hwaes key");
442 }
443 });
444 return use_hwaes;
445 }
446
447 bool ks_open_keybag(CFDataRef keybag, CFDataRef password, keybag_handle_t *handle, CFErrorRef *error) {
448 #if USE_KEYSTORE
449 kern_return_t kernResult;
450 if (!asData(keybag, error)) return false;
451 kernResult = aks_load_bag(CFDataGetBytePtr(keybag), (int)CFDataGetLength(keybag), handle);
452 if (kernResult)
453 return SecKernError(kernResult, error, CFSTR("aks_load_bag failed: %@"), keybag);
454
455 if (password) {
456 kernResult = aks_unlock_bag(*handle, CFDataGetBytePtr(password), (int)CFDataGetLength(password));
457 if (kernResult) {
458 aks_unload_bag(*handle);
459 return SecKernError(kernResult, error, CFSTR("aks_unlock_bag failed"));
460 }
461 }
462 return true;
463 #else /* !USE_KEYSTORE */
464 *handle = KEYBAG_NONE;
465 return true;
466 #endif /* USE_KEYSTORE */
467 }
468
469 bool ks_close_keybag(keybag_handle_t keybag, CFErrorRef *error) {
470 #if USE_KEYSTORE
471 IOReturn kernResult = aks_unload_bag(keybag);
472 if (kernResult) {
473 return SecKernError(kernResult, error, CFSTR("aks_unload_bag failed"));
474 }
475 #endif /* USE_KEYSTORE */
476 return true;
477 }
478