]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSTLKShareRecord.m
Security-59306.140.5.tar.gz
[apple/security.git] / keychain / ckks / CKKSTLKShareRecord.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 #if OCTAGON
25
26 #import <Foundation/NSKeyedArchiver_Private.h>
27
28 #import "keychain/ckks/CKKSTLKShareRecord.h"
29 #import "keychain/ckks/CKKSPeer.h"
30 #import "keychain/ckks/CloudKitCategories.h"
31 #import "keychain/categories/NSError+UsefulConstructors.h"
32
33 #import <SecurityFoundation/SFKey.h>
34 #import <SecurityFoundation/SFEncryptionOperation.h>
35 #import <SecurityFoundation/SFSigningOperation.h>
36 #import <SecurityFoundation/SFDigestOperation.h>
37
38 @interface CKKSTLKShareRecord ()
39 @end
40
41 @implementation CKKSTLKShareRecord
42
43 - (instancetype)init:(CKKSTLKShare*)share
44 zoneID:(CKRecordZoneID*)zoneID
45 encodedCKRecord:(NSData*)encodedCKRecord
46 {
47 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
48 encodedCKRecord:encodedCKRecord
49 zoneID:zoneID])) {
50 _share = share;
51 }
52 return self;
53 }
54
55 -(instancetype)init:(CKKSKey*)key
56 sender:(id<CKKSSelfPeer>)sender
57 receiver:(id<CKKSPeer>)receiver
58 curve:(SFEllipticCurve)curve
59 version:(SecCKKSTLKShareVersion)version
60 epoch:(NSInteger)epoch
61 poisoned:(NSInteger)poisoned
62 zoneID:(CKRecordZoneID*)zoneID
63 encodedCKRecord:(NSData*)encodedCKRecord
64 {
65 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
66 encodedCKRecord:encodedCKRecord
67 zoneID:zoneID])) {
68
69 _share = [[CKKSTLKShare alloc] init:key.keycore
70 sender:sender
71 receiver:receiver
72 curve:curve
73 version:version
74 epoch:epoch
75 poisoned:poisoned
76 zoneID:zoneID];
77 }
78 return self;
79 }
80
81 - (instancetype)initForKey:(NSString*)tlkUUID
82 senderPeerID:(NSString*)senderPeerID
83 recieverPeerID:(NSString*)receiverPeerID
84 receiverEncPublicKeySPKI:(NSData*)publicKeySPKI
85 curve:(SFEllipticCurve)curve
86 version:(SecCKKSTLKShareVersion)version
87 epoch:(NSInteger)epoch
88 poisoned:(NSInteger)poisoned
89 wrappedKey:(NSData*)wrappedKey
90 signature:(NSData*)signature
91 zoneID:(CKRecordZoneID*)zoneID
92 encodedCKRecord:(NSData*)encodedCKRecord
93 {
94 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
95 encodedCKRecord:encodedCKRecord
96 zoneID:zoneID])) {
97
98 _share = [[CKKSTLKShare alloc] initForKey:tlkUUID
99 senderPeerID:senderPeerID
100 recieverPeerID:receiverPeerID
101 receiverEncPublicKeySPKI:publicKeySPKI
102 curve:curve
103 version:version
104 epoch:epoch
105 poisoned:poisoned
106 wrappedKey:wrappedKey
107 signature:signature
108 zoneID:zoneID];
109 }
110 return self;
111 }
112
113 - (NSString*)description {
114 return [NSString stringWithFormat:@"<CKKSTLKShare(%@): recv:%@ send:%@>",
115 self.share.tlkUUID,
116 self.share.receiverPeerID,
117 self.share.senderPeerID];
118 }
119
120 - (NSString*)tlkUUID
121 {
122 return self.share.tlkUUID;
123 }
124
125 - (NSString*)senderPeerID
126 {
127 return self.share.senderPeerID;
128 }
129 - (NSInteger)epoch
130 {
131 return self.share.epoch;
132 }
133
134 - (NSInteger)poisoned
135 {
136 return self.share.poisoned;
137 }
138
139 - (NSData*)wrappedTLK
140 {
141 return self.share.wrappedTLK;
142 }
143 - (NSData*)signature
144 {
145 return self.share.signature;
146 }
147
148 - (CKKSKey*)unwrapUsing:(id<CKKSSelfPeer>)localPeer
149 error:(NSError * __autoreleasing *)error
150 {
151 CKKSKeychainBackedKey* realkey = [self.share unwrapUsing:localPeer
152 error:error];
153
154 if(!realkey) {
155 return nil;
156 }
157
158 return [[CKKSKey alloc] initWithKeyCore:realkey];
159 }
160
161 - (NSData*)dataForSigning
162 {
163 return [self.share dataForSigning:self.storedCKRecord];
164 }
165
166 // Returns the signature, but not the signed data itself;
167 - (NSData*)signRecord:(SFECKeyPair*)signingKey
168 error:(NSError* __autoreleasing *)error
169 {
170 return [self.share signRecord:signingKey
171 ckrecord:self.storedCKRecord
172 error:error];
173 }
174
175 - (bool)verifySignature:(NSData*)signature
176 verifyingPeer:(id<CKKSPeer>)peer
177 error:(NSError* __autoreleasing *)error
178 {
179 return [self.share verifySignature:signature
180 verifyingPeer:peer
181 ckrecord:self.storedCKRecord
182 error:error];
183 }
184
185 - (bool)signatureVerifiesWithPeerSet:(NSSet<id<CKKSPeer>>*)peerSet
186 error:(NSError**)error
187 {
188 return [self.share signatureVerifiesWithPeerSet:peerSet
189 ckrecord:self.storedCKRecord
190 error:error];
191 }
192
193 - (instancetype)copyWithZone:(NSZone *)zone {
194 CKKSTLKShareRecord* shareRecord = [[[self class] allocWithZone:zone] init];
195 shareRecord.share = [self.share copyWithZone:zone];
196 return shareRecord;
197 }
198
199 - (BOOL)isEqual:(id)object {
200 if(![object isKindOfClass:[CKKSTLKShareRecord class]]) {
201 return NO;
202 }
203
204 CKKSTLKShareRecord* obj = (CKKSTLKShareRecord*) object;
205 return [self.share isEqual: obj.share];
206 }
207
208 + (CKKSTLKShareRecord*)share:(CKKSKey*)key
209 as:(id<CKKSSelfPeer>)sender
210 to:(id<CKKSPeer>)receiver
211 epoch:(NSInteger)epoch
212 poisoned:(NSInteger)poisoned
213 error:(NSError* __autoreleasing *)error
214 {
215 NSError* localerror = nil;
216 // Load any existing TLK Share, so we can update it
217 CKKSTLKShareRecord* oldShare = [CKKSTLKShareRecord tryFromDatabase:key.uuid
218 receiverPeerID:receiver.peerID
219 senderPeerID:sender.peerID
220 zoneID:key.zoneID
221 error:&localerror];
222 if(localerror) {
223 secerror("ckksshare: couldn't load old share for %@: %@", key, localerror);
224 if(error) {
225 *error = localerror;
226 }
227 return nil;
228 }
229
230 CKKSTLKShare* share = [CKKSTLKShare share:key.keycore
231 as:sender
232 to:receiver
233 epoch:epoch
234 poisoned:poisoned
235 error:error];
236 if(!share) {
237 return nil;
238 }
239
240 CKKSTLKShareRecord* sharerecord = [[CKKSTLKShareRecord alloc] init:share
241 zoneID:key.zoneID
242 encodedCKRecord:oldShare.encodedCKRecord];
243 return sharerecord;
244 }
245
246 - (CKKSKey*)recoverTLK:(id<CKKSSelfPeer>)recoverer
247 trustedPeers:(NSSet<id<CKKSPeer>>*)peers
248 error:(NSError* __autoreleasing *)error
249 {
250 CKKSKeychainBackedKey* realkey = [self.share recoverTLK:recoverer
251 trustedPeers:peers
252 ckrecord:self.storedCKRecord
253 error:error];
254 if(!realkey) {
255 return nil;
256 }
257 return [[CKKSKey alloc] initWithKeyCore:realkey];
258 }
259
260 #pragma mark - Database Operations
261
262 + (instancetype)fromDatabase:(NSString*)uuid
263 receiverPeerID:(NSString*)receiverPeerID
264 senderPeerID:(NSString*)senderPeerID
265 zoneID:(CKRecordZoneID*)zoneID
266 error:(NSError * __autoreleasing *)error {
267 return [self fromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
268 @"recvpeerid":CKKSNilToNSNull(receiverPeerID),
269 @"senderpeerid":CKKSNilToNSNull(senderPeerID),
270 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
271 }
272
273 + (instancetype)tryFromDatabase:(NSString*)uuid
274 receiverPeerID:(NSString*)receiverPeerID
275 senderPeerID:(NSString*)senderPeerID
276 zoneID:(CKRecordZoneID*)zoneID
277 error:(NSError * __autoreleasing *)error {
278 return [self tryFromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
279 @"recvpeerid":CKKSNilToNSNull(receiverPeerID),
280 @"senderpeerid":CKKSNilToNSNull(senderPeerID),
281 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
282 }
283
284 + (NSArray<CKKSTLKShareRecord*>*)allFor:(NSString*)receiverPeerID
285 keyUUID:(NSString*)uuid
286 zoneID:(CKRecordZoneID*)zoneID
287 error:(NSError * __autoreleasing *)error {
288 return [self allWhere:@{@"recvpeerid":CKKSNilToNSNull(receiverPeerID),
289 @"uuid":uuid,
290 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
291 }
292
293 + (NSArray<CKKSTLKShareRecord*>*)allForUUID:(NSString*)uuid
294 zoneID:(CKRecordZoneID*)zoneID
295 error:(NSError * __autoreleasing *)error {
296 return [self allWhere:@{@"uuid":CKKSNilToNSNull(uuid),
297 @"ckzone":CKKSNilToNSNull(zoneID.zoneName)} error:error];
298 }
299
300 + (NSArray<CKKSTLKShareRecord*>*)allInZone:(CKRecordZoneID*)zoneID
301 error:(NSError * __autoreleasing *)error {
302 return [self allWhere:@{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
303 }
304
305 + (instancetype)tryFromDatabaseFromCKRecordID:(CKRecordID*)recordID
306 error:(NSError * __autoreleasing *)error {
307 // Welp. Try to parse!
308 NSError *localerror = NULL;
309 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^tlkshare-(?<uuid>[0-9A-Fa-f-]*)::(?<receiver>.*)::(?<sender>.*)$"
310 options:NSRegularExpressionCaseInsensitive
311 error:&localerror];
312 if(localerror) {
313 if(error) {
314 *error = localerror;
315 }
316 return nil;
317 }
318
319 NSTextCheckingResult* regexmatch = [regex firstMatchInString:recordID.recordName options:0 range:NSMakeRange(0, recordID.recordName.length)];
320 if(!regexmatch) {
321 if(error) {
322 *error = [NSError errorWithDomain:CKKSErrorDomain
323 code:CKKSNoSuchRecord
324 description:[NSString stringWithFormat:@"Couldn't parse '%@' as a TLKShare ID", recordID.recordName]];
325 }
326 return nil;
327 }
328
329 NSString* uuid = [recordID.recordName substringWithRange:[regexmatch rangeWithName:@"uuid"]];
330 NSString* receiver = [recordID.recordName substringWithRange:[regexmatch rangeWithName:@"receiver"]];
331 NSString* sender = [recordID.recordName substringWithRange:[regexmatch rangeWithName:@"sender"]];
332
333 return [self tryFromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
334 @"recvpeerid":CKKSNilToNSNull(receiver),
335 @"senderpeerid":CKKSNilToNSNull(sender),
336 @"ckzone": CKKSNilToNSNull(recordID.zoneID.zoneName)} error:error];
337 }
338
339 #pragma mark - CKKSCKRecordHolder methods
340
341 + (NSString*)ckrecordPrefix {
342 return @"tlkshare";
343 }
344
345 - (NSString*)CKRecordName {
346 return [NSString stringWithFormat:@"tlkshare-%@::%@::%@", self.share.tlkUUID, self.share.receiverPeerID, self.share.senderPeerID];
347 }
348
349 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID {
350 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
351 @throw [NSException
352 exceptionWithName:@"WrongCKRecordNameException"
353 reason:[NSString stringWithFormat: @"CKRecord name (%@) was not %@", record.recordID.recordName, [self CKRecordName]]
354 userInfo:nil];
355 }
356 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
357 @throw [NSException
358 exceptionWithName:@"WrongCKRecordTypeException"
359 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordTLKShareType]
360 userInfo:nil];
361 }
362
363 record[SecCKRecordSenderPeerID] = self.share.senderPeerID;
364 record[SecCKRecordReceiverPeerID] = self.share.receiverPeerID;
365 record[SecCKRecordReceiverPublicEncryptionKey] = [self.share.receiverPublicEncryptionKeySPKI base64EncodedStringWithOptions:0];
366 record[SecCKRecordCurve] = [NSNumber numberWithUnsignedInteger:(NSUInteger)self.share.curve];
367 record[SecCKRecordVersion] = [NSNumber numberWithUnsignedInteger:(NSUInteger)self.share.version];
368 record[SecCKRecordEpoch] = [NSNumber numberWithLong:(long)self.share.epoch];
369 record[SecCKRecordPoisoned] = [NSNumber numberWithLong:(long)self.share.poisoned];
370
371 record[SecCKRecordParentKeyRefKey] = [[CKReference alloc] initWithRecordID: [[CKRecordID alloc] initWithRecordName: self.share.tlkUUID zoneID: zoneID]
372 action: CKReferenceActionValidate];
373
374 record[SecCKRecordWrappedKeyKey] = [self.share.wrappedTLK base64EncodedStringWithOptions:0];
375 record[SecCKRecordSignature] = [self.share.signature base64EncodedStringWithOptions:0];
376
377 return record;
378 }
379
380 - (bool)matchesCKRecord:(CKRecord*)record {
381 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
382 return false;
383 }
384
385 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
386 return false;
387 }
388
389 CKKSTLKShareRecord* share = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
390 return [self isEqual: share];
391 }
392
393 - (void)setFromCKRecord: (CKRecord*) record {
394 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
395 @throw [NSException
396 exceptionWithName:@"WrongCKRecordTypeException"
397 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordDeviceStateType]
398 userInfo:nil];
399 }
400
401 [self setStoredCKRecord:record];
402
403 NSData* pubkeydata = CKKSUnbase64NullableString(record[SecCKRecordReceiverPublicEncryptionKey]);
404
405 self.share = [[CKKSTLKShare alloc] initForKey:((CKReference*)record[SecCKRecordParentKeyRefKey]).recordID.recordName
406 senderPeerID:record[SecCKRecordSenderPeerID]
407 recieverPeerID:record[SecCKRecordReceiverPeerID]
408 receiverEncPublicKeySPKI:pubkeydata
409 curve:[record[SecCKRecordCurve] longValue] // TODO: sanitize
410 version:[record[SecCKRecordVersion] longValue]
411 epoch:[record[SecCKRecordEpoch] longValue]
412 poisoned:[record[SecCKRecordPoisoned] longValue]
413 wrappedKey:[[NSData alloc] initWithBase64EncodedString:record[SecCKRecordWrappedKeyKey] options:0]
414 signature:[[NSData alloc] initWithBase64EncodedString:record[SecCKRecordSignature] options:0]
415 zoneID:record.recordID.zoneID];
416 }
417
418 #pragma mark - CKKSSQLDatabaseObject methods
419
420 + (NSString*)sqlTable {
421 return @"tlkshare";
422 }
423
424 + (NSArray<NSString*>*)sqlColumns {
425 return @[@"ckzone", @"uuid", @"senderpeerid", @"recvpeerid", @"recvpubenckey", @"poisoned", @"epoch", @"curve", @"version", @"wrappedkey", @"signature", @"ckrecord"];
426 }
427
428 - (NSDictionary<NSString*,NSString*>*)whereClauseToFindSelf {
429 return @{@"uuid":self.share.tlkUUID,
430 @"senderpeerid":self.share.senderPeerID,
431 @"recvpeerid":self.share.receiverPeerID,
432 @"ckzone":self.zoneID.zoneName,
433 };
434 }
435
436 - (NSDictionary<NSString*,NSString*>*)sqlValues {
437 return @{@"uuid": self.share.tlkUUID,
438 @"senderpeerid": self.share.senderPeerID,
439 @"recvpeerid": self.share.receiverPeerID,
440 @"recvpubenckey": CKKSNilToNSNull([self.share.receiverPublicEncryptionKeySPKI base64EncodedStringWithOptions:0]),
441 @"poisoned": [NSString stringWithFormat:@"%ld", (long)self.share.poisoned],
442 @"epoch": [NSString stringWithFormat:@"%ld", (long)self.share.epoch],
443 @"curve": [NSString stringWithFormat:@"%ld", (long)self.share.curve],
444 @"version": [NSString stringWithFormat:@"%ld", (long)self.share.version],
445 @"wrappedkey": CKKSNilToNSNull([self.share.wrappedTLK base64EncodedStringWithOptions:0]),
446 @"signature": CKKSNilToNSNull([self.share.signature base64EncodedStringWithOptions:0]),
447 @"ckzone": CKKSNilToNSNull(self.zoneID.zoneName),
448 @"ckrecord": CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]),
449 };
450 }
451
452 + (instancetype)fromDatabaseRow:(NSDictionary<NSString*, CKKSSQLResult*>*)row {
453 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName: row[@"ckzone"].asString ownerName:CKCurrentUserDefaultName];
454
455 SFEllipticCurve curve = (SFEllipticCurve)row[@"curve"].asNSInteger; // TODO: sanitize
456 SecCKKSTLKShareVersion version = (SecCKKSTLKShareVersion)row[@"version"].asNSInteger; // TODO: sanitize
457
458 return [[CKKSTLKShareRecord alloc] initForKey:row[@"uuid"].asString
459 senderPeerID:row[@"senderpeerid"].asString
460 recieverPeerID:row[@"recvpeerid"].asString
461 receiverEncPublicKeySPKI:row[@"recvpubenckey"].asBase64DecodedData
462 curve:curve
463 version:version
464 epoch:row[@"epoch"].asNSInteger
465 poisoned:row[@"poisoned"].asNSInteger
466 wrappedKey:row[@"wrappedkey"].asBase64DecodedData
467 signature:row[@"signature"].asBase64DecodedData
468 zoneID:zoneID
469 encodedCKRecord:row[@"ckrecord"].asBase64DecodedData
470 ];
471 }
472
473 @end
474
475 #endif // OCTAGON