]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSTLKShare.m
Security-58286.31.2.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 "keychain/ckks/CKKSTLKShare.h"
27 #import "keychain/ckks/CKKSPeer.h"
28 #import "keychain/ckks/CloudKitCategories.h"
29
30 #import <SecurityFoundation/SFKey.h>
31 #import <SecurityFoundation/SFEncryptionOperation.h>
32 #import <SecurityFoundation/SFSigningOperation.h>
33 #import <SecurityFoundation/SFDigestOperation.h>
34
35 @interface CKKSTLKShare ()
36 @end
37
38 @implementation CKKSTLKShare
39 -(instancetype)init:(CKKSKey*)key
40 sender:(id<CKKSSelfPeer>)sender
41 receiver:(id<CKKSPeer>)receiver
42 curve:(SFEllipticCurve)curve
43 version:(SecCKKSTLKShareVersion)version
44 epoch:(NSInteger)epoch
45 poisoned:(NSInteger)poisoned
46 zoneID:(CKRecordZoneID*)zoneID
47 encodedCKRecord:(NSData*)encodedCKRecord
48 {
49 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
50 encodedCKRecord:encodedCKRecord
51 zoneID:zoneID])) {
52 _curve = curve;
53 _version = version;
54 _tlkUUID = key.uuid;
55
56 _receiver = receiver;
57 _senderPeerID = sender.peerID;
58
59 _epoch = epoch;
60 _poisoned = poisoned;
61 }
62 return self;
63 }
64
65 - (instancetype)initForKey:(NSString*)tlkUUID
66 senderPeerID:(NSString*)senderPeerID
67 recieverPeerID:(NSString*)receiverPeerID
68 receiverEncPublicKey:(SFECPublicKey*)publicKey
69 curve:(SFEllipticCurve)curve
70 version:(SecCKKSTLKShareVersion)version
71 epoch:(NSInteger)epoch
72 poisoned:(NSInteger)poisoned
73 wrappedKey:(NSData*)wrappedKey
74 signature:(NSData*)signature
75 zoneID:(CKRecordZoneID*)zoneID
76 encodedCKRecord:(NSData*)encodedCKRecord
77 {
78 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
79 encodedCKRecord:encodedCKRecord
80 zoneID:zoneID])) {
81 _tlkUUID = tlkUUID;
82 _senderPeerID = senderPeerID;
83
84 _receiver = [[CKKSSOSPeer alloc] initWithSOSPeerID:receiverPeerID encryptionPublicKey:publicKey signingPublicKey:nil];
85
86 _curve = curve;
87 _version = version;
88 _epoch = epoch;
89 _poisoned = poisoned;
90
91 _wrappedTLK = wrappedKey;
92 _signature = signature;
93 }
94 return self;
95 }
96
97 - (NSString*)description {
98 return [NSString stringWithFormat:@"<CKKSTLKShare(%@): recv:%@ send:%@>", self.tlkUUID, self.receiver.peerID, self.senderPeerID];
99 }
100
101 - (NSData*)wrap:(CKKSKey*)key publicKey:(SFECPublicKey*)receiverPublicKey error:(NSError* __autoreleasing *)error {
102 NSData* plaintext = [key serializeAsProtobuf:error];
103 if(!plaintext) {
104 return nil;
105 }
106
107 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
108 SFIESCiphertext* ciphertext = [sfieso encrypt:plaintext withKey:receiverPublicKey error:error];
109
110 // Now use NSCoding to turn the ciphertext into something transportable
111 NSMutableData* data = [NSMutableData data];
112 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
113 [ciphertext encodeWithCoder:archiver];
114 [archiver finishEncoding];
115
116 return data;
117 }
118
119 - (CKKSKey*)unwrapUsing:(id<CKKSSelfPeer>)localPeer error:(NSError * __autoreleasing *)error {
120 // Unwrap the ciphertext using NSSecureCoding
121 NSKeyedUnarchiver *coder = [[NSKeyedUnarchiver alloc] initForReadingWithData:self.wrappedTLK];
122 coder.requiresSecureCoding = YES;
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 // TODO: the digest operation can't be changed, as we don't have a good way of communicating it, like self.curve
233 SFEC_X962SigningOperation* xso = [[SFEC_X962SigningOperation alloc] initWithKeySpecifier:[[SFECKeySpecifier alloc] initWithCurve:self.curve]
234 digestOperation:[[SFSHA256DigestOperation alloc] init]];
235 SFSignedData* signedData = [[SFSignedData alloc] initWithData:[self dataForSigning] signature:signature];
236
237 bool ret = [xso verify:signedData withKey:peer.publicSigningKey error:error];
238 return ret;
239 }
240
241 - (instancetype)copyWithZone:(NSZone *)zone {
242 CKKSTLKShare* share = [[[self class] allocWithZone:zone] init];
243 share.curve = self.curve;
244 share.version = self.version;
245 share.tlkUUID = [self.tlkUUID copy];
246 share.senderPeerID = [self.senderPeerID copy];
247 share.epoch = self.epoch;
248 share.poisoned = self.poisoned;
249 share.wrappedTLK = [self.wrappedTLK copy];
250 share.signature = [self.signature copy];
251
252 share.receiver = self.receiver;
253 return share;
254 }
255
256 - (BOOL)isEqual:(id)object {
257 if(![object isKindOfClass:[CKKSTLKShare class]]) {
258 return NO;
259 }
260
261 CKKSTLKShare* obj = (CKKSTLKShare*) object;
262
263 // Note that for purposes of CKKSTLK equality, we only care about the receiver's peer ID and publicEncryptionKey
264 // <rdar://problem/34897551> SFKeys should support [isEqual:]
265 return ([self.tlkUUID isEqualToString:obj.tlkUUID] &&
266 [self.zoneID isEqual: obj.zoneID] &&
267 [self.senderPeerID isEqualToString:obj.senderPeerID] &&
268 ((self.receiver.peerID == nil && obj.receiver.peerID == nil) || [self.receiver.peerID isEqual: obj.receiver.peerID]) &&
269 ((self.receiver.publicEncryptionKey == nil && obj.receiver.publicEncryptionKey == nil)
270 || [self.receiver.publicEncryptionKey.keyData isEqual: obj.receiver.publicEncryptionKey.keyData]) &&
271 self.epoch == obj.epoch &&
272 self.curve == obj.curve &&
273 self.poisoned == obj.poisoned &&
274 ((self.wrappedTLK == nil && obj.wrappedTLK == nil) || [self.wrappedTLK isEqual: obj.wrappedTLK]) &&
275 ((self.signature == nil && obj.signature == nil) || [self.signature isEqual: obj.signature]) &&
276 true) ? YES : NO;
277 }
278
279 + (CKKSTLKShare*)share:(CKKSKey*)key
280 as:(id<CKKSSelfPeer>)sender
281 to:(id<CKKSPeer>)receiver
282 epoch:(NSInteger)epoch
283 poisoned:(NSInteger)poisoned
284 error:(NSError* __autoreleasing *)error
285 {
286 NSError* localerror = nil;
287
288 // Load any existing TLK Share, so we can update it
289 CKKSTLKShare* oldShare = [CKKSTLKShare tryFromDatabase:key.uuid
290 receiverPeerID:receiver.peerID
291 senderPeerID:sender.peerID
292 zoneID:key.zoneID
293 error:&localerror];
294 if(localerror) {
295 secerror("ckksshare: couldn't load old share for %@: %@", key, localerror);
296 if(error) {
297 *error = localerror;
298 }
299 return nil;
300 }
301
302 CKKSTLKShare* share = [[CKKSTLKShare alloc] init:key
303 sender:sender
304 receiver:receiver
305 curve:SFEllipticCurveNistp384
306 version:SecCKKSTLKShareCurrentVersion
307 epoch:epoch
308 poisoned:poisoned
309 zoneID:key.zoneID
310 encodedCKRecord:oldShare.encodedCKRecord];
311
312 share.wrappedTLK = [share wrap:key publicKey:receiver.publicEncryptionKey error:&localerror];
313 if(localerror) {
314 secerror("ckksshare: couldn't share %@ (wrap failed): %@", key, localerror);
315 if(error) {
316 *error = localerror;
317 }
318 return nil;
319 }
320
321 share.signature = [share signRecord:sender.signingKey error:&localerror];
322 if(localerror) {
323 secerror("ckksshare: couldn't share %@ (signing failed): %@", key, localerror);
324 if(error) {
325 *error = localerror;
326 }
327 return nil;
328 }
329
330 return share;
331 }
332
333 - (CKKSKey*)recoverTLK:(id<CKKSSelfPeer>)recoverer
334 trustedPeers:(NSSet<id<CKKSPeer>>*)peers
335 error:(NSError* __autoreleasing *)error
336 {
337 NSError* localerror = nil;
338
339 id<CKKSPeer> peer = nil;
340 for(id<CKKSPeer> p in peers) {
341 if([p.peerID isEqualToString: self.senderPeerID]) {
342 peer = p;
343 }
344 }
345
346 if(!peer) {
347 localerror = [NSError errorWithDomain:CKKSErrorDomain
348 code:CKKSNoTrustedPeer
349 description:[NSString stringWithFormat:@"No trusted peer signed %@", self]];
350 if(error) {
351 *error = localerror;
352 }
353 return nil;
354 }
355
356 bool isSigned = [self verifySignature:self.signature verifyingPeer:peer error:error];
357 if(!isSigned) {
358 return nil;
359 }
360
361 CKKSKey* tlkTrial = [self unwrapUsing:recoverer error:error];
362 if(!tlkTrial) {
363 return nil;
364 }
365
366 if(![self.tlkUUID isEqualToString:tlkTrial.uuid]) {
367 localerror = [NSError errorWithDomain:CKKSErrorDomain
368 code:CKKSDataMismatch
369 description:[NSString stringWithFormat:@"Signed UUID doesn't match unsigned UUID for %@", self]];
370 if(error) {
371 *error = localerror;
372 }
373 return nil;
374 }
375
376 return tlkTrial;
377 }
378
379 #pragma mark - Database Operations
380
381 + (instancetype)fromDatabase:(NSString*)uuid
382 receiverPeerID:(NSString*)receiverPeerID
383 senderPeerID:(NSString*)senderPeerID
384 zoneID:(CKRecordZoneID*)zoneID
385 error:(NSError * __autoreleasing *)error {
386 return [self fromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
387 @"recvpeerid":CKKSNilToNSNull(receiverPeerID),
388 @"senderpeerid":CKKSNilToNSNull(senderPeerID),
389 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
390 }
391
392 + (instancetype)tryFromDatabase:(NSString*)uuid
393 receiverPeerID:(NSString*)receiverPeerID
394 senderPeerID:(NSString*)senderPeerID
395 zoneID:(CKRecordZoneID*)zoneID
396 error:(NSError * __autoreleasing *)error {
397 return [self tryFromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
398 @"recvpeerid":CKKSNilToNSNull(receiverPeerID),
399 @"senderpeerid":CKKSNilToNSNull(senderPeerID),
400 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
401 }
402
403 + (NSArray<CKKSTLKShare*>*)allFor:(NSString*)receiverPeerID
404 keyUUID:(NSString*)uuid
405 zoneID:(CKRecordZoneID*)zoneID
406 error:(NSError * __autoreleasing *)error {
407 return [self allWhere:@{@"recvpeerid":CKKSNilToNSNull(receiverPeerID),
408 @"uuid":uuid,
409 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
410 }
411
412 + (NSArray<CKKSTLKShare*>*)allForUUID:(NSString*)uuid
413 zoneID:(CKRecordZoneID*)zoneID
414 error:(NSError * __autoreleasing *)error {
415 return [self allWhere:@{@"uuid":CKKSNilToNSNull(uuid),
416 @"ckzone":CKKSNilToNSNull(zoneID.zoneName)} error:error];
417 }
418
419 + (NSArray<CKKSTLKShare*>*)allInZone:(CKRecordZoneID*)zoneID
420 error:(NSError * __autoreleasing *)error {
421 return [self allWhere:@{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
422 }
423
424 + (instancetype)tryFromDatabaseFromCKRecordID:(CKRecordID*)recordID
425 error:(NSError * __autoreleasing *)error {
426 // Welp. Try to parse!
427 NSError *localerror = NULL;
428 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^tlkshare-(?<uuid>[0-9A-Fa-f-]*)::(?<receiver>.*)::(?<sender>.*)$"
429 options:NSRegularExpressionCaseInsensitive
430 error:&localerror];
431 if(localerror) {
432 if(error) {
433 *error = localerror;
434 }
435 return nil;
436 }
437
438 NSTextCheckingResult* regexmatch = [regex firstMatchInString:recordID.recordName options:0 range:NSMakeRange(0, recordID.recordName.length)];
439 if(!regexmatch) {
440 if(error) {
441 *error = [NSError errorWithDomain:CKKSErrorDomain
442 code:CKKSNoSuchRecord
443 description:[NSString stringWithFormat:@"Couldn't parse '%@' as a TLKShare ID", recordID.recordName]];
444 }
445 return nil;
446 }
447
448 NSString* uuid = [recordID.recordName substringWithRange:[regexmatch rangeWithName:@"uuid"]];
449 NSString* receiver = [recordID.recordName substringWithRange:[regexmatch rangeWithName:@"receiver"]];
450 NSString* sender = [recordID.recordName substringWithRange:[regexmatch rangeWithName:@"sender"]];
451
452 return [self tryFromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
453 @"recvpeerid":CKKSNilToNSNull(receiver),
454 @"senderpeerid":CKKSNilToNSNull(sender),
455 @"ckzone": CKKSNilToNSNull(recordID.zoneID.zoneName)} error:error];
456 }
457
458 #pragma mark - CKKSCKRecordHolder methods
459
460 + (NSString*)ckrecordPrefix {
461 return @"tlkshare";
462 }
463
464 - (NSString*)CKRecordName {
465 return [NSString stringWithFormat:@"tlkshare-%@::%@::%@", self.tlkUUID, self.receiver.peerID, self.senderPeerID];
466 }
467
468 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID {
469 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
470 @throw [NSException
471 exceptionWithName:@"WrongCKRecordNameException"
472 reason:[NSString stringWithFormat: @"CKRecord name (%@) was not %@", record.recordID.recordName, [self CKRecordName]]
473 userInfo:nil];
474 }
475 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
476 @throw [NSException
477 exceptionWithName:@"WrongCKRecordTypeException"
478 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordTLKShareType]
479 userInfo:nil];
480 }
481
482 record[SecCKRecordSenderPeerID] = self.senderPeerID;
483 record[SecCKRecordReceiverPeerID] = self.receiver.peerID;
484 record[SecCKRecordReceiverPublicEncryptionKey] = [self.receiver.publicEncryptionKey.keyData base64EncodedStringWithOptions:0];
485 record[SecCKRecordCurve] = [NSNumber numberWithUnsignedInteger:(NSUInteger)self.curve];
486 record[SecCKRecordVersion] = [NSNumber numberWithUnsignedInteger:(NSUInteger)self.version];
487 record[SecCKRecordEpoch] = [NSNumber numberWithLong:(long)self.epoch];
488 record[SecCKRecordPoisoned] = [NSNumber numberWithLong:(long)self.poisoned];
489
490 record[SecCKRecordParentKeyRefKey] = [[CKReference alloc] initWithRecordID: [[CKRecordID alloc] initWithRecordName: self.tlkUUID zoneID: zoneID]
491 action: CKReferenceActionValidate];
492
493 record[SecCKRecordWrappedKeyKey] = [self.wrappedTLK base64EncodedStringWithOptions:0];
494 record[SecCKRecordSignature] = [self.signature base64EncodedStringWithOptions:0];
495
496 return record;
497 }
498
499 - (bool)matchesCKRecord:(CKRecord*)record {
500 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
501 return false;
502 }
503
504 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
505 return false;
506 }
507
508 CKKSTLKShare* share = [[CKKSTLKShare alloc] initWithCKRecord:record];
509 return [self isEqual: share];
510 }
511
512 - (void)setFromCKRecord: (CKRecord*) record {
513 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
514 @throw [NSException
515 exceptionWithName:@"WrongCKRecordTypeException"
516 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordDeviceStateType]
517 userInfo:nil];
518 }
519
520 [self setStoredCKRecord:record];
521
522 self.senderPeerID = record[SecCKRecordSenderPeerID];
523 self.curve = [record[SecCKRecordCurve] longValue]; // TODO: sanitize
524 self.version = [record[SecCKRecordVersion] longValue];
525
526 NSData* pubkeydata = CKKSUnbase64NullableString(record[SecCKRecordReceiverPublicEncryptionKey]);
527 NSError* error = nil;
528 SFECPublicKey* receiverPublicKey = pubkeydata ? [[SFECPublicKey alloc] initWithData:pubkeydata
529 specifier:[[SFECKeySpecifier alloc] initWithCurve:self.curve]
530 error:&error] : nil;
531
532 if(error) {
533 ckkserror("ckksshare", record.recordID.zoneID, "Couldn't make public key from data: %@", error);
534 receiverPublicKey = nil;
535 }
536
537 self.receiver = [[CKKSSOSPeer alloc] initWithSOSPeerID:record[SecCKRecordReceiverPeerID] encryptionPublicKey:receiverPublicKey signingPublicKey:nil];
538
539 self.epoch = [record[SecCKRecordEpoch] longValue];
540 self.poisoned = [record[SecCKRecordPoisoned] longValue];
541
542 self.tlkUUID = ((CKReference*)record[SecCKRecordParentKeyRefKey]).recordID.recordName;
543
544 self.wrappedTLK = CKKSUnbase64NullableString(record[SecCKRecordWrappedKeyKey]);
545 self.signature = CKKSUnbase64NullableString(record[SecCKRecordSignature]);
546 }
547
548 #pragma mark - CKKSSQLDatabaseObject methods
549
550 + (NSString*)sqlTable {
551 return @"tlkshare";
552 }
553
554 + (NSArray<NSString*>*)sqlColumns {
555 return @[@"ckzone", @"uuid", @"senderpeerid", @"recvpeerid", @"recvpubenckey", @"poisoned", @"epoch", @"curve", @"version", @"wrappedkey", @"signature", @"ckrecord"];
556 }
557
558 - (NSDictionary<NSString*,NSString*>*)whereClauseToFindSelf {
559 return @{@"uuid":self.tlkUUID,
560 @"senderpeerid":self.senderPeerID,
561 @"recvpeerid":self.receiver.peerID,
562 @"ckzone":self.zoneID.zoneName,
563 };
564 }
565
566 - (NSDictionary<NSString*,NSString*>*)sqlValues {
567 return @{@"uuid": self.tlkUUID,
568 @"senderpeerid": self.senderPeerID,
569 @"recvpeerid": self.receiver.peerID,
570 @"recvpubenckey": CKKSNilToNSNull([self.receiver.publicEncryptionKey.keyData base64EncodedStringWithOptions:0]),
571 @"poisoned": [NSString stringWithFormat:@"%ld", (long)self.poisoned],
572 @"epoch": [NSString stringWithFormat:@"%ld", (long)self.epoch],
573 @"curve": [NSString stringWithFormat:@"%ld", (long)self.curve],
574 @"version": [NSString stringWithFormat:@"%ld", (long)self.version],
575 @"wrappedkey": CKKSNilToNSNull([self.wrappedTLK base64EncodedStringWithOptions:0]),
576 @"signature": CKKSNilToNSNull([self.signature base64EncodedStringWithOptions:0]),
577 @"ckzone": CKKSNilToNSNull(self.zoneID.zoneName),
578 @"ckrecord": CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]),
579 };
580 }
581
582 + (instancetype)fromDatabaseRow:(NSDictionary<NSString*,NSString*>*)row {
583 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName: row[@"ckzone"] ownerName:CKCurrentUserDefaultName];
584
585 SFEllipticCurve curve = (SFEllipticCurve)[row[@"curve"] integerValue]; // TODO: sanitize
586 SecCKKSTLKShareVersion version = (SecCKKSTLKShareVersion)[row[@"version"] integerValue]; // TODO: sanitize
587
588 NSData* keydata = CKKSUnbase64NullableString(row[@"recvpubenckey"]);
589 NSError* error = nil;
590 SFECPublicKey* receiverPublicKey = keydata ? [[SFECPublicKey alloc] initWithData:keydata
591 specifier:[[SFECKeySpecifier alloc] initWithCurve:curve]
592 error:&error] : nil;
593
594 if(error) {
595 ckkserror("ckksshare", zoneID, "Couldn't make public key from data: %@", error);
596 receiverPublicKey = nil;
597 }
598
599 return [[CKKSTLKShare alloc] initForKey:row[@"uuid"]
600 senderPeerID:row[@"senderpeerid"]
601 recieverPeerID:row[@"recvpeerid"]
602 receiverEncPublicKey:receiverPublicKey
603 curve:curve
604 version:version
605 epoch:[row[@"epoch"] integerValue]
606 poisoned:[row[@"poisoned"] integerValue]
607 wrappedKey:CKKSUnbase64NullableString(row[@"wrappedkey"])
608 signature:CKKSUnbase64NullableString(row[@"signature"])
609 zoneID:zoneID
610 encodedCKRecord:CKKSUnbase64NullableString(row[@"ckrecord"])
611 ];
612 }
613
614 @end
615
616 #endif // OCTAGON