]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSTLKShare.m
Security-58286.51.6.tar.gz
[apple/security.git] / keychain / ckks / CKKSTLKShare.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/CKKSTLKShare.h"
29 #import "keychain/ckks/CKKSPeer.h"
30 #import "keychain/ckks/CloudKitCategories.h"
31
32 #import <SecurityFoundation/SFKey.h>
33 #import <SecurityFoundation/SFEncryptionOperation.h>
34 #import <SecurityFoundation/SFSigningOperation.h>
35 #import <SecurityFoundation/SFDigestOperation.h>
36
37 @interface CKKSTLKShare ()
38 @end
39
40 @implementation CKKSTLKShare
41 -(instancetype)init:(CKKSKey*)key
42 sender:(id<CKKSSelfPeer>)sender
43 receiver:(id<CKKSPeer>)receiver
44 curve:(SFEllipticCurve)curve
45 version:(SecCKKSTLKShareVersion)version
46 epoch:(NSInteger)epoch
47 poisoned:(NSInteger)poisoned
48 zoneID:(CKRecordZoneID*)zoneID
49 encodedCKRecord:(NSData*)encodedCKRecord
50 {
51 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
52 encodedCKRecord:encodedCKRecord
53 zoneID:zoneID])) {
54 _curve = curve;
55 _version = version;
56 _tlkUUID = key.uuid;
57
58 _receiver = receiver;
59 _senderPeerID = sender.peerID;
60
61 _epoch = epoch;
62 _poisoned = poisoned;
63 }
64 return self;
65 }
66
67 - (instancetype)initForKey:(NSString*)tlkUUID
68 senderPeerID:(NSString*)senderPeerID
69 recieverPeerID:(NSString*)receiverPeerID
70 receiverEncPublicKey:(SFECPublicKey*)publicKey
71 curve:(SFEllipticCurve)curve
72 version:(SecCKKSTLKShareVersion)version
73 epoch:(NSInteger)epoch
74 poisoned:(NSInteger)poisoned
75 wrappedKey:(NSData*)wrappedKey
76 signature:(NSData*)signature
77 zoneID:(CKRecordZoneID*)zoneID
78 encodedCKRecord:(NSData*)encodedCKRecord
79 {
80 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
81 encodedCKRecord:encodedCKRecord
82 zoneID:zoneID])) {
83 _tlkUUID = tlkUUID;
84 _senderPeerID = senderPeerID;
85
86 _receiver = [[CKKSSOSPeer alloc] initWithSOSPeerID:receiverPeerID encryptionPublicKey:publicKey signingPublicKey:nil];
87
88 _curve = curve;
89 _version = version;
90 _epoch = epoch;
91 _poisoned = poisoned;
92
93 _wrappedTLK = wrappedKey;
94 _signature = signature;
95 }
96 return self;
97 }
98
99 - (NSString*)description {
100 return [NSString stringWithFormat:@"<CKKSTLKShare(%@): recv:%@ send:%@>", self.tlkUUID, self.receiver, self.senderPeerID];
101 }
102
103 - (NSData*)wrap:(CKKSKey*)key publicKey:(SFECPublicKey*)receiverPublicKey error:(NSError* __autoreleasing *)error {
104 NSData* plaintext = [key serializeAsProtobuf:error];
105 if(!plaintext) {
106 return nil;
107 }
108
109 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
110 SFIESCiphertext* ciphertext = [sfieso encrypt:plaintext withKey:receiverPublicKey error:error];
111
112 // Now use NSCoding to turn the ciphertext into something transportable
113 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
114 [ciphertext encodeWithCoder:archiver];
115
116 return archiver.encodedData;
117 }
118
119 - (CKKSKey*)unwrapUsing:(id<CKKSSelfPeer>)localPeer error:(NSError * __autoreleasing *)error {
120 // Unwrap the ciphertext using NSSecureCoding
121 NSKeyedUnarchiver *coder = [[NSKeyedUnarchiver alloc] initForReadingFromData:self.wrappedTLK error:nil];
122 SFIESCiphertext* ciphertext = [[SFIESCiphertext alloc] initWithCoder:coder];
123 [coder finishDecoding];
124
125 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
126
127 NSError* localerror = nil;
128 NSData* plaintext = [sfieso decrypt:ciphertext withKey:localPeer.encryptionKey error:&localerror];
129 if(!plaintext || localerror) {
130 if(error) {
131 *error = localerror;
132 }
133 return nil;
134 }
135
136 return [CKKSKey loadFromProtobuf:plaintext error:error];
137 }
138
139 // Serialize this record in some format suitable for signing.
140 // This record must serialize exactly the same on the other side for the signature to verify.
141 - (NSData*)dataForSigning {
142 // Ideally, we'd put this as DER or some other structured, versioned format.
143 // For now, though, do the straightforward thing and concatenate the fields of interest.
144 NSMutableData* dataToSign = [[NSMutableData alloc] init];
145
146 uint64_t version = OSSwapHostToLittleConstInt64(self.version);
147 [dataToSign appendBytes:&version length:sizeof(version)];
148
149 // We only include the peer IDs in the signature; the receiver doesn't care if we signed the receiverPublicKey field;
150 // if it's wrong or doesn't match, the receiver will simply fail to decrypt the encrypted record.
151 [dataToSign appendData:[self.receiver.peerID dataUsingEncoding:NSUTF8StringEncoding]];
152 [dataToSign appendData:[self.senderPeerID dataUsingEncoding:NSUTF8StringEncoding]];
153
154 [dataToSign appendData:self.wrappedTLK];
155
156 uint64_t curve = OSSwapHostToLittleConstInt64(self.curve);
157 [dataToSign appendBytes:&curve length:sizeof(curve)];
158
159 uint64_t epoch = OSSwapHostToLittleConstInt64(self.epoch);
160 [dataToSign appendBytes:&epoch length:sizeof(epoch)];
161
162 uint64_t poisoned = OSSwapHostToLittleConstInt64(self.poisoned);
163 [dataToSign appendBytes:&poisoned length:sizeof(poisoned)];
164
165 // If we have a CKRecord saved here, add any unknown fields (that don't start with server_) to the signed data
166 // in sorted order by CKRecord key
167 CKRecord* record = self.storedCKRecord;
168 if(record) {
169 NSMutableDictionary<NSString*,id>* extraData = [NSMutableDictionary dictionary];
170
171 for(NSString* key in record.allKeys) {
172 if([key isEqualToString:SecCKRecordSenderPeerID] ||
173 [key isEqualToString:SecCKRecordReceiverPeerID] ||
174 [key isEqualToString:SecCKRecordReceiverPublicEncryptionKey] ||
175 [key isEqualToString:SecCKRecordCurve] ||
176 [key isEqualToString:SecCKRecordEpoch] ||
177 [key isEqualToString:SecCKRecordPoisoned] ||
178 [key isEqualToString:SecCKRecordSignature] ||
179 [key isEqualToString:SecCKRecordVersion] ||
180 [key isEqualToString:SecCKRecordParentKeyRefKey] ||
181 [key isEqualToString:SecCKRecordWrappedKeyKey]) {
182 // This version of CKKS knows about this data field. Ignore them with prejudice.
183 continue;
184 }
185
186 if([key hasPrefix:@"server_"]) {
187 // Ignore all fields prefixed by "server_"
188 continue;
189 }
190
191 extraData[key] = record[key];
192 }
193
194 NSArray* extraKeys = [[extraData allKeys] sortedArrayUsingSelector:@selector(compare:)];
195 for(NSString* extraKey in extraKeys) {
196 id obj = extraData[extraKey];
197
198 // Skip CKReferences, NSArray, CLLocation, and CKAsset.
199 if([obj isKindOfClass: [NSString class]]) {
200 [dataToSign appendData: [obj dataUsingEncoding: NSUTF8StringEncoding]];
201 } else if([obj isKindOfClass: [NSData class]]) {
202 [dataToSign appendData: obj];
203 } else if([obj isKindOfClass:[NSDate class]]) {
204 NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
205 NSString* str = [formatter stringForObjectValue: obj];
206 [dataToSign appendData: [str dataUsingEncoding: NSUTF8StringEncoding]];
207 } else if([obj isKindOfClass: [NSNumber class]]) {
208 // Add an NSNumber
209 uint64_t n64 = OSSwapHostToLittleConstInt64([obj unsignedLongLongValue]);
210 [dataToSign appendBytes:&n64 length:sizeof(n64)];
211 }
212 }
213 }
214
215 return dataToSign;
216 }
217
218 // Returns the signature, but not the signed data itself;
219 - (NSData*)signRecord:(SFECKeyPair*)signingKey error:(NSError* __autoreleasing *)error {
220 // TODO: the digest operation can't be changed, as we don't have a good way of communicating it, like self.curve
221 SFEC_X962SigningOperation* xso = [[SFEC_X962SigningOperation alloc] initWithKeySpecifier:[[SFECKeySpecifier alloc] initWithCurve:self.curve]
222 digestOperation:[[SFSHA256DigestOperation alloc] init]];
223
224 NSData* data = [self dataForSigning];
225 SFSignedData* signedData = [xso sign:data withKey:signingKey error:error];
226
227 return signedData.signature;
228 }
229
230 - (bool)verifySignature:(NSData*)signature verifyingPeer:(id<CKKSPeer>)peer error:(NSError* __autoreleasing *)error {
231 if(!peer.publicSigningKey) {
232 secerror("ckksshare: no signing key for peer: %@", peer);
233 if(error) {
234 *error = [NSError errorWithDomain:CKKSErrorDomain
235 code:CKKSNoSigningKey
236 description:[NSString stringWithFormat:@"Peer(%@) has no signing key", peer]];
237 }
238 return false;
239 }
240
241 // TODO: the digest operation can't be changed, as we don't have a good way of communicating it, like self.curve
242 SFEC_X962SigningOperation* xso = [[SFEC_X962SigningOperation alloc] initWithKeySpecifier:[[SFECKeySpecifier alloc] initWithCurve:self.curve]
243 digestOperation:[[SFSHA256DigestOperation alloc] init]];
244 SFSignedData* signedData = [[SFSignedData alloc] initWithData:[self dataForSigning] signature:signature];
245
246 bool ret = [xso verify:signedData withKey:peer.publicSigningKey error:error];
247 return ret;
248 }
249
250 - (bool)signatureVerifiesWithPeerSet:(NSSet<id<CKKSPeer>>*)peerSet error:(NSError**)error {
251 NSError* lastVerificationError = nil;
252 for(id<CKKSPeer> peer in peerSet) {
253 if([peer.peerID isEqualToString: self.senderPeerID]) {
254 // Does the signature verify using this peer?
255 NSError* localerror = nil;
256 bool isSigned = [self verifySignature:self.signature verifyingPeer:peer error:&localerror];
257 if(localerror) {
258 secerror("ckksshare: signature didn't verify for %@ %@: %@", self, peer, localerror);
259 lastVerificationError = localerror;
260 }
261 if(isSigned) {
262 return true;
263 }
264 }
265 }
266
267 if(error) {
268 if(lastVerificationError) {
269 *error = lastVerificationError;
270 } else {
271 *error = [NSError errorWithDomain:CKKSErrorDomain
272 code:CKKSNoTrustedTLKShares
273 description:[NSString stringWithFormat:@"No TLK share from %@", self.senderPeerID]];
274 }
275 }
276 return false;
277 }
278
279 - (instancetype)copyWithZone:(NSZone *)zone {
280 CKKSTLKShare* share = [[[self class] allocWithZone:zone] init];
281 share.curve = self.curve;
282 share.version = self.version;
283 share.tlkUUID = [self.tlkUUID copy];
284 share.senderPeerID = [self.senderPeerID copy];
285 share.epoch = self.epoch;
286 share.poisoned = self.poisoned;
287 share.wrappedTLK = [self.wrappedTLK copy];
288 share.signature = [self.signature copy];
289
290 share.receiver = self.receiver;
291 return share;
292 }
293
294 - (BOOL)isEqual:(id)object {
295 if(![object isKindOfClass:[CKKSTLKShare class]]) {
296 return NO;
297 }
298
299 CKKSTLKShare* obj = (CKKSTLKShare*) object;
300
301 // Note that for purposes of CKKSTLK equality, we only care about the receiver's peer ID and publicEncryptionKey
302 // <rdar://problem/34897551> SFKeys should support [isEqual:]
303 return ([self.tlkUUID isEqualToString:obj.tlkUUID] &&
304 [self.zoneID isEqual: obj.zoneID] &&
305 [self.senderPeerID isEqualToString:obj.senderPeerID] &&
306 ((self.receiver.peerID == nil && obj.receiver.peerID == nil) || [self.receiver.peerID isEqual: obj.receiver.peerID]) &&
307 ((self.receiver.publicEncryptionKey == nil && obj.receiver.publicEncryptionKey == nil)
308 || [self.receiver.publicEncryptionKey.keyData isEqual: obj.receiver.publicEncryptionKey.keyData]) &&
309 self.epoch == obj.epoch &&
310 self.curve == obj.curve &&
311 self.poisoned == obj.poisoned &&
312 ((self.wrappedTLK == nil && obj.wrappedTLK == nil) || [self.wrappedTLK isEqual: obj.wrappedTLK]) &&
313 ((self.signature == nil && obj.signature == nil) || [self.signature isEqual: obj.signature]) &&
314 true) ? YES : NO;
315 }
316
317 + (CKKSTLKShare*)share:(CKKSKey*)key
318 as:(id<CKKSSelfPeer>)sender
319 to:(id<CKKSPeer>)receiver
320 epoch:(NSInteger)epoch
321 poisoned:(NSInteger)poisoned
322 error:(NSError* __autoreleasing *)error
323 {
324 NSError* localerror = nil;
325
326 // Load any existing TLK Share, so we can update it
327 CKKSTLKShare* oldShare = [CKKSTLKShare tryFromDatabase:key.uuid
328 receiverPeerID:receiver.peerID
329 senderPeerID:sender.peerID
330 zoneID:key.zoneID
331 error:&localerror];
332 if(localerror) {
333 secerror("ckksshare: couldn't load old share for %@: %@", key, localerror);
334 if(error) {
335 *error = localerror;
336 }
337 return nil;
338 }
339
340 CKKSTLKShare* share = [[CKKSTLKShare alloc] init:key
341 sender:sender
342 receiver:receiver
343 curve:SFEllipticCurveNistp384
344 version:SecCKKSTLKShareCurrentVersion
345 epoch:epoch
346 poisoned:poisoned
347 zoneID:key.zoneID
348 encodedCKRecord:oldShare.encodedCKRecord];
349
350 share.wrappedTLK = [share wrap:key publicKey:receiver.publicEncryptionKey error:&localerror];
351 if(localerror) {
352 secerror("ckksshare: couldn't share %@ (wrap failed): %@", key, localerror);
353 if(error) {
354 *error = localerror;
355 }
356 return nil;
357 }
358
359 share.signature = [share signRecord:sender.signingKey error:&localerror];
360 if(localerror) {
361 secerror("ckksshare: couldn't share %@ (signing failed): %@", key, localerror);
362 if(error) {
363 *error = localerror;
364 }
365 return nil;
366 }
367
368 return share;
369 }
370
371 - (CKKSKey*)recoverTLK:(id<CKKSSelfPeer>)recoverer
372 trustedPeers:(NSSet<id<CKKSPeer>>*)peers
373 error:(NSError* __autoreleasing *)error
374 {
375 NSError* localerror = nil;
376
377 id<CKKSPeer> peer = nil;
378 for(id<CKKSPeer> p in peers) {
379 if([p.peerID isEqualToString: self.senderPeerID]) {
380 peer = p;
381 }
382 }
383
384 if(!peer) {
385 localerror = [NSError errorWithDomain:CKKSErrorDomain
386 code:CKKSNoTrustedPeer
387 description:[NSString stringWithFormat:@"No trusted peer signed %@", self]];
388 if(error) {
389 *error = localerror;
390 }
391 return nil;
392 }
393
394 bool isSigned = [self verifySignature:self.signature verifyingPeer:peer error:error];
395 if(!isSigned) {
396 return nil;
397 }
398
399 CKKSKey* tlkTrial = [self unwrapUsing:recoverer error:error];
400 if(!tlkTrial) {
401 return nil;
402 }
403
404 if(![self.tlkUUID isEqualToString:tlkTrial.uuid]) {
405 localerror = [NSError errorWithDomain:CKKSErrorDomain
406 code:CKKSDataMismatch
407 description:[NSString stringWithFormat:@"Signed UUID doesn't match unsigned UUID for %@", self]];
408 if(error) {
409 *error = localerror;
410 }
411 return nil;
412 }
413
414 return tlkTrial;
415 }
416
417 #pragma mark - Database Operations
418
419 + (instancetype)fromDatabase:(NSString*)uuid
420 receiverPeerID:(NSString*)receiverPeerID
421 senderPeerID:(NSString*)senderPeerID
422 zoneID:(CKRecordZoneID*)zoneID
423 error:(NSError * __autoreleasing *)error {
424 return [self fromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
425 @"recvpeerid":CKKSNilToNSNull(receiverPeerID),
426 @"senderpeerid":CKKSNilToNSNull(senderPeerID),
427 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
428 }
429
430 + (instancetype)tryFromDatabase:(NSString*)uuid
431 receiverPeerID:(NSString*)receiverPeerID
432 senderPeerID:(NSString*)senderPeerID
433 zoneID:(CKRecordZoneID*)zoneID
434 error:(NSError * __autoreleasing *)error {
435 return [self tryFromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
436 @"recvpeerid":CKKSNilToNSNull(receiverPeerID),
437 @"senderpeerid":CKKSNilToNSNull(senderPeerID),
438 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
439 }
440
441 + (NSArray<CKKSTLKShare*>*)allFor:(NSString*)receiverPeerID
442 keyUUID:(NSString*)uuid
443 zoneID:(CKRecordZoneID*)zoneID
444 error:(NSError * __autoreleasing *)error {
445 return [self allWhere:@{@"recvpeerid":CKKSNilToNSNull(receiverPeerID),
446 @"uuid":uuid,
447 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
448 }
449
450 + (NSArray<CKKSTLKShare*>*)allForUUID:(NSString*)uuid
451 zoneID:(CKRecordZoneID*)zoneID
452 error:(NSError * __autoreleasing *)error {
453 return [self allWhere:@{@"uuid":CKKSNilToNSNull(uuid),
454 @"ckzone":CKKSNilToNSNull(zoneID.zoneName)} error:error];
455 }
456
457 + (NSArray<CKKSTLKShare*>*)allInZone:(CKRecordZoneID*)zoneID
458 error:(NSError * __autoreleasing *)error {
459 return [self allWhere:@{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
460 }
461
462 + (instancetype)tryFromDatabaseFromCKRecordID:(CKRecordID*)recordID
463 error:(NSError * __autoreleasing *)error {
464 // Welp. Try to parse!
465 NSError *localerror = NULL;
466 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^tlkshare-(?<uuid>[0-9A-Fa-f-]*)::(?<receiver>.*)::(?<sender>.*)$"
467 options:NSRegularExpressionCaseInsensitive
468 error:&localerror];
469 if(localerror) {
470 if(error) {
471 *error = localerror;
472 }
473 return nil;
474 }
475
476 NSTextCheckingResult* regexmatch = [regex firstMatchInString:recordID.recordName options:0 range:NSMakeRange(0, recordID.recordName.length)];
477 if(!regexmatch) {
478 if(error) {
479 *error = [NSError errorWithDomain:CKKSErrorDomain
480 code:CKKSNoSuchRecord
481 description:[NSString stringWithFormat:@"Couldn't parse '%@' as a TLKShare ID", recordID.recordName]];
482 }
483 return nil;
484 }
485
486 NSString* uuid = [recordID.recordName substringWithRange:[regexmatch rangeWithName:@"uuid"]];
487 NSString* receiver = [recordID.recordName substringWithRange:[regexmatch rangeWithName:@"receiver"]];
488 NSString* sender = [recordID.recordName substringWithRange:[regexmatch rangeWithName:@"sender"]];
489
490 return [self tryFromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
491 @"recvpeerid":CKKSNilToNSNull(receiver),
492 @"senderpeerid":CKKSNilToNSNull(sender),
493 @"ckzone": CKKSNilToNSNull(recordID.zoneID.zoneName)} error:error];
494 }
495
496 #pragma mark - CKKSCKRecordHolder methods
497
498 + (NSString*)ckrecordPrefix {
499 return @"tlkshare";
500 }
501
502 - (NSString*)CKRecordName {
503 return [NSString stringWithFormat:@"tlkshare-%@::%@::%@", self.tlkUUID, self.receiver.peerID, self.senderPeerID];
504 }
505
506 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID {
507 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
508 @throw [NSException
509 exceptionWithName:@"WrongCKRecordNameException"
510 reason:[NSString stringWithFormat: @"CKRecord name (%@) was not %@", record.recordID.recordName, [self CKRecordName]]
511 userInfo:nil];
512 }
513 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
514 @throw [NSException
515 exceptionWithName:@"WrongCKRecordTypeException"
516 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordTLKShareType]
517 userInfo:nil];
518 }
519
520 record[SecCKRecordSenderPeerID] = self.senderPeerID;
521 record[SecCKRecordReceiverPeerID] = self.receiver.peerID;
522 record[SecCKRecordReceiverPublicEncryptionKey] = [self.receiver.publicEncryptionKey.keyData base64EncodedStringWithOptions:0];
523 record[SecCKRecordCurve] = [NSNumber numberWithUnsignedInteger:(NSUInteger)self.curve];
524 record[SecCKRecordVersion] = [NSNumber numberWithUnsignedInteger:(NSUInteger)self.version];
525 record[SecCKRecordEpoch] = [NSNumber numberWithLong:(long)self.epoch];
526 record[SecCKRecordPoisoned] = [NSNumber numberWithLong:(long)self.poisoned];
527
528 record[SecCKRecordParentKeyRefKey] = [[CKReference alloc] initWithRecordID: [[CKRecordID alloc] initWithRecordName: self.tlkUUID zoneID: zoneID]
529 action: CKReferenceActionValidate];
530
531 record[SecCKRecordWrappedKeyKey] = [self.wrappedTLK base64EncodedStringWithOptions:0];
532 record[SecCKRecordSignature] = [self.signature base64EncodedStringWithOptions:0];
533
534 return record;
535 }
536
537 - (bool)matchesCKRecord:(CKRecord*)record {
538 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
539 return false;
540 }
541
542 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
543 return false;
544 }
545
546 CKKSTLKShare* share = [[CKKSTLKShare alloc] initWithCKRecord:record];
547 return [self isEqual: share];
548 }
549
550 - (void)setFromCKRecord: (CKRecord*) record {
551 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
552 @throw [NSException
553 exceptionWithName:@"WrongCKRecordTypeException"
554 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordDeviceStateType]
555 userInfo:nil];
556 }
557
558 [self setStoredCKRecord:record];
559
560 self.senderPeerID = record[SecCKRecordSenderPeerID];
561 self.curve = [record[SecCKRecordCurve] longValue]; // TODO: sanitize
562 self.version = [record[SecCKRecordVersion] longValue];
563
564 NSData* pubkeydata = CKKSUnbase64NullableString(record[SecCKRecordReceiverPublicEncryptionKey]);
565 NSError* error = nil;
566 SFECPublicKey* receiverPublicKey = pubkeydata ? [[SFECPublicKey alloc] initWithData:pubkeydata
567 specifier:[[SFECKeySpecifier alloc] initWithCurve:self.curve]
568 error:&error] : nil;
569
570 if(error) {
571 ckkserror("ckksshare", record.recordID.zoneID, "Couldn't make public key from data: %@", error);
572 receiverPublicKey = nil;
573 }
574
575 self.receiver = [[CKKSSOSPeer alloc] initWithSOSPeerID:record[SecCKRecordReceiverPeerID] encryptionPublicKey:receiverPublicKey signingPublicKey:nil];
576
577 self.epoch = [record[SecCKRecordEpoch] longValue];
578 self.poisoned = [record[SecCKRecordPoisoned] longValue];
579
580 self.tlkUUID = ((CKReference*)record[SecCKRecordParentKeyRefKey]).recordID.recordName;
581
582 self.wrappedTLK = CKKSUnbase64NullableString(record[SecCKRecordWrappedKeyKey]);
583 self.signature = CKKSUnbase64NullableString(record[SecCKRecordSignature]);
584 }
585
586 #pragma mark - CKKSSQLDatabaseObject methods
587
588 + (NSString*)sqlTable {
589 return @"tlkshare";
590 }
591
592 + (NSArray<NSString*>*)sqlColumns {
593 return @[@"ckzone", @"uuid", @"senderpeerid", @"recvpeerid", @"recvpubenckey", @"poisoned", @"epoch", @"curve", @"version", @"wrappedkey", @"signature", @"ckrecord"];
594 }
595
596 - (NSDictionary<NSString*,NSString*>*)whereClauseToFindSelf {
597 return @{@"uuid":self.tlkUUID,
598 @"senderpeerid":self.senderPeerID,
599 @"recvpeerid":self.receiver.peerID,
600 @"ckzone":self.zoneID.zoneName,
601 };
602 }
603
604 - (NSDictionary<NSString*,NSString*>*)sqlValues {
605 return @{@"uuid": self.tlkUUID,
606 @"senderpeerid": self.senderPeerID,
607 @"recvpeerid": self.receiver.peerID,
608 @"recvpubenckey": CKKSNilToNSNull([self.receiver.publicEncryptionKey.keyData base64EncodedStringWithOptions:0]),
609 @"poisoned": [NSString stringWithFormat:@"%ld", (long)self.poisoned],
610 @"epoch": [NSString stringWithFormat:@"%ld", (long)self.epoch],
611 @"curve": [NSString stringWithFormat:@"%ld", (long)self.curve],
612 @"version": [NSString stringWithFormat:@"%ld", (long)self.version],
613 @"wrappedkey": CKKSNilToNSNull([self.wrappedTLK base64EncodedStringWithOptions:0]),
614 @"signature": CKKSNilToNSNull([self.signature base64EncodedStringWithOptions:0]),
615 @"ckzone": CKKSNilToNSNull(self.zoneID.zoneName),
616 @"ckrecord": CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]),
617 };
618 }
619
620 + (instancetype)fromDatabaseRow:(NSDictionary<NSString*,NSString*>*)row {
621 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName: row[@"ckzone"] ownerName:CKCurrentUserDefaultName];
622
623 SFEllipticCurve curve = (SFEllipticCurve)[row[@"curve"] integerValue]; // TODO: sanitize
624 SecCKKSTLKShareVersion version = (SecCKKSTLKShareVersion)[row[@"version"] integerValue]; // TODO: sanitize
625
626 NSData* keydata = CKKSUnbase64NullableString(row[@"recvpubenckey"]);
627 NSError* error = nil;
628 SFECPublicKey* receiverPublicKey = keydata ? [[SFECPublicKey alloc] initWithData:keydata
629 specifier:[[SFECKeySpecifier alloc] initWithCurve:curve]
630 error:&error] : nil;
631
632 if(error) {
633 ckkserror("ckksshare", zoneID, "Couldn't make public key from data: %@", error);
634 receiverPublicKey = nil;
635 }
636
637 return [[CKKSTLKShare alloc] initForKey:row[@"uuid"]
638 senderPeerID:row[@"senderpeerid"]
639 recieverPeerID:row[@"recvpeerid"]
640 receiverEncPublicKey:receiverPublicKey
641 curve:curve
642 version:version
643 epoch:[row[@"epoch"] integerValue]
644 poisoned:[row[@"poisoned"] integerValue]
645 wrappedKey:CKKSUnbase64NullableString(row[@"wrappedkey"])
646 signature:CKKSUnbase64NullableString(row[@"signature"])
647 zoneID:zoneID
648 encodedCKRecord:CKKSUnbase64NullableString(row[@"ckrecord"])
649 ];
650 }
651
652 @end
653
654 #endif // OCTAGON