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