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@
26 #import "keychain/ckks/CKKSTLKShare.h"
27 #import "keychain/ckks/CKKSPeer.h"
28 #import "keychain/ckks/CloudKitCategories.h"
30 #import <SecurityFoundation/SFKey.h>
31 #import <SecurityFoundation/SFEncryptionOperation.h>
32 #import <SecurityFoundation/SFSigningOperation.h>
33 #import <SecurityFoundation/SFDigestOperation.h>
35 @interface CKKSTLKShare ()
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
49 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
50 encodedCKRecord:encodedCKRecord
57 _senderPeerID = sender.peerID;
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
78 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
79 encodedCKRecord:encodedCKRecord
82 _senderPeerID = senderPeerID;
84 _receiver = [[CKKSSOSPeer alloc] initWithSOSPeerID:receiverPeerID encryptionPublicKey:publicKey signingPublicKey:nil];
91 _wrappedTLK = wrappedKey;
92 _signature = signature;
97 - (NSString*)description {
98 return [NSString stringWithFormat:@"<CKKSTLKShare(%@): recv:%@ send:%@>", self.tlkUUID, self.receiver.peerID, self.senderPeerID];
101 - (NSData*)wrap:(CKKSKey*)key publicKey:(SFECPublicKey*)receiverPublicKey error:(NSError* __autoreleasing *)error {
102 NSData* plaintext = [key serializeAsProtobuf:error];
107 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
108 SFIESCiphertext* ciphertext = [sfieso encrypt:plaintext withKey:receiverPublicKey error:error];
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];
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];
126 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
128 NSError* localerror = nil;
129 NSData* plaintext = [sfieso decrypt:ciphertext withKey:localPeer.encryptionKey error:&localerror];
130 if(!plaintext || localerror) {
137 return [CKKSKey loadFromProtobuf:plaintext error:error];
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];
147 uint64_t version = OSSwapHostToLittleConstInt64(self.version);
148 [dataToSign appendBytes:&version length:sizeof(version)];
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]];
155 [dataToSign appendData:self.wrappedTLK];
157 uint64_t curve = OSSwapHostToLittleConstInt64(self.curve);
158 [dataToSign appendBytes:&curve length:sizeof(curve)];
160 uint64_t epoch = OSSwapHostToLittleConstInt64(self.epoch);
161 [dataToSign appendBytes:&epoch length:sizeof(epoch)];
163 uint64_t poisoned = OSSwapHostToLittleConstInt64(self.poisoned);
164 [dataToSign appendBytes:&poisoned length:sizeof(poisoned)];
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;
170 NSMutableDictionary<NSString*,id>* extraData = [NSMutableDictionary dictionary];
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.
187 if([key hasPrefix:@"server_"]) {
188 // Ignore all fields prefixed by "server_"
192 extraData[key] = record[key];
195 NSArray* extraKeys = [[extraData allKeys] sortedArrayUsingSelector:@selector(compare:)];
196 for(NSString* extraKey in extraKeys) {
197 id obj = extraData[extraKey];
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]]) {
210 uint64_t n64 = OSSwapHostToLittleConstInt64([obj unsignedLongLongValue]);
211 [dataToSign appendBytes:&n64 length:sizeof(n64)];
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]];
225 NSData* data = [self dataForSigning];
226 SFSignedData* signedData = [xso sign:data withKey:signingKey error:error];
228 return signedData.signature;
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];
237 bool ret = [xso verify:signedData withKey:peer.publicSigningKey error:error];
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];
252 share.receiver = self.receiver;
256 - (BOOL)isEqual:(id)object {
257 if(![object isKindOfClass:[CKKSTLKShare class]]) {
261 CKKSTLKShare* obj = (CKKSTLKShare*) object;
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]) &&
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
286 NSError* localerror = nil;
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
295 secerror("ckksshare: couldn't load old share for %@: %@", key, localerror);
302 CKKSTLKShare* share = [[CKKSTLKShare alloc] init:key
305 curve:SFEllipticCurveNistp384
306 version:SecCKKSTLKShareCurrentVersion
310 encodedCKRecord:oldShare.encodedCKRecord];
312 share.wrappedTLK = [share wrap:key publicKey:receiver.publicEncryptionKey error:&localerror];
314 secerror("ckksshare: couldn't share %@ (wrap failed): %@", key, localerror);
321 share.signature = [share signRecord:sender.signingKey error:&localerror];
323 secerror("ckksshare: couldn't share %@ (signing failed): %@", key, localerror);
333 - (CKKSKey*)recoverTLK:(id<CKKSSelfPeer>)recoverer
334 trustedPeers:(NSSet<id<CKKSPeer>>*)peers
335 error:(NSError* __autoreleasing *)error
337 NSError* localerror = nil;
339 id<CKKSPeer> peer = nil;
340 for(id<CKKSPeer> p in peers) {
341 if([p.peerID isEqualToString: self.senderPeerID]) {
347 localerror = [NSError errorWithDomain:CKKSErrorDomain
348 code:CKKSNoTrustedPeer
349 description:[NSString stringWithFormat:@"No trusted peer signed %@", self]];
356 bool isSigned = [self verifySignature:self.signature verifyingPeer:peer error:error];
361 CKKSKey* tlkTrial = [self unwrapUsing:recoverer error:error];
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]];
379 #pragma mark - Database Operations
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];
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];
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),
409 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
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];
419 + (NSArray<CKKSTLKShare*>*)allInZone:(CKRecordZoneID*)zoneID
420 error:(NSError * __autoreleasing *)error {
421 return [self allWhere:@{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
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
438 NSTextCheckingResult* regexmatch = [regex firstMatchInString:recordID.recordName options:0 range:NSMakeRange(0, recordID.recordName.length)];
441 *error = [NSError errorWithDomain:CKKSErrorDomain
442 code:CKKSNoSuchRecord
443 description:[NSString stringWithFormat:@"Couldn't parse '%@' as a TLKShare ID", recordID.recordName]];
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"]];
452 return [self tryFromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
453 @"recvpeerid":CKKSNilToNSNull(receiver),
454 @"senderpeerid":CKKSNilToNSNull(sender),
455 @"ckzone": CKKSNilToNSNull(recordID.zoneID.zoneName)} error:error];
458 #pragma mark - CKKSCKRecordHolder methods
460 + (NSString*)ckrecordPrefix {
464 - (NSString*)CKRecordName {
465 return [NSString stringWithFormat:@"tlkshare-%@::%@::%@", self.tlkUUID, self.receiver.peerID, self.senderPeerID];
468 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID {
469 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
471 exceptionWithName:@"WrongCKRecordNameException"
472 reason:[NSString stringWithFormat: @"CKRecord name (%@) was not %@", record.recordID.recordName, [self CKRecordName]]
475 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
477 exceptionWithName:@"WrongCKRecordTypeException"
478 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordTLKShareType]
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];
490 record[SecCKRecordParentKeyRefKey] = [[CKReference alloc] initWithRecordID: [[CKRecordID alloc] initWithRecordName: self.tlkUUID zoneID: zoneID]
491 action: CKReferenceActionValidate];
493 record[SecCKRecordWrappedKeyKey] = [self.wrappedTLK base64EncodedStringWithOptions:0];
494 record[SecCKRecordSignature] = [self.signature base64EncodedStringWithOptions:0];
499 - (bool)matchesCKRecord:(CKRecord*)record {
500 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
504 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
508 CKKSTLKShare* share = [[CKKSTLKShare alloc] initWithCKRecord:record];
509 return [self isEqual: share];
512 - (void)setFromCKRecord: (CKRecord*) record {
513 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
515 exceptionWithName:@"WrongCKRecordTypeException"
516 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordDeviceStateType]
520 [self setStoredCKRecord:record];
522 self.senderPeerID = record[SecCKRecordSenderPeerID];
523 self.curve = [record[SecCKRecordCurve] longValue]; // TODO: sanitize
524 self.version = [record[SecCKRecordVersion] longValue];
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]
533 ckkserror("ckksshare", record.recordID.zoneID, "Couldn't make public key from data: %@", error);
534 receiverPublicKey = nil;
537 self.receiver = [[CKKSSOSPeer alloc] initWithSOSPeerID:record[SecCKRecordReceiverPeerID] encryptionPublicKey:receiverPublicKey signingPublicKey:nil];
539 self.epoch = [record[SecCKRecordEpoch] longValue];
540 self.poisoned = [record[SecCKRecordPoisoned] longValue];
542 self.tlkUUID = ((CKReference*)record[SecCKRecordParentKeyRefKey]).recordID.recordName;
544 self.wrappedTLK = CKKSUnbase64NullableString(record[SecCKRecordWrappedKeyKey]);
545 self.signature = CKKSUnbase64NullableString(record[SecCKRecordSignature]);
548 #pragma mark - CKKSSQLDatabaseObject methods
550 + (NSString*)sqlTable {
554 + (NSArray<NSString*>*)sqlColumns {
555 return @[@"ckzone", @"uuid", @"senderpeerid", @"recvpeerid", @"recvpubenckey", @"poisoned", @"epoch", @"curve", @"version", @"wrappedkey", @"signature", @"ckrecord"];
558 - (NSDictionary<NSString*,NSString*>*)whereClauseToFindSelf {
559 return @{@"uuid":self.tlkUUID,
560 @"senderpeerid":self.senderPeerID,
561 @"recvpeerid":self.receiver.peerID,
562 @"ckzone":self.zoneID.zoneName,
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]),
582 + (instancetype)fromDatabaseRow:(NSDictionary<NSString*,NSString*>*)row {
583 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName: row[@"ckzone"] ownerName:CKCurrentUserDefaultName];
585 SFEllipticCurve curve = (SFEllipticCurve)[row[@"curve"] integerValue]; // TODO: sanitize
586 SecCKKSTLKShareVersion version = (SecCKKSTLKShareVersion)[row[@"version"] integerValue]; // TODO: sanitize
588 NSData* keydata = CKKSUnbase64NullableString(row[@"recvpubenckey"]);
589 NSError* error = nil;
590 SFECPublicKey* receiverPublicKey = keydata ? [[SFECPublicKey alloc] initWithData:keydata
591 specifier:[[SFECKeySpecifier alloc] initWithCurve:curve]
595 ckkserror("ckksshare", zoneID, "Couldn't make public key from data: %@", error);
596 receiverPublicKey = nil;
599 return [[CKKSTLKShare alloc] initForKey:row[@"uuid"]
600 senderPeerID:row[@"senderpeerid"]
601 recieverPeerID:row[@"recvpeerid"]
602 receiverEncPublicKey:receiverPublicKey
605 epoch:[row[@"epoch"] integerValue]
606 poisoned:[row[@"poisoned"] integerValue]
607 wrappedKey:CKKSUnbase64NullableString(row[@"wrappedkey"])
608 signature:CKKSUnbase64NullableString(row[@"signature"])
610 encodedCKRecord:CKKSUnbase64NullableString(row[@"ckrecord"])