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