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 <Foundation/NSKeyedArchiver_Private.h>
28 #import "keychain/ckks/CKKSTLKShare.h"
29 #import "keychain/ckks/CKKSPeer.h"
30 #import "keychain/ckks/CloudKitCategories.h"
32 #import <SecurityFoundation/SFKey.h>
33 #import <SecurityFoundation/SFEncryptionOperation.h>
34 #import <SecurityFoundation/SFSigningOperation.h>
35 #import <SecurityFoundation/SFDigestOperation.h>
37 @interface CKKSTLKShare ()
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
51 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
52 encodedCKRecord:encodedCKRecord
59 _senderPeerID = sender.peerID;
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
80 if((self = [super initWithCKRecordType:SecCKRecordTLKShareType
81 encodedCKRecord:encodedCKRecord
84 _senderPeerID = senderPeerID;
86 _receiver = [[CKKSSOSPeer alloc] initWithSOSPeerID:receiverPeerID encryptionPublicKey:publicKey signingPublicKey:nil];
93 _wrappedTLK = wrappedKey;
94 _signature = signature;
99 - (NSString*)description {
100 return [NSString stringWithFormat:@"<CKKSTLKShare(%@): recv:%@ send:%@>", self.tlkUUID, self.receiver, self.senderPeerID];
103 - (NSData*)wrap:(CKKSKey*)key publicKey:(SFECPublicKey*)receiverPublicKey error:(NSError* __autoreleasing *)error {
104 NSData* plaintext = [key serializeAsProtobuf:error];
109 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
110 SFIESCiphertext* ciphertext = [sfieso encrypt:plaintext withKey:receiverPublicKey error:error];
112 // Now use NSCoding to turn the ciphertext into something transportable
113 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
114 [ciphertext encodeWithCoder:archiver];
116 return archiver.encodedData;
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];
125 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
127 NSError* localerror = nil;
128 NSData* plaintext = [sfieso decrypt:ciphertext withKey:localPeer.encryptionKey error:&localerror];
129 if(!plaintext || localerror) {
136 return [CKKSKey loadFromProtobuf:plaintext error:error];
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];
146 uint64_t version = OSSwapHostToLittleConstInt64(self.version);
147 [dataToSign appendBytes:&version length:sizeof(version)];
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]];
154 [dataToSign appendData:self.wrappedTLK];
156 uint64_t curve = OSSwapHostToLittleConstInt64(self.curve);
157 [dataToSign appendBytes:&curve length:sizeof(curve)];
159 uint64_t epoch = OSSwapHostToLittleConstInt64(self.epoch);
160 [dataToSign appendBytes:&epoch length:sizeof(epoch)];
162 uint64_t poisoned = OSSwapHostToLittleConstInt64(self.poisoned);
163 [dataToSign appendBytes:&poisoned length:sizeof(poisoned)];
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;
169 NSMutableDictionary<NSString*,id>* extraData = [NSMutableDictionary dictionary];
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.
186 if([key hasPrefix:@"server_"]) {
187 // Ignore all fields prefixed by "server_"
191 extraData[key] = record[key];
194 NSArray* extraKeys = [[extraData allKeys] sortedArrayUsingSelector:@selector(compare:)];
195 for(NSString* extraKey in extraKeys) {
196 id obj = extraData[extraKey];
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]]) {
209 uint64_t n64 = OSSwapHostToLittleConstInt64([obj unsignedLongLongValue]);
210 [dataToSign appendBytes:&n64 length:sizeof(n64)];
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]];
224 NSData* data = [self dataForSigning];
225 SFSignedData* signedData = [xso sign:data withKey:signingKey error:error];
227 return signedData.signature;
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);
234 *error = [NSError errorWithDomain:CKKSErrorDomain
235 code:CKKSNoSigningKey
236 description:[NSString stringWithFormat:@"Peer(%@) has no signing key", peer]];
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];
246 bool ret = [xso verify:signedData withKey:peer.publicSigningKey error:error];
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];
258 secerror("ckksshare: signature didn't verify for %@ %@: %@", self, peer, localerror);
259 lastVerificationError = localerror;
268 if(lastVerificationError) {
269 *error = lastVerificationError;
271 *error = [NSError errorWithDomain:CKKSErrorDomain
272 code:CKKSNoTrustedTLKShares
273 description:[NSString stringWithFormat:@"No TLK share from %@", self.senderPeerID]];
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];
290 share.receiver = self.receiver;
294 - (BOOL)isEqual:(id)object {
295 if(![object isKindOfClass:[CKKSTLKShare class]]) {
299 CKKSTLKShare* obj = (CKKSTLKShare*) object;
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]) &&
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
324 NSError* localerror = nil;
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
333 secerror("ckksshare: couldn't load old share for %@: %@", key, localerror);
340 CKKSTLKShare* share = [[CKKSTLKShare alloc] init:key
343 curve:SFEllipticCurveNistp384
344 version:SecCKKSTLKShareCurrentVersion
348 encodedCKRecord:oldShare.encodedCKRecord];
350 share.wrappedTLK = [share wrap:key publicKey:receiver.publicEncryptionKey error:&localerror];
352 secerror("ckksshare: couldn't share %@ (wrap failed): %@", key, localerror);
359 share.signature = [share signRecord:sender.signingKey error:&localerror];
361 secerror("ckksshare: couldn't share %@ (signing failed): %@", key, localerror);
371 - (CKKSKey*)recoverTLK:(id<CKKSSelfPeer>)recoverer
372 trustedPeers:(NSSet<id<CKKSPeer>>*)peers
373 error:(NSError* __autoreleasing *)error
375 NSError* localerror = nil;
377 id<CKKSPeer> peer = nil;
378 for(id<CKKSPeer> p in peers) {
379 if([p.peerID isEqualToString: self.senderPeerID]) {
385 localerror = [NSError errorWithDomain:CKKSErrorDomain
386 code:CKKSNoTrustedPeer
387 description:[NSString stringWithFormat:@"No trusted peer signed %@", self]];
394 bool isSigned = [self verifySignature:self.signature verifyingPeer:peer error:error];
399 CKKSKey* tlkTrial = [self unwrapUsing:recoverer error:error];
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]];
417 #pragma mark - Database Operations
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];
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];
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),
447 @"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
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];
457 + (NSArray<CKKSTLKShare*>*)allInZone:(CKRecordZoneID*)zoneID
458 error:(NSError * __autoreleasing *)error {
459 return [self allWhere:@{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)} error:error];
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
476 NSTextCheckingResult* regexmatch = [regex firstMatchInString:recordID.recordName options:0 range:NSMakeRange(0, recordID.recordName.length)];
479 *error = [NSError errorWithDomain:CKKSErrorDomain
480 code:CKKSNoSuchRecord
481 description:[NSString stringWithFormat:@"Couldn't parse '%@' as a TLKShare ID", recordID.recordName]];
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"]];
490 return [self tryFromDatabaseWhere: @{@"uuid":CKKSNilToNSNull(uuid),
491 @"recvpeerid":CKKSNilToNSNull(receiver),
492 @"senderpeerid":CKKSNilToNSNull(sender),
493 @"ckzone": CKKSNilToNSNull(recordID.zoneID.zoneName)} error:error];
496 #pragma mark - CKKSCKRecordHolder methods
498 + (NSString*)ckrecordPrefix {
502 - (NSString*)CKRecordName {
503 return [NSString stringWithFormat:@"tlkshare-%@::%@::%@", self.tlkUUID, self.receiver.peerID, self.senderPeerID];
506 - (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID {
507 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
509 exceptionWithName:@"WrongCKRecordNameException"
510 reason:[NSString stringWithFormat: @"CKRecord name (%@) was not %@", record.recordID.recordName, [self CKRecordName]]
513 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
515 exceptionWithName:@"WrongCKRecordTypeException"
516 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordTLKShareType]
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];
528 record[SecCKRecordParentKeyRefKey] = [[CKReference alloc] initWithRecordID: [[CKRecordID alloc] initWithRecordName: self.tlkUUID zoneID: zoneID]
529 action: CKReferenceActionValidate];
531 record[SecCKRecordWrappedKeyKey] = [self.wrappedTLK base64EncodedStringWithOptions:0];
532 record[SecCKRecordSignature] = [self.signature base64EncodedStringWithOptions:0];
537 - (bool)matchesCKRecord:(CKRecord*)record {
538 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
542 if(![record.recordID.recordName isEqualToString: [self CKRecordName]]) {
546 CKKSTLKShare* share = [[CKKSTLKShare alloc] initWithCKRecord:record];
547 return [self isEqual: share];
550 - (void)setFromCKRecord: (CKRecord*) record {
551 if(![record.recordType isEqualToString: SecCKRecordTLKShareType]) {
553 exceptionWithName:@"WrongCKRecordTypeException"
554 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordDeviceStateType]
558 [self setStoredCKRecord:record];
560 self.senderPeerID = record[SecCKRecordSenderPeerID];
561 self.curve = [record[SecCKRecordCurve] longValue]; // TODO: sanitize
562 self.version = [record[SecCKRecordVersion] longValue];
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]
571 ckkserror("ckksshare", record.recordID.zoneID, "Couldn't make public key from data: %@", error);
572 receiverPublicKey = nil;
575 self.receiver = [[CKKSSOSPeer alloc] initWithSOSPeerID:record[SecCKRecordReceiverPeerID] encryptionPublicKey:receiverPublicKey signingPublicKey:nil];
577 self.epoch = [record[SecCKRecordEpoch] longValue];
578 self.poisoned = [record[SecCKRecordPoisoned] longValue];
580 self.tlkUUID = ((CKReference*)record[SecCKRecordParentKeyRefKey]).recordID.recordName;
582 self.wrappedTLK = CKKSUnbase64NullableString(record[SecCKRecordWrappedKeyKey]);
583 self.signature = CKKSUnbase64NullableString(record[SecCKRecordSignature]);
586 #pragma mark - CKKSSQLDatabaseObject methods
588 + (NSString*)sqlTable {
592 + (NSArray<NSString*>*)sqlColumns {
593 return @[@"ckzone", @"uuid", @"senderpeerid", @"recvpeerid", @"recvpubenckey", @"poisoned", @"epoch", @"curve", @"version", @"wrappedkey", @"signature", @"ckrecord"];
596 - (NSDictionary<NSString*,NSString*>*)whereClauseToFindSelf {
597 return @{@"uuid":self.tlkUUID,
598 @"senderpeerid":self.senderPeerID,
599 @"recvpeerid":self.receiver.peerID,
600 @"ckzone":self.zoneID.zoneName,
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]),
620 + (instancetype)fromDatabaseRow:(NSDictionary<NSString*,NSString*>*)row {
621 CKRecordZoneID* zoneID = [[CKRecordZoneID alloc] initWithZoneName: row[@"ckzone"] ownerName:CKCurrentUserDefaultName];
623 SFEllipticCurve curve = (SFEllipticCurve)[row[@"curve"] integerValue]; // TODO: sanitize
624 SecCKKSTLKShareVersion version = (SecCKKSTLKShareVersion)[row[@"version"] integerValue]; // TODO: sanitize
626 NSData* keydata = CKKSUnbase64NullableString(row[@"recvpubenckey"]);
627 NSError* error = nil;
628 SFECPublicKey* receiverPublicKey = keydata ? [[SFECPublicKey alloc] initWithData:keydata
629 specifier:[[SFECKeySpecifier alloc] initWithCurve:curve]
633 ckkserror("ckksshare", zoneID, "Couldn't make public key from data: %@", error);
634 receiverPublicKey = nil;
637 return [[CKKSTLKShare alloc] initForKey:row[@"uuid"]
638 senderPeerID:row[@"senderpeerid"]
639 recieverPeerID:row[@"recvpeerid"]
640 receiverEncPublicKey:receiverPublicKey
643 epoch:[row[@"epoch"] integerValue]
644 poisoned:[row[@"poisoned"] integerValue]
645 wrappedKey:CKKSUnbase64NullableString(row[@"wrappedkey"])
646 signature:CKKSUnbase64NullableString(row[@"signature"])
648 encodedCKRecord:CKKSUnbase64NullableString(row[@"ckrecord"])