]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecKeybagSupport.c
Security-57740.60.18.tar.gz
[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_OSX
90 special_handle = session_keybag_handle;
91 #elif TARGET_OS_EMBEDDED
92 special_handle = device_keybag_handle;
93 #else
94 #error "supported keybag target"
95 #endif
96
97 kern_return_t kr = aks_get_system(special_handle, &handle);
98 if (kr != kIOReturnSuccess) {
99 #if TARGET_OS_EMBEDDED
100 /* TODO: Remove this once the kext runs the daemon on demand if
101 there is no system keybag. */
102 int kb_state = MKBGetDeviceLockState(NULL);
103 asl_log(NULL, NULL, ASL_LEVEL_INFO, "AppleKeyStore lock state: %d", kb_state);
104 #endif
105 }
106 return true;
107 }
108
109 #else /* !USE_KEYSTORE */
110
111 static bool hwaes_key_available(void)
112 {
113 return false;
114 }
115
116 #endif /* USE_KEYSTORE */
117
118 /* Wrap takes a 128 - 256 bit key as input and returns output of
119 inputsize + 64 bits.
120 In bytes this means that a
121 16 byte (128 bit) key returns a 24 byte wrapped key
122 24 byte (192 bit) key returns a 32 byte wrapped key
123 32 byte (256 bit) key returns a 40 byte wrapped key */
124 bool ks_crypt(CFTypeRef operation, keybag_handle_t keybag,
125 keyclass_t keyclass, uint32_t textLength, const uint8_t *source, keyclass_t *actual_class, CFMutableDataRef dest, CFErrorRef *error) {
126 #if USE_KEYSTORE
127 kern_return_t kernResult = kIOReturnBadArgument;
128
129 int dest_len = (int)CFDataGetLength(dest);
130 if (CFEqual(operation, kAKSKeyOpEncrypt)) {
131 kernResult = aks_wrap_key(source, textLength, keyclass, keybag, CFDataGetMutableBytePtr(dest), &dest_len, actual_class);
132 } else if (CFEqual(operation, kAKSKeyOpDecrypt) || CFEqual(operation, kAKSKeyOpDelete)) {
133 kernResult = aks_unwrap_key(source, textLength, keyclass, keybag, CFDataGetMutableBytePtr(dest), &dest_len);
134 }
135
136 if (kernResult != KERN_SUCCESS) {
137 if ((kernResult == kIOReturnNotPermitted) || (kernResult == kIOReturnNotPrivileged)) {
138 const char *substatus = "";
139 if (keyclass == key_class_ck || keyclass == key_class_cku)
140 substatus = " (hiberation ?)";
141 /* Access to item attempted while keychain is locked. */
142 return SecError(errSecInteractionNotAllowed, error, CFSTR("ks_crypt: %x failed to '%@' item (class %"PRId32", bag: %"PRId32") Access to item attempted while keychain is locked%s."),
143 kernResult, operation, keyclass, keybag, substatus);
144 } else if (kernResult == kIOReturnError) {
145 /* Item can't be decrypted on this device, ever, so drop the item. */
146 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."),
147 kernResult, operation, keyclass, keybag);
148 } else {
149 return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: %x failed to '%@' item (class %"PRId32", bag: %"PRId32")"),
150 kernResult, operation, keyclass, keybag);
151 }
152 }
153 else
154 CFDataSetLength(dest, dest_len);
155 return true;
156 #else /* !USE_KEYSTORE */
157 uint32_t dest_len = (uint32_t)CFDataGetLength(dest);
158 if (CFEqual(operation, kAKSKeyOpEncrypt)) {
159 /* The no encryption case. */
160 if (dest_len >= textLength + 8) {
161 memcpy(CFDataGetMutableBytePtr(dest), source, textLength);
162 memset(CFDataGetMutableBytePtr(dest) + textLength, 8, 8);
163 CFDataSetLength(dest, textLength + 8);
164 *actual_class = keyclass;
165 } else
166 return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: failed to wrap item (class %"PRId32")"), keyclass);
167 } else if (CFEqual(operation, kAKSKeyOpDecrypt) || CFEqual(operation, kAKSKeyOpDelete)) {
168 if (dest_len + 8 >= textLength) {
169 memcpy(CFDataGetMutableBytePtr(dest), source, textLength - 8);
170 CFDataSetLength(dest, textLength - 8);
171 } else
172 return SecError(errSecNotAvailable, error, CFSTR("ks_crypt: failed to unwrap item (class %"PRId32")"), keyclass);
173 }
174 return true;
175 #endif /* USE_KEYSTORE */
176 }
177
178 #if USE_KEYSTORE
179 static bool ks_access_control_needed_error(CFErrorRef *error, CFDataRef access_control_data, CFTypeRef operation) {
180 if (error == NULL)
181 return false;
182
183 if (*error && CFErrorGetCode(*error) != errSecAuthNeeded) {
184 // If we already had an error there, just leave it, no access_control specific error is needed here.
185 return false;
186 }
187
188 // Create new error instance which adds new access control data appended to existing
189 CFMutableDictionaryRef user_info;
190 if (*error) {
191 CFDictionaryRef old_user_info = CFErrorCopyUserInfo(*error);
192 user_info = CFDictionaryCreateMutableCopy(NULL, 0, old_user_info);
193 CFRelease(old_user_info);
194 CFReleaseNull(*error);
195 } else {
196 user_info = CFDictionaryCreateMutableForCFTypes(NULL);
197 }
198
199 if (access_control_data) {
200 CFNumberRef key = CFNumberCreateWithCFIndex(NULL, errSecAuthNeeded);
201 CFMutableArrayRef acls;
202 CFArrayRef old_acls = CFDictionaryGetValue(user_info, key);
203 if (old_acls)
204 acls = CFArrayCreateMutableCopy(NULL, 0, old_acls);
205 else
206 acls = CFArrayCreateMutableForCFTypes(NULL);
207
208 CFArrayRef pair = CFArrayCreateForCFTypes(NULL, access_control_data, operation, NULL);
209 CFArrayAppendValue(acls, pair);
210 CFRelease(pair);
211
212 CFDictionarySetValue(user_info, key, acls);
213 CFRelease(key);
214 CFRelease(acls);
215
216 *error = CFErrorCreate(NULL, kSecErrorDomain, errSecAuthNeeded, user_info);
217 }
218 else
219 *error = CFErrorCreate(NULL, kSecErrorDomain, errSecAuthNeeded, NULL);
220
221 CFReleaseSafe(user_info);
222 return false;
223 }
224
225 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)
226 {
227 bool ok = false;
228 CFDataRef ed_data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, ed_blob, ed_blob_len, kCFAllocatorNull);
229 CFDataRef key_data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, key_blob, key_blob_len, kCFAllocatorNull);
230
231 if (ed_data && key_data) {
232 CFDictionaryRef result_dict = CFDictionaryCreateForCFTypes(kCFAllocatorDefault, kSecKSKeyData1, ed_data, kSecKSKeyData2, key_data, NULL);
233
234 CFDataSetLength(mergedData, 0);
235 CFDataRef der_data = CFPropertyListCreateDERData(kCFAllocatorDefault, result_dict, NULL);
236 if (der_data) {
237 CFDataAppend(mergedData, der_data);
238 CFRelease(der_data);
239 ok = CFDataGetLength(mergedData) > 0;
240 }
241 CFRelease(result_dict);
242 }
243
244 CFReleaseSafe(ed_data);
245 CFReleaseSafe(key_data);
246 return ok;
247 }
248
249 bool ks_separate_data_and_key(CFDictionaryRef blob_dict, CFDataRef *ed_data, CFDataRef *key_data)
250 {
251 bool ok = false;
252 CFDataRef tmp_ed_data = CFDictionaryGetValue(blob_dict, kSecKSKeyData1);
253 CFDataRef tmp_key_data = CFDictionaryGetValue(blob_dict, kSecKSKeyData2);
254
255 if (tmp_ed_data && tmp_key_data &&
256 CFDataGetTypeID() == CFGetTypeID(tmp_ed_data) &&
257 CFDataGetTypeID() == CFGetTypeID(tmp_key_data)) {
258 *ed_data = CFRetain(tmp_ed_data);
259 *key_data = CFRetain(tmp_key_data);
260 ok = true;
261 }
262
263 return ok;
264 }
265
266 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)
267 {
268 const char *operation_string = "";
269 if (CFEqual(operation, kAKSKeyOpDecrypt)) {
270 operation_string = "decrypt";
271 } else if (CFEqual(operation, kAKSKeyOpEncrypt)) {
272 operation_string = "encrypt";
273 } if (CFEqual(operation, kAKSKeyOpDelete)) {
274 operation_string = "delete";
275 }
276
277 if (aks_return == kAKSReturnNoPermission) {
278 /* Keychain is locked. */
279 SecError(errSecInteractionNotAllowed, error, CFSTR("aks_ref_key: %x failed to '%s' item (class %"PRId32", bag: %"PRId32") Access to item attempted while keychain is locked."),
280 aks_return, operation_string, keyclass, keybag);
281 } else if (aks_return == kAKSReturnPolicyError || aks_return == kAKSReturnBadPassword) {
282 if (aks_return == kAKSReturnBadPassword) {
283 ACMContextRef acm_context_ref = ACMContextCreateWithExternalForm(CFDataGetBytePtr(acm_context_data), CFDataGetLength(acm_context_data));
284 if (acm_context_ref) {
285 ACMContextRemovePassphraseCredentialsByPurposeAndScope(acm_context_ref, kACMPassphrasePurposeGeneral, kACMScopeContext);
286 ACMContextDelete(acm_context_ref, false);
287 }
288 }
289
290 /* Item needed authentication. */
291 ks_access_control_needed_error(error, access_control_data, operation);
292 } else if (aks_return == kAKSReturnError || aks_return == kAKSReturnPolicyInvalid) {
293 /* Item can't be decrypted on this device, ever, so drop the item. */
294 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."),
295 aks_return, operation_string, keyclass, keybag);
296 } else {
297 SecError(errSecNotAvailable, error, CFSTR("aks_ref_key: %x failed to '%s' item (class %"PRId32", bag: %"PRId32")"),
298 aks_return, operation_string, keyclass, keybag);
299 }
300
301 return false;
302 }
303
304 bool ks_encrypt_acl(keybag_handle_t keybag, keyclass_t keyclass, uint32_t textLength, const uint8_t *source,
305 CFMutableDataRef dest, CFDataRef auth_data, CFDataRef acm_context,
306 SecAccessControlRef access_control, CFErrorRef *error) {
307 void *params = NULL, *der = NULL;
308 size_t params_len = 0, der_len = 0;
309 CFDataRef access_control_data = SecAccessControlCopyData(access_control);
310 int aks_return = kAKSReturnSuccess;
311 aks_ref_key_t key_handle = NULL;
312
313 /* Verify that we have credential handle, otherwise generate proper error containing ACL and operation requested. */
314 bool ok = false;
315 if (!acm_context || !SecAccessControlIsBound(access_control)) {
316 require_quiet(ok = ks_access_control_needed_error(error, access_control_data, SecAccessControlIsBound(access_control) ? kAKSKeyOpEncrypt : CFSTR("")), out);
317 }
318
319 aks_operation_optional_params(0, 0, CFDataGetBytePtr(auth_data), CFDataGetLength(auth_data), CFDataGetBytePtr(acm_context), (int)CFDataGetLength(acm_context), &params, &params_len);
320 require_noerr_action_quiet(aks_return = aks_ref_key_create(keybag, keyclass, key_type_sym, params, params_len, &key_handle), out,
321 create_cferror_from_aks(aks_return, kAKSKeyOpEncrypt, keybag, keyclass, access_control_data, acm_context, error));
322 require_noerr_action_quiet(aks_return = aks_ref_key_encrypt(key_handle, params, params_len, source, textLength, &der, &der_len), out,
323 create_cferror_from_aks(aks_return, kAKSKeyOpEncrypt, keybag, keyclass, access_control_data, acm_context, error));
324 size_t key_blob_len;
325 const void *key_blob = aks_ref_key_get_blob(key_handle, &key_blob_len);
326 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."),
327 aks_return, "encrypt", keyclass, keybag));
328
329 require_action_quiet(merge_der_in_to_data(der, der_len, key_blob, key_blob_len, dest), out,
330 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."),
331 aks_return, "encrypt", keyclass, keybag));
332
333 ok = true;
334
335 out:
336 if (key_handle)
337 aks_ref_key_free(&key_handle);
338 if(params)
339 free(params);
340 if(der)
341 free(der);
342 CFReleaseSafe(access_control_data);
343 return ok;
344 }
345
346 bool ks_decrypt_acl(aks_ref_key_t ref_key, CFDataRef encrypted_data, CFMutableDataRef dest,
347 CFDataRef acm_context, CFDataRef caller_access_groups,
348 SecAccessControlRef access_control, CFErrorRef *error) {
349 void *params = NULL, *der = NULL;
350 const uint8_t *access_groups = caller_access_groups?CFDataGetBytePtr(caller_access_groups):NULL;
351 size_t params_len = 0, der_len = 0, access_groups_len = caller_access_groups?CFDataGetLength(caller_access_groups):0;
352 CFDataRef access_control_data = SecAccessControlCopyData(access_control);
353 int aks_return = kAKSReturnSuccess;
354
355 /* Verify that we have credential handle, otherwise generate proper error containing ACL and operation requested. */
356 bool ok = false;
357 if (!acm_context) {
358 require_quiet(ok = ks_access_control_needed_error(error, NULL, NULL), out);
359 }
360
361 aks_operation_optional_params(access_groups, access_groups_len, 0, 0, CFDataGetBytePtr(acm_context), (int)CFDataGetLength(acm_context), &params, &params_len);
362 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,
363 create_cferror_from_aks(aks_return, kAKSKeyOpDecrypt, 0, 0, access_control_data, acm_context, error));
364 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."),
365 aks_return, "decrypt"));
366
367 CFPropertyListRef decoded_data = NULL;
368 der_decode_plist(kCFAllocatorDefault, kCFPropertyListImmutable, &decoded_data, NULL, der, der + der_len);
369 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."),
370 aks_return, "decrypt"));
371 if (CFGetTypeID(decoded_data) == CFDataGetTypeID()) {
372 CFDataSetLength(dest, 0);
373 CFDataAppend(dest, decoded_data);
374 CFRelease(decoded_data);
375 }
376 else {
377 CFRelease(decoded_data);
378 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."),
379 aks_return, "decrypt"));
380 }
381
382 ok = true;
383
384 out:
385 if(params)
386 free(params);
387 if(der)
388 free(der);
389 CFReleaseSafe(access_control_data);
390 return ok;
391 }
392
393 bool ks_delete_acl(aks_ref_key_t ref_key, CFDataRef encrypted_data,
394 CFDataRef acm_context, CFDataRef caller_access_groups,
395 SecAccessControlRef access_control, CFErrorRef *error) {
396 void *params = NULL;
397 CFDataRef access_control_data = NULL;
398 int aks_return = kAKSReturnSuccess;
399 bool ok = false;
400
401 nrequire_action_quiet(CFEqual(SecAccessControlGetConstraint(access_control, kAKSKeyOpDelete), kCFBooleanTrue), out, ok = true);
402
403 /* Verify that we have credential handle, otherwise generate proper error containing ACL and operation requested. */
404 if (!acm_context) {
405 require_quiet(ok = ks_access_control_needed_error(error, NULL, NULL), out);
406 }
407
408 access_control_data = SecAccessControlCopyData(access_control);
409 const uint8_t *access_groups = caller_access_groups?CFDataGetBytePtr(caller_access_groups):NULL;
410 size_t params_len = 0, access_groups_len = caller_access_groups?CFDataGetLength(caller_access_groups):0;
411 aks_operation_optional_params(access_groups, access_groups_len, 0, 0, CFDataGetBytePtr(acm_context), (int)CFDataGetLength(acm_context), &params, &params_len);
412 require_noerr_action_quiet(aks_return = aks_ref_key_delete(ref_key, params, params_len), out,
413 create_cferror_from_aks(aks_return, kAKSKeyOpDelete, 0, 0, access_control_data, acm_context, error));
414
415 ok = true;
416
417 out:
418 if(params)
419 free(params);
420 CFReleaseSafe(access_control_data);
421 return ok;
422 }
423
424 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) {
425 const void* result = NULL;
426 int aks_return = kAKSReturnSuccess;
427 require_noerr_action_quiet(aks_ref_key_create_with_blob(keybag, CFDataGetBytePtr(key_data), CFDataGetLength(key_data), ref_key), out,
428 SecError(errSecNotAvailable, error, CFSTR("aks_ref_key: %x failed to '%s' item (bag: %"PRId32")"), aks_return, "create ref key with blob", keybag));
429 result = aks_ref_key_get_external_data(*ref_key, external_data_len);
430
431 out:
432 return result;
433 }
434 #endif
435
436 bool use_hwaes(void) {
437 static bool use_hwaes;
438 static dispatch_once_t check_once;
439 dispatch_once(&check_once, ^{
440 use_hwaes = hwaes_key_available();
441 if (use_hwaes) {
442 asl_log(NULL, NULL, ASL_LEVEL_INFO, "using hwaes key");
443 } else {
444 asl_log(NULL, NULL, ASL_LEVEL_ERR, "unable to access hwaes key");
445 }
446 });
447 return use_hwaes;
448 }
449
450 bool ks_open_keybag(CFDataRef keybag, CFDataRef password, keybag_handle_t *handle, CFErrorRef *error) {
451 #if USE_KEYSTORE
452 kern_return_t kernResult;
453 if (!asData(keybag, error)) return false;
454 kernResult = aks_load_bag(CFDataGetBytePtr(keybag), (int)CFDataGetLength(keybag), handle);
455 if (kernResult)
456 return SecKernError(kernResult, error, CFSTR("aks_load_bag failed: %@"), keybag);
457
458 if (password) {
459 kernResult = aks_unlock_bag(*handle, CFDataGetBytePtr(password), (int)CFDataGetLength(password));
460 if (kernResult) {
461 aks_unload_bag(*handle);
462 return SecKernError(kernResult, error, CFSTR("aks_unlock_bag failed"));
463 }
464 }
465 return true;
466 #else /* !USE_KEYSTORE */
467 *handle = KEYBAG_NONE;
468 return true;
469 #endif /* USE_KEYSTORE */
470 }
471
472 bool ks_close_keybag(keybag_handle_t keybag, CFErrorRef *error) {
473 #if USE_KEYSTORE
474 IOReturn kernResult = aks_unload_bag(keybag);
475 if (kernResult) {
476 return SecKernError(kernResult, error, CFSTR("aks_unload_bag failed"));
477 }
478 #endif /* USE_KEYSTORE */
479 return true;
480 }
481