]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSReencryptOutgoingItemsOperation.m
Security-59754.41.1.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 @end
41
42 @implementation CKKSReencryptOutgoingItemsOperation
43 @synthesize nextState = _nextState;
44 @synthesize intendedState = _intendedState;
45
46 - (instancetype)init {
47 return nil;
48 }
49 - (instancetype)initWithDependencies:(CKKSOperationDependencies*)dependencies
50 ckks:(CKKSKeychainView*)ckks
51 intendedState:(OctagonState*)intendedState
52 errorState:(OctagonState*)errorState
53 {
54 if(self = [super init]) {
55 _deps = dependencies;
56 _ckks = ckks;
57
58 _nextState = errorState;
59 _intendedState = intendedState;
60
61 [self addNullableDependency:ckks.keyStateReadyDependency];
62 [self addNullableDependency:ckks.holdReencryptOutgoingItemsOperation];
63
64 // We also depend on the key hierarchy being reasonable
65 [self addNullableDependency:ckks.keyStateReadyDependency];
66 }
67 return self;
68 }
69
70 - (void)main
71 {
72 CKKSKeychainView* ckks = self.ckks;
73
74 [self.deps.databaseProvider dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
75 if(self.cancelled) {
76 ckksnotice("ckksreencrypt", self.deps.zoneID, "CKKSReencryptOutgoingItemsOperation cancelled, quitting");
77 return CKKSDatabaseTransactionRollback;
78 }
79
80 ckks.lastReencryptOutgoingItemsOperation = self;
81
82 NSError* error = nil;
83 bool newItems = false;
84
85 NSArray<CKKSOutgoingQueueEntry*>* oqes = [CKKSOutgoingQueueEntry allInState:SecCKKSStateReencrypt zoneID:self.deps.zoneID error:&error];
86 if(error) {
87 ckkserror("ckksreencrypt", self.deps.zoneID, "Error fetching oqes from database: %@", error);
88 self.error = error;
89 return CKKSDatabaseTransactionRollback;
90 }
91
92 [CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventReencryptOutgoing zone:self.deps.zoneID.zoneName count:oqes.count];
93
94 for(CKKSOutgoingQueueEntry* oqe in oqes) {
95 // If there's already a 'new' item replacing this one, drop the reencryption on the floor
96 CKKSOutgoingQueueEntry* newOQE = [CKKSOutgoingQueueEntry tryFromDatabase:oqe.uuid state:SecCKKSStateNew zoneID:oqe.item.zoneID error:&error];
97 if(error) {
98 ckkserror("ckksreencrypt", self.deps.zoneID, "Couldn't load 'new' OQE to determine status: %@", error);
99 self.error = error;
100 error = nil;
101 continue;
102 }
103 if(newOQE) {
104 ckksnotice("ckksreencrypt", self.deps.zoneID, "Have a new OQE superceding %@ (%@), skipping", oqe, newOQE);
105 // Don't use the state transition here, either, since this item isn't really changing states
106 [oqe deleteFromDatabase:&error];
107 if(error) {
108 ckkserror("ckksreencrypt", self.deps.zoneID, "Couldn't delete reencrypting OQE(%@) from database: %@", oqe, error);
109 self.error = error;
110 error = nil;
111 continue;
112 }
113 continue;
114 }
115
116 ckksnotice("ckksreencrypt", self.deps.zoneID, "Reencrypting item %@", oqe);
117
118 NSDictionary* item = [CKKSItemEncrypter decryptItemToDictionary: oqe.item error:&error];
119 if(error) {
120 if ([error.domain isEqualToString:@"securityd"] && error.code == errSecItemNotFound) {
121 ckkserror("ckksreencrypt", self.deps.zoneID, "Couldn't find key in keychain; asking for reset: %@", error);
122 [ckks _onqueuePokeKeyStateMachine];
123 self.nextState = SecCKKSZoneKeyStateUnhealthy;
124 } else {
125 ckkserror("ckksreencrypt", self.deps.zoneID, "Couldn't decrypt item %@: %@", oqe, error);
126 }
127 self.error = error;
128 error = nil;
129 continue;
130 }
131
132 // Pick a key whose class matches the keyclass that this item
133 CKKSKey* originalKey = [CKKSKey fromDatabase: oqe.item.parentKeyUUID zoneID:self.deps.zoneID error:&error];
134 if(error) {
135 ckkserror("ckksreencrypt", self.deps.zoneID, "Couldn't fetch key (%@) for item %@: %@", oqe.item.parentKeyUUID, oqe, error);
136 self.error = error;
137 error = nil;
138 continue;
139 }
140
141 CKKSKey* newkey = [CKKSKey currentKeyForClass: originalKey.keyclass zoneID:self.deps.zoneID error:&error];
142 [newkey ensureKeyLoaded: &error];
143 if(error) {
144 ckkserror("ckksreencrypt", self.deps.zoneID, "Couldn't fetch the current key for class %@: %@", originalKey.keyclass, error);
145 self.error = error;
146 error = nil;
147 continue;
148 }
149
150 CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase:oqe.item.uuid zoneID:self.deps.zoneID error:&error];
151 if(error) {
152 ckkserror("ckksreencrypt", self.deps.zoneID, "Couldn't fetch ckme (%@) for item %@: %@", oqe.item.parentKeyUUID, oqe, error);
153 self.error = error;
154 error = nil;
155 continue;
156 }
157
158 CKKSItem* encryptedItem = [CKKSItemEncrypter encryptCKKSItem:oqe.item
159 dataDictionary:item
160 updatingCKKSItem:ckme.item
161 parentkey:newkey
162 error:&error];
163
164 if(error) {
165 ckkserror("ckksreencrypt", self.deps.zoneID, "Couldn't encrypt under the new key %@: %@", newkey, error);
166 self.error = error;
167 error = nil;
168 continue;
169 }
170
171 CKKSOutgoingQueueEntry* replacement = [[CKKSOutgoingQueueEntry alloc] initWithCKKSItem:encryptedItem
172 action:oqe.action
173 state:SecCKKSStateNew
174 waitUntil:nil
175 accessGroup:oqe.accessgroup];
176
177 // Don't use the CKKSKeychainView state change here, since we're doing a wholesale item swap.
178 [oqe deleteFromDatabase:&error];
179 [replacement saveToDatabase:&error];
180 if(error) {
181 ckkserror("ckksreencrypt", self.deps.zoneID, "Couldn't save newly-encrypted oqe %@: %@", replacement, error);
182 self.error = error;
183 error = nil;
184 continue;
185 }
186
187 newItems = true;
188 }
189
190 CKKSAnalytics* logger = [CKKSAnalytics logger];
191 if (self.error) {
192 [logger logRecoverableError:error forEvent:CKKSEventProcessReencryption zoneName:self.deps.zoneID.zoneName withAttributes:nil];
193 } else {
194 [logger logSuccessForEvent:CKKSEventProcessReencryption zoneName:self.deps.zoneID.zoneName ];
195 }
196
197 if(newItems) {
198 [ckks processOutgoingQueue:self.deps.ckoperationGroup];
199 }
200 self.nextState = self.intendedState;
201 return true;
202 }];
203 }
204
205 @end;
206
207 #endif