]> git.saurik.com Git - apple/security.git/blame - keychain/KeychainStasher/KeychainStasher.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / KeychainStasher / KeychainStasher.m
CommitLineData
d64be36e
A
1#import <xpc/private.h>
2
3#import "utilities/debugging.h"
4#import <Security/SecItemPriv.h>
5#import "LocalKeychainAnalytics.h"
6
7#import "KeychainStasher.h"
8
9NSString* const kApplicationIdentifier = @"com.apple.security.KeychainStasher";
10
11@implementation KeychainStasher
12
13- (NSError*)errorWithStatus:(OSStatus)status message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3)
14{
15 if (status == errSecSuccess) {
16 return nil;
17 }
18
19 NSString* desc;
20 if (format) {
21 va_list ap;
22 va_start(ap, format);
23 desc = [[NSString alloc] initWithFormat:format arguments:ap];
24 va_end(ap);
25 }
26 return [NSError errorWithDomain:NSOSStatusErrorDomain
27 code:status
28 userInfo:desc ? @{NSLocalizedDescriptionKey : desc} : nil];
29}
30
31- (NSMutableDictionary*)baseQuery {
32 return [@{
33 (id)kSecUseDataProtectionKeychain : @YES,
34 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
35 // Prevents backups in case this ever comes to the Mac, prevents sync
36 (id)kSecAttrSysBound : @(kSecSecAttrSysBoundPreserveDuringRestore),
37 // Belt-and-suspenders prohibition on syncing
38 (id)kSecAttrSynchronizable : @NO,
39 (id)kSecClass : (id)kSecClassKey,
40 (id)kSecAttrApplicationLabel : @"loginstash",
41 // This is the default, but just making sure
42 (id)kSecAttrAccessGroup : kApplicationIdentifier,
43 } mutableCopy];
44}
45
46- (void)stashKey:(NSData*)key withReply:(void (^)(NSError*))reply {
47 secnotice("stashkey", "Will attempt to stash key");
48 if (!key || key.length == 0) {
49 reply([self errorWithStatus:errSecParam message:@"nil or empty key passed"]);
50 return;
51 }
52
53 NSMutableDictionary* query = [self baseQuery];
54 query[(id)kSecValueData] = key;
55
56 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
57 secnotice("stashkey", "SecItemAdd result: %ld", (long)status);
58
59 if (status == errSecDuplicateItem) {
60 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStash hardFailure:NO result:[self errorWithStatus:status message:nil]];
61 query[(id)kSecValueData] = nil;
62 NSDictionary* update = @{(id)kSecValueData : key};
63 status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
64 secnotice("stashkey", "SecItemUpdate result: %ld", (long)status);
65 }
66
67 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStash hardFailure:YES result:[self errorWithStatus:status message:nil]];
68 if (status == errSecSuccess) {
69 reply(nil);
70 } else {
71 reply([self errorWithStatus:status message:@"Stash failed with keychain error"]);
72 }
73}
74
75- (void)loadKeyWithReply:(void (^)(NSData*, NSError*))reply {
76 secnotice("KeychainStasher", "Will attempt to retrieve stashed key");
77
78 NSMutableDictionary* query = [self baseQuery];
79 query[(id)kSecReturnData] = @YES;
80
81 CFTypeRef object = NULL;
82 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &object);
83
84 if (status != errSecSuccess) {
85 if (status == errSecItemNotFound) {
86 reply(nil, [self errorWithStatus:status message:@"No stashed key found"]);
87 } else {
88 reply(nil, [self errorWithStatus:status message:@"Keychain error"]);
89 }
90 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStashLoad hardFailure:YES result:[self errorWithStatus:status message:nil]];
91 return;
92 }
93
94 NSData* key;
95 if (!object || CFGetTypeID(object) != CFDataGetTypeID() || !(key = CFBridgingRelease(object))) {
96 reply(nil, [self errorWithStatus:errSecInternalError
97 message:@"No or bad object: %d / %lu / %d",
98 object != NULL, object ? CFGetTypeID(object) : 0, key != nil]);
99 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStashLoad
100 hardFailure:YES
101 result:[self errorWithStatus:errSecInternalError message:nil]];
102 return;
103 }
104
105 // Caller does not need to wait for our delete
106 reply(key, nil);
107
108 query[(id)kSecReturnData] = nil;
109 status = SecItemDelete((__bridge CFDictionaryRef)query);
110 if (status != errSecSuccess) {
111 seccritical("Unable to delete masterkey after load: %d", (int)status);
112 }
113 [[LocalKeychainAnalytics logger] logResultForEvent:LKAEventStashLoad hardFailure:NO result:[self errorWithStatus:status message:nil]];
114}
115
116@end