]> git.saurik.com Git - apple/security.git/blob - keychain/trust/TrustedPeers/TPPolicyDocument.m
Security-58286.1.32.tar.gz
[apple/security.git] / keychain / trust / TrustedPeers / TPPolicyDocument.m
1 /*
2 * Copyright (c) 2017 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 "TPPolicyDocument.h"
25 #import "TPPolicy.h"
26 #import "TPUtils.h"
27 #import "TPCategoryRule.h"
28
29 static const NSString *kPolicyVersion = @"policyVersion";
30 static const NSString *kModelToCategory = @"modelToCategory";
31 static const NSString *kCategoriesByView = @"categoriesByView";
32 static const NSString *kIntroducersByCategory = @"introducersByCategory";
33 static const NSString *kRedactions = @"redactions";
34 static const NSString *kPrefix = @"prefix";
35 static const NSString *kCategory = @"category";
36
37 @interface TPPolicyDocument ()
38
39 @property (nonatomic, assign) TPCounter policyVersion;
40 @property (nonatomic, strong) NSString *policyHash;
41 @property (nonatomic, strong) NSData *pList;
42
43 @property (nonatomic, strong) NSArray<TPCategoryRule*> *modelToCategory;
44 @property (nonatomic, strong) NSDictionary<NSString*,NSSet<NSString*>*> *categoriesByView;
45 @property (nonatomic, strong) NSDictionary<NSString*,NSSet<NSString*>*> *introducersByCategory;
46 @property (nonatomic, strong) NSDictionary<NSString*,NSData*> *redactions;
47
48 @end
49
50
51 @implementation TPPolicyDocument
52
53 + (nullable NSArray<TPCategoryRule*> *)modelToCategoryFromObj:(id)obj
54 {
55 if (![obj isKindOfClass:[NSArray class]]) {
56 return nil;
57 }
58 NSArray *arr = obj;
59 NSMutableArray<TPCategoryRule*> *rules = [[NSMutableArray alloc] initWithCapacity:arr.count];
60 for (id item in arr) {
61 TPCategoryRule *rule = [self categoryRuleFromObj:item];
62 if (nil == rule) {
63 return nil;
64 }
65 [rules addObject:rule];
66 }
67 return rules;
68 }
69
70 + (nullable TPCategoryRule *)categoryRuleFromObj:(id)obj
71 {
72 if (![obj isKindOfClass:[NSDictionary class]]) {
73 return nil;
74 }
75 NSDictionary *dict = obj;
76 if (![dict[kPrefix] isKindOfClass:[NSString class]]) {
77 return nil;
78 }
79 if (![dict[kCategory] isKindOfClass:[NSString class]]) {
80 return nil;
81 }
82 return [TPCategoryRule ruleWithPrefix:dict[kPrefix] category:dict[kCategory]];
83 }
84
85 // Used for parsing categoriesByView and introducersByCategory
86 // which both have the same structure.
87 + (nullable NSDictionary<NSString*,NSSet<NSString*>*> *)dictionaryOfSetsFromObj:(id)obj
88 {
89 if (![obj isKindOfClass:[NSDictionary class]]) {
90 return nil;
91 }
92 NSDictionary *dict = obj;
93 NSMutableDictionary<NSString*,NSSet<NSString*>*> *result = [NSMutableDictionary dictionary];
94 for (id key in dict) {
95 if (![key isKindOfClass:[NSString class]]) {
96 return nil;
97 }
98 id value = dict[key];
99 if (![value isKindOfClass:[NSArray class]]) {
100 return nil;
101 }
102 NSArray *arr = value;
103 for (id item in arr) {
104 if (![item isKindOfClass:[NSString class]]) {
105 return nil;
106 }
107 }
108 result[key] = [NSSet setWithArray:arr];
109 }
110 return result;
111 }
112
113 + (nullable NSDictionary<NSString*,NSData*> *)redactionsFromObj:(id)obj
114 {
115 if (![obj isKindOfClass:[NSDictionary class]]) {
116 return nil;
117 }
118 NSDictionary *dict = obj;
119 for (id key in dict) {
120 if (![key isKindOfClass:[NSString class]]) {
121 return nil;
122 }
123 id value = dict[key];
124 if (![value isKindOfClass:[NSData class]]) {
125 return nil;
126 }
127 }
128 return dict;
129 }
130
131 + (nullable instancetype)policyDocWithHash:(NSString *)policyHash
132 pList:(NSData *)pList
133 {
134 TPHashAlgo algo = [TPHashBuilder algoOfHash:policyHash];
135 NSString *hash = [TPHashBuilder hashWithAlgo:algo ofData:pList];
136 if (![policyHash isEqualToString:hash]) {
137 return nil;
138 }
139 TPPolicyDocument *doc = [[TPPolicyDocument alloc] init];
140 doc.policyHash = hash;
141 doc.pList = pList;
142
143 id obj = [NSPropertyListSerialization propertyListWithData:pList
144 options:NSPropertyListImmutable
145 format:nil
146 error:NULL];
147 if (![obj isKindOfClass:[NSDictionary class]]) {
148 return nil;
149 }
150 NSDictionary *dict = obj;
151
152 if (![dict[kPolicyVersion] isKindOfClass:[NSNumber class]]) {
153 return nil;
154 }
155 doc.policyVersion = [dict[kPolicyVersion] unsignedLongLongValue];
156
157 doc.modelToCategory = [self modelToCategoryFromObj:dict[kModelToCategory]];
158 if (nil == doc.modelToCategory) {
159 return nil;
160 }
161 doc.categoriesByView = [self dictionaryOfSetsFromObj:dict[kCategoriesByView]];
162 if (nil == doc.categoriesByView) {
163 return nil;
164 }
165 doc.introducersByCategory = [self dictionaryOfSetsFromObj:dict[kIntroducersByCategory]];
166 if (nil == doc.introducersByCategory) {
167 return nil;
168 }
169 doc.redactions = [self redactionsFromObj:dict[kRedactions]];
170 if (nil == doc.redactions) {
171 return nil;
172 }
173 return doc;
174 }
175
176 + (instancetype)policyDocWithVersion:(TPCounter)policyVersion
177 modelToCategory:(NSArray<NSDictionary*> *)modelToCategory
178 categoriesByView:(NSDictionary<NSString*,NSArray<NSString*>*> *)categoriesByView
179 introducersByCategory:(NSDictionary<NSString*,NSArray<NSString*>*> *)introducersByCategory
180 redactions:(NSDictionary<NSString*,NSData*> *)redactions
181 hashAlgo:(TPHashAlgo)hashAlgo
182 {
183 TPPolicyDocument *doc = [[TPPolicyDocument alloc] init];
184
185 doc.policyVersion = policyVersion;
186
187 doc.modelToCategory = [TPPolicyDocument modelToCategoryFromObj:modelToCategory];
188 NSAssert(doc.modelToCategory, @"malformed modelToCategory");
189
190 doc.categoriesByView = [TPPolicyDocument dictionaryOfSetsFromObj:categoriesByView];
191 NSAssert(doc.categoriesByView, @"malformed categoriesByView");
192
193 doc.introducersByCategory = [TPPolicyDocument dictionaryOfSetsFromObj:introducersByCategory];
194 NSAssert(doc.introducersByCategory, @"malformed introducersByCategory");
195
196 doc.redactions = [redactions copy];
197
198 NSDictionary *dict = @{
199 kPolicyVersion: @(policyVersion),
200 kModelToCategory: modelToCategory,
201 kCategoriesByView: categoriesByView,
202 kIntroducersByCategory: introducersByCategory,
203 kRedactions: redactions
204 };
205 doc.pList = [TPUtils serializedPListWithDictionary:dict];
206 doc.policyHash = [TPHashBuilder hashWithAlgo:hashAlgo ofData:doc.pList];
207
208 return doc;
209 }
210
211 + (nullable NSData *)redactionWithEncrypter:(id<TPEncrypter>)encrypter
212 modelToCategory:(nullable NSArray<NSDictionary*> *)modelToCategory
213 categoriesByView:(nullable NSDictionary<NSString*,NSArray<NSString*>*> *)categoriesByView
214 introducersByCategory:(nullable NSDictionary<NSString*,NSArray<NSString*>*> *)introducersByCategory
215 error:(NSError **)error
216 {
217 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
218 if (nil != modelToCategory) {
219 dict[kModelToCategory] = modelToCategory;
220 }
221 if (nil != categoriesByView) {
222 dict[kCategoriesByView] = categoriesByView;
223 }
224 if (nil != introducersByCategory) {
225 dict[kIntroducersByCategory] = introducersByCategory;
226 }
227 NSData *plist = [TPUtils serializedPListWithDictionary:dict];
228 return [encrypter encryptData:plist error:error];
229 }
230
231 - (id<TPPolicy>)policyWithSecrets:(NSDictionary<NSString*,NSData*> *)secrets
232 decrypter:(id<TPDecrypter>)decrypter
233 error:(NSError **)error
234 {
235 NSArray<TPCategoryRule*> *modelToCategory = self.modelToCategory;
236 NSMutableDictionary<NSString*,NSSet<NSString*>*> *categoriesByView
237 = [NSMutableDictionary dictionaryWithDictionary:self.categoriesByView];
238 NSMutableDictionary<NSString*,NSSet<NSString*>*> *introducersByCategory
239 = [NSMutableDictionary dictionaryWithDictionary:self.introducersByCategory];
240
241 // We are going to prepend extra items to modelToCategory.
242 // To make the resulting array order deterministic we sort secrets by name first.
243 NSArray<NSString*> *names = [secrets.allKeys sortedArrayUsingSelector:@selector(compare:)];
244 for (NSString *name in names) {
245 NSData *key = secrets[name];
246 NSData *ciphertext = self.redactions[name];
247 if (nil == ciphertext) {
248 // This is normal. A new version might have no need to redact
249 // info that was revealed by keys for a previous version.
250 continue;
251 }
252 NSData *plist = [decrypter decryptData:ciphertext withKey:key error:error];
253 if (nil == plist) {
254 return nil;
255 }
256 id obj = [NSPropertyListSerialization propertyListWithData:plist
257 options:NSPropertyListImmutable
258 format:nil
259 error:NULL];
260 if (![obj isKindOfClass:[NSDictionary class]]) {
261 return nil;
262 }
263 NSDictionary *dict = obj;
264
265 NSArray<TPCategoryRule*> *extraModelToCategory;
266 extraModelToCategory = [TPPolicyDocument modelToCategoryFromObj:dict[kModelToCategory]];
267 if (nil != extraModelToCategory) {
268 // Extra rules are prepended to the list so that they are considered first.
269 modelToCategory = [extraModelToCategory arrayByAddingObjectsFromArray:modelToCategory];
270 }
271
272 NSDictionary<NSString*,NSSet<NSString*>*> *extraCategoriesByView;
273 extraCategoriesByView = [TPPolicyDocument dictionaryOfSetsFromObj:dict[kCategoriesByView]];
274 if (nil != extraCategoriesByView) {
275 [self mergeExtras:extraCategoriesByView intoDictionary:categoriesByView];
276 }
277
278 NSDictionary<NSString*,NSSet<NSString*>*> *extraIntroducersByCategory;
279 extraIntroducersByCategory = [TPPolicyDocument dictionaryOfSetsFromObj:dict[kIntroducersByCategory]];
280 if (nil != extraIntroducersByCategory) {
281 [self mergeExtras:extraIntroducersByCategory intoDictionary:introducersByCategory];
282 }
283 }
284
285 return [TPPolicy policyWithModelToCategory:modelToCategory
286 categoriesByView:categoriesByView
287 introducersByCategory:introducersByCategory];
288 }
289
290 - (void)mergeExtras:(NSDictionary<NSString*,NSSet<NSString*>*> *)extras
291 intoDictionary:(NSMutableDictionary<NSString*,NSSet<NSString*>*> *)target
292 {
293 for (NSString *name in extras) {
294 NSSet<NSString*>* extraSet = extras[name];
295 if (target[name] == nil) {
296 target[name] = extraSet;
297 } else {
298 target[name] = [target[name] setByAddingObjectsFromSet:extraSet];
299 }
300 }
301 }
302
303 - (BOOL)isEqualToPolicyDocument:(TPPolicyDocument *)other
304 {
305 if (other == self) {
306 return YES;
307 }
308 return self.policyVersion == other.policyVersion
309 && [self.policyHash isEqualToString:other.policyHash]
310 && [self.pList isEqualToData:other.pList]
311 && [self.modelToCategory isEqualToArray:other.modelToCategory]
312 && [self.categoriesByView isEqualToDictionary:other.categoriesByView]
313 && [self.introducersByCategory isEqualToDictionary:other.introducersByCategory]
314 && [self.redactions isEqualToDictionary:other.redactions];
315 }
316
317 #pragma mark - NSObject
318
319 - (BOOL)isEqual:(id)object
320 {
321 if (self == object) {
322 return YES;
323 }
324 if (![object isKindOfClass:[TPPolicyDocument class]]) {
325 return NO;
326 }
327 return [self isEqualToPolicyDocument:object];
328 }
329
330 - (NSUInteger)hash
331 {
332 return [self.policyHash hash];
333 }
334
335 @end