2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "TPPolicyDocument.h"
27 #import "TPCategoryRule.h"
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";
37 @interface TPPolicyDocument ()
39 @property (nonatomic, assign) TPCounter policyVersion;
40 @property (nonatomic, strong) NSString *policyHash;
41 @property (nonatomic, strong) NSData *pList;
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;
51 @implementation TPPolicyDocument
53 + (nullable NSArray<TPCategoryRule*> *)modelToCategoryFromObj:(id)obj
55 if (![obj isKindOfClass:[NSArray class]]) {
59 NSMutableArray<TPCategoryRule*> *rules = [[NSMutableArray alloc] initWithCapacity:arr.count];
60 for (id item in arr) {
61 TPCategoryRule *rule = [self categoryRuleFromObj:item];
65 [rules addObject:rule];
70 + (nullable TPCategoryRule *)categoryRuleFromObj:(id)obj
72 if (![obj isKindOfClass:[NSDictionary class]]) {
75 NSDictionary *dict = obj;
76 if (![dict[kPrefix] isKindOfClass:[NSString class]]) {
79 if (![dict[kCategory] isKindOfClass:[NSString class]]) {
82 return [TPCategoryRule ruleWithPrefix:dict[kPrefix] category:dict[kCategory]];
85 // Used for parsing categoriesByView and introducersByCategory
86 // which both have the same structure.
87 + (nullable NSDictionary<NSString*,NSSet<NSString*>*> *)dictionaryOfSetsFromObj:(id)obj
89 if (![obj isKindOfClass:[NSDictionary class]]) {
92 NSDictionary *dict = obj;
93 NSMutableDictionary<NSString*,NSSet<NSString*>*> *result = [NSMutableDictionary dictionary];
94 for (id key in dict) {
95 if (![key isKindOfClass:[NSString class]]) {
99 if (![value isKindOfClass:[NSArray class]]) {
102 NSArray *arr = value;
103 for (id item in arr) {
104 if (![item isKindOfClass:[NSString class]]) {
108 result[key] = [NSSet setWithArray:arr];
113 + (nullable NSDictionary<NSString*,NSData*> *)redactionsFromObj:(id)obj
115 if (![obj isKindOfClass:[NSDictionary class]]) {
118 NSDictionary *dict = obj;
119 for (id key in dict) {
120 if (![key isKindOfClass:[NSString class]]) {
123 id value = dict[key];
124 if (![value isKindOfClass:[NSData class]]) {
131 + (nullable instancetype)policyDocWithHash:(NSString *)policyHash
132 pList:(NSData *)pList
134 TPHashAlgo algo = [TPHashBuilder algoOfHash:policyHash];
135 NSString *hash = [TPHashBuilder hashWithAlgo:algo ofData:pList];
136 if (![policyHash isEqualToString:hash]) {
139 TPPolicyDocument *doc = [[TPPolicyDocument alloc] init];
140 doc.policyHash = hash;
143 id obj = [NSPropertyListSerialization propertyListWithData:pList
144 options:NSPropertyListImmutable
147 if (![obj isKindOfClass:[NSDictionary class]]) {
150 NSDictionary *dict = obj;
152 if (![dict[kPolicyVersion] isKindOfClass:[NSNumber class]]) {
155 doc.policyVersion = [dict[kPolicyVersion] unsignedLongLongValue];
157 doc.modelToCategory = [self modelToCategoryFromObj:dict[kModelToCategory]];
158 if (nil == doc.modelToCategory) {
161 doc.categoriesByView = [self dictionaryOfSetsFromObj:dict[kCategoriesByView]];
162 if (nil == doc.categoriesByView) {
165 doc.introducersByCategory = [self dictionaryOfSetsFromObj:dict[kIntroducersByCategory]];
166 if (nil == doc.introducersByCategory) {
169 doc.redactions = [self redactionsFromObj:dict[kRedactions]];
170 if (nil == doc.redactions) {
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
183 TPPolicyDocument *doc = [[TPPolicyDocument alloc] init];
185 doc.policyVersion = policyVersion;
187 doc.modelToCategory = [TPPolicyDocument modelToCategoryFromObj:modelToCategory];
188 NSAssert(doc.modelToCategory, @"malformed modelToCategory");
190 doc.categoriesByView = [TPPolicyDocument dictionaryOfSetsFromObj:categoriesByView];
191 NSAssert(doc.categoriesByView, @"malformed categoriesByView");
193 doc.introducersByCategory = [TPPolicyDocument dictionaryOfSetsFromObj:introducersByCategory];
194 NSAssert(doc.introducersByCategory, @"malformed introducersByCategory");
196 doc.redactions = [redactions copy];
198 NSDictionary *dict = @{
199 kPolicyVersion: @(policyVersion),
200 kModelToCategory: modelToCategory,
201 kCategoriesByView: categoriesByView,
202 kIntroducersByCategory: introducersByCategory,
203 kRedactions: redactions
205 doc.pList = [TPUtils serializedPListWithDictionary:dict];
206 doc.policyHash = [TPHashBuilder hashWithAlgo:hashAlgo ofData:doc.pList];
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
217 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
218 if (nil != modelToCategory) {
219 dict[kModelToCategory] = modelToCategory;
221 if (nil != categoriesByView) {
222 dict[kCategoriesByView] = categoriesByView;
224 if (nil != introducersByCategory) {
225 dict[kIntroducersByCategory] = introducersByCategory;
227 NSData *plist = [TPUtils serializedPListWithDictionary:dict];
228 return [encrypter encryptData:plist error:error];
231 - (id<TPPolicy>)policyWithSecrets:(NSDictionary<NSString*,NSData*> *)secrets
232 decrypter:(id<TPDecrypter>)decrypter
233 error:(NSError **)error
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];
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.
252 NSData *plist = [decrypter decryptData:ciphertext withKey:key error:error];
256 id obj = [NSPropertyListSerialization propertyListWithData:plist
257 options:NSPropertyListImmutable
260 if (![obj isKindOfClass:[NSDictionary class]]) {
263 NSDictionary *dict = obj;
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];
272 NSDictionary<NSString*,NSSet<NSString*>*> *extraCategoriesByView;
273 extraCategoriesByView = [TPPolicyDocument dictionaryOfSetsFromObj:dict[kCategoriesByView]];
274 if (nil != extraCategoriesByView) {
275 [self mergeExtras:extraCategoriesByView intoDictionary:categoriesByView];
278 NSDictionary<NSString*,NSSet<NSString*>*> *extraIntroducersByCategory;
279 extraIntroducersByCategory = [TPPolicyDocument dictionaryOfSetsFromObj:dict[kIntroducersByCategory]];
280 if (nil != extraIntroducersByCategory) {
281 [self mergeExtras:extraIntroducersByCategory intoDictionary:introducersByCategory];
285 return [TPPolicy policyWithModelToCategory:modelToCategory
286 categoriesByView:categoriesByView
287 introducersByCategory:introducersByCategory];
290 - (void)mergeExtras:(NSDictionary<NSString*,NSSet<NSString*>*> *)extras
291 intoDictionary:(NSMutableDictionary<NSString*,NSSet<NSString*>*> *)target
293 for (NSString *name in extras) {
294 NSSet<NSString*>* extraSet = extras[name];
295 if (target[name] == nil) {
296 target[name] = extraSet;
298 target[name] = [target[name] setByAddingObjectsFromSet:extraSet];
303 - (BOOL)isEqualToPolicyDocument:(TPPolicyDocument *)other
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];
317 #pragma mark - NSObject
319 - (BOOL)isEqual:(id)object
321 if (self == object) {
324 if (![object isKindOfClass:[TPPolicyDocument class]]) {
327 return [self isEqualToPolicyDocument:object];
332 return [self.policyHash hash];