]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSReencryptOutgoingItemsOperation.m
Security-59306.80.4.tar.gz
[apple/security.git] / keychain / ckks / CKKSReencryptOutgoingItemsOperation.m
1 /*
2 * Copyright (c) 2016 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 #import "keychain/ckks/CKKS.h"
25 #import "keychain/ckks/CKKSKeychainView.h"
26 #import "keychain/ckks/CKKSCurrentKeyPointer.h"
27 #import "keychain/ckks/CKKSKey.h"
28 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
29 #import "keychain/ckks/CKKSReencryptOutgoingItemsOperation.h"
30 #import "keychain/ckks/CKKSItemEncrypter.h"
31 #import "keychain/ckks/CloudKitCategories.h"
32 #import "keychain/ckks/CKKSAnalytics.h"
33 #import "keychain/categories/NSError+UsefulConstructors.h"
34 #import "keychain/analytics/CKKSPowerCollection.h"
35
36 #if OCTAGON
37
38 // Note: reencryption is not, strictly speaking, a CloudKit operation. However, we preserve this to pass it back to the outgoing queue operation we'll create
39 @interface CKKSReencryptOutgoingItemsOperation ()
40 @property CKOperationGroup* ckoperationGroup;
41 @end
42
43 @implementation CKKSReencryptOutgoingItemsOperation
44
45 - (instancetype)init {
46 return nil;
47 }
48 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
49 if(self = [super init]) {
50 _ckks = ckks;
51 _ckoperationGroup = ckoperationGroup;
52
53 [self addNullableDependency:ckks.keyStateReadyDependency];
54 [self addNullableDependency:ckks.holdReencryptOutgoingItemsOperation];
55
56 // We also depend on the key hierarchy being reasonable
57 [self addNullableDependency:ckks.keyStateReadyDependency];
58
59 }
60 return self;
61 }
62
63 - (void) main {
64 CKKSKeychainView* ckks = self.ckks;
65 if(!ckks) {
66 ckkserror("ckksreencrypt", ckks, "no CKKS object");
67 return;
68 }
69
70 [ckks dispatchSync: ^bool{
71 if(self.cancelled) {
72 ckksnotice("ckksreencrypt", ckks, "CKKSReencryptOutgoingItemsOperation cancelled, quitting");
73 return false;
74 }
75
76 ckks.lastReencryptOutgoingItemsOperation = self;
77
78 NSError* error = nil;
79 bool newItems = false;
80
81 NSArray<CKKSOutgoingQueueEntry*>* oqes = [CKKSOutgoingQueueEntry allInState: SecCKKSStateReencrypt zoneID:ckks.zoneID error:&error];
82 if(error) {
83 ckkserror("ckksreencrypt", ckks, "Error fetching oqes from database: %@", error);
84 self.error = error;
85 return false;
86 }
87
88 [CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventReencryptOutgoing zone:ckks.zoneName count:oqes.count];
89
90 for(CKKSOutgoingQueueEntry* oqe in oqes) {
91 // If there's already a 'new' item replacing this one, drop the reencryption on the floor
92 CKKSOutgoingQueueEntry* newOQE = [CKKSOutgoingQueueEntry tryFromDatabase:oqe.uuid state:SecCKKSStateNew zoneID:oqe.item.zoneID error:&error];
93 if(error) {
94 ckkserror("ckksreencrypt", ckks, "Couldn't load 'new' OQE to determine status: %@", error);
95 self.error = error;
96 error = nil;
97 continue;
98 }
99 if(newOQE) {
100 ckksnotice("ckksreencrypt", ckks, "Have a new OQE superceding %@ (%@), skipping", oqe, newOQE);
101 // Don't use the state transition here, either, since this item isn't really changing states
102 [oqe deleteFromDatabase:&error];
103 if(error) {
104 ckkserror("ckksreencrypt", ckks, "Couldn't delete reencrypting OQE(%@) from database: %@", oqe, error);
105 self.error = error;
106 error = nil;
107 continue;
108 }
109 continue;
110 }
111
112 ckksnotice("ckksreencrypt", ckks, "Reencrypting item %@", oqe);
113
114 NSDictionary* item = [CKKSItemEncrypter decryptItemToDictionary: oqe.item error:&error];
115 if(error) {
116 if ([error.domain isEqualToString:@"securityd"] && error.code == errSecItemNotFound) {
117 ckkserror("ckksreencrypt", ckks, "Couldn't find key in keychain; attempting to poke key hierarchy: %@", error);
118 [ckks.pokeKeyStateMachineScheduler trigger];
119 } else {
120 ckkserror("ckksreencrypt", ckks, "Couldn't decrypt item %@: %@", oqe, error);
121 }
122 self.error = error;
123 error = nil;
124 continue;
125 }
126
127 // Pick a key whose class matches the keyclass that this item
128 CKKSKey* originalKey = [CKKSKey fromDatabase: oqe.item.parentKeyUUID zoneID:ckks.zoneID error:&error];
129 if(error) {
130 ckkserror("ckksreencrypt", ckks, "Couldn't fetch key (%@) for item %@: %@", oqe.item.parentKeyUUID, oqe, error);
131 self.error = error;
132 error = nil;
133 continue;
134 }
135
136 CKKSKey* newkey = [CKKSKey currentKeyForClass: originalKey.keyclass zoneID:ckks.zoneID error:&error];
137 [newkey ensureKeyLoaded: &error];
138 if(error) {
139 ckkserror("ckksreencrypt", ckks, "Couldn't fetch the current key for class %@: %@", originalKey.keyclass, error);
140 self.error = error;
141 error = nil;
142 continue;
143 }
144
145 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase:oqe.item.uuid zoneID:ckks.zoneID error:&error];
146 if(error) {
147 ckkserror("ckksreencrypt", ckks, "Couldn't fetch ckme (%@) for item %@: %@", oqe.item.parentKeyUUID, oqe, error);
148 self.error = error;
149 error = nil;
150 continue;
151 }
152
153 CKKSItem* encryptedItem = [CKKSItemEncrypter encryptCKKSItem:oqe.item
154 dataDictionary:item
155 updatingCKKSItem:ckme.item
156 parentkey:newkey
157 error:&error];
158
159 if(error) {
160 ckkserror("ckksreencrypt", ckks, "Couldn't encrypt under the new key %@: %@", newkey, error);
161 self.error = error;
162 error = nil;
163 continue;
164 }
165
166 CKKSOutgoingQueueEntry* replacement = [[CKKSOutgoingQueueEntry alloc] initWithCKKSItem:encryptedItem
167 action:oqe.action
168 state:SecCKKSStateNew
169 waitUntil:nil
170 accessGroup:oqe.accessgroup];
171
172 // Don't use the CKKSKeychainView state change here, since we're doing a wholesale item swap.
173 [oqe deleteFromDatabase:&error];
174 [replacement saveToDatabase:&error];
175 if(error) {
176 ckkserror("ckksreencrypt", ckks, "Couldn't save newly-encrypted oqe %@: %@", replacement, error);
177 self.error = error;
178 error = nil;
179 continue;
180 }
181
182 newItems = true;
183 }
184
185 CKKSAnalytics* logger = [CKKSAnalytics logger];
186 if (self.error) {
187 [logger logRecoverableError:error forEvent:CKKSEventProcessReencryption inView:ckks withAttributes:nil];
188 } else {
189 [logger logSuccessForEvent:CKKSEventProcessReencryption inView:ckks];
190 }
191
192 if(newItems) {
193 [ckks processOutgoingQueue:self.ckoperationGroup];
194 }
195 return true;
196 }];
197 }
198
199 @end;
200
201 #endif