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"
28 #import <Foundation/NSKeyedArchiver_Private.h>
29 #import <SecurityFoundation/SFDigestOperation.h>
30 #import <SecurityFoundation/SFEncryptionOperation.h>
31 #import <SecurityFoundation/SFKey.h>
32 #import <SecurityFoundation/SFSigningOperation.h>
34 #import "keychain/categories/NSError+UsefulConstructors.h"
35 #import "keychain/ckks/CKKSPeer.h"
36 #import "keychain/ckks/CloudKitCategories.h"
38 @interface CKKSTLKShare ()
41 @implementation CKKSTLKShare
42 - (instancetype)init:(CKKSKeychainBackedKey*)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
51 if((self = [super init])) {
58 _receiverPeerID = receiver.peerID;
59 _receiverPublicEncryptionKeySPKI = receiver.publicEncryptionKey.keyData;
61 _senderPeerID = sender.peerID;
69 - (instancetype)initForKey:(NSString*)tlkUUID
70 senderPeerID:(NSString*)senderPeerID
71 recieverPeerID:(NSString*)receiverPeerID
72 receiverEncPublicKeySPKI:(NSData* _Nullable)publicKeySPKI
73 curve:(SFEllipticCurve)curve
74 version:(SecCKKSTLKShareVersion)version
75 epoch:(NSInteger)epoch
76 poisoned:(NSInteger)poisoned
77 wrappedKey:(NSData*)wrappedKey
78 signature:(NSData*)signature
79 zoneID:(CKRecordZoneID*)zoneID
81 if((self = [super init])) {
84 _senderPeerID = senderPeerID;
86 _receiverPeerID = receiverPeerID;
87 _receiverPublicEncryptionKeySPKI = publicKeySPKI;
94 _wrappedTLK = wrappedKey;
95 _signature = signature;
100 - (NSString*)description
102 return [NSString stringWithFormat:@"<CKKSTLKShareCore(%@): recv:%@ send:%@>",
108 - (NSData*)wrap:(CKKSKeychainBackedKey*)key
109 publicKey:(SFECPublicKey*)receiverPublicKey
110 error:(NSError* __autoreleasing*)error
112 NSData* plaintext = [key serializeAsProtobuf:error];
117 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
118 SFIESCiphertext* ciphertext =
119 [sfieso encrypt:plaintext withKey:receiverPublicKey error:error];
121 // Now use NSCoding to turn the ciphertext into something transportable
122 NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
123 [ciphertext encodeWithCoder:archiver];
125 return archiver.encodedData;
128 - (CKKSKeychainBackedKey* _Nullable)unwrapUsing:(id<CKKSSelfPeer>)localPeer
129 error:(NSError* __autoreleasing*)error
131 // Unwrap the ciphertext using NSSecureCoding
132 NSKeyedUnarchiver* coder =
133 [[NSKeyedUnarchiver alloc] initForReadingFromData:self.wrappedTLK error:nil];
134 SFIESCiphertext* ciphertext = [[SFIESCiphertext alloc] initWithCoder:coder];
135 [coder finishDecoding];
137 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
139 NSError* localerror = nil;
141 [sfieso decrypt:ciphertext withKey:localPeer.encryptionKey error:&localerror];
142 if(!plaintext || localerror) {
149 return [CKKSKeychainBackedKey loadFromProtobuf:plaintext error:error];
152 // Serialize this record in some format suitable for signing.
153 // This record must serialize exactly the same on the other side for the signature to verify.
154 - (NSData*)dataForSigning:(CKRecord* _Nullable)record
156 // Ideally, we'd put this as DER or some other structured, versioned format.
157 // For now, though, do the straightforward thing and concatenate the fields of interest.
158 NSMutableData* dataToSign = [[NSMutableData alloc] init];
160 uint64_t version = OSSwapHostToLittleConstInt64(self.version);
161 [dataToSign appendBytes:&version length:sizeof(version)];
163 // We only include the peer IDs in the signature; the receiver doesn't care if we signed the receiverPublicKey field;
164 // if it's wrong or doesn't match, the receiver will simply fail to decrypt the encrypted record.
165 [dataToSign appendData:[self.receiverPeerID dataUsingEncoding:NSUTF8StringEncoding]];
166 [dataToSign appendData:[self.senderPeerID dataUsingEncoding:NSUTF8StringEncoding]];
168 [dataToSign appendData:self.wrappedTLK];
170 uint64_t curve = OSSwapHostToLittleConstInt64(self.curve);
171 [dataToSign appendBytes:&curve length:sizeof(curve)];
173 uint64_t epoch = OSSwapHostToLittleConstInt64(self.epoch);
174 [dataToSign appendBytes:&epoch length:sizeof(epoch)];
176 uint64_t poisoned = OSSwapHostToLittleConstInt64(self.poisoned);
177 [dataToSign appendBytes:&poisoned length:sizeof(poisoned)];
179 // If we have a CKRecord passed in, add any unknown fields (that don't start with server_) to the signed data
180 // in sorted order by CKRecord key
182 NSMutableDictionary<NSString*, id>* extraData = [NSMutableDictionary dictionary];
184 for(NSString* key in record.allKeys) {
185 if([key isEqualToString:SecCKRecordSenderPeerID] ||
186 [key isEqualToString:SecCKRecordReceiverPeerID] ||
187 [key isEqualToString:SecCKRecordReceiverPublicEncryptionKey] ||
188 [key isEqualToString:SecCKRecordCurve] || [key isEqualToString:SecCKRecordEpoch] ||
189 [key isEqualToString:SecCKRecordPoisoned] ||
190 [key isEqualToString:SecCKRecordSignature] || [key isEqualToString:SecCKRecordVersion] ||
191 [key isEqualToString:SecCKRecordParentKeyRefKey] ||
192 [key isEqualToString:SecCKRecordWrappedKeyKey]) {
193 // This version of CKKS knows about this data field. Ignore them with prejudice.
197 if([key hasPrefix:@"server_"]) {
198 // Ignore all fields prefixed by "server_"
202 extraData[key] = record[key];
205 NSArray* extraKeys = [[extraData allKeys] sortedArrayUsingSelector:@selector(compare:)];
206 for(NSString* extraKey in extraKeys) {
207 id obj = extraData[extraKey];
209 // Skip CKReferences, NSArray, CLLocation, and CKAsset.
210 if([obj isKindOfClass:[NSString class]]) {
211 [dataToSign appendData:[obj dataUsingEncoding:NSUTF8StringEncoding]];
212 } else if([obj isKindOfClass:[NSData class]]) {
213 [dataToSign appendData:obj];
214 } else if([obj isKindOfClass:[NSDate class]]) {
215 NSISO8601DateFormatter* formatter = [[NSISO8601DateFormatter alloc] init];
216 NSString* str = [formatter stringForObjectValue:obj];
217 [dataToSign appendData:[str dataUsingEncoding:NSUTF8StringEncoding]];
218 } else if([obj isKindOfClass:[NSNumber class]]) {
220 uint64_t n64 = OSSwapHostToLittleConstInt64([obj unsignedLongLongValue]);
221 [dataToSign appendBytes:&n64 length:sizeof(n64)];
229 // Returns the signature, but not the signed data itself;
230 - (NSData* _Nullable)signRecord:(SFECKeyPair*)signingKey
231 ckrecord:(CKRecord* _Nullable)ckrecord
232 error:(NSError* __autoreleasing*)error
234 // TODO: the digest operation can't be changed, as we don't have a good way of communicating it, like self.curve
235 SFEC_X962SigningOperation* xso = [[SFEC_X962SigningOperation alloc]
236 initWithKeySpecifier:[[SFECKeySpecifier alloc] initWithCurve:self.curve]
237 digestOperation:[[SFSHA256DigestOperation alloc] init]];
239 NSData* data = [self dataForSigning:ckrecord];
240 SFSignedData* signedData = [xso sign:data withKey:signingKey error:error];
242 return signedData.signature;
245 - (bool)verifySignature:(NSData*)signature
246 verifyingPeer:(id<CKKSPeer>)peer
247 ckrecord:(CKRecord* _Nullable)ckrecord
248 error:(NSError* __autoreleasing*)error
250 if(!peer.publicSigningKey) {
251 ckkserror("ckksshare", self.zoneID, "no signing key for peer: %@", peer);
254 errorWithDomain:CKKSErrorDomain
255 code:CKKSNoSigningKey
256 description:[NSString stringWithFormat:@"Peer(%@) has no signing key", peer]];
261 // TODO: the digest operation can't be changed, as we don't have a good way of communicating it, like self.curve
262 SFEC_X962SigningOperation* xso = [[SFEC_X962SigningOperation alloc]
263 initWithKeySpecifier:[[SFECKeySpecifier alloc] initWithCurve:self.curve]
264 digestOperation:[[SFSHA256DigestOperation alloc] init]];
265 SFSignedData* signedData =
266 [[SFSignedData alloc] initWithData:[self dataForSigning:ckrecord] signature:signature];
268 bool ret = [xso verify:signedData withKey:peer.publicSigningKey error:error];
272 - (bool)signatureVerifiesWithPeerSet:(NSSet<id<CKKSPeer>>*)peerSet
273 ckrecord:(CKRecord* _Nullable)ckrecord
274 error:(NSError**)error
276 NSError* lastVerificationError = nil;
277 for(id<CKKSPeer> peer in peerSet) {
278 if([peer.peerID isEqualToString:self.senderPeerID]) {
279 // Does the signature verify using this peer?
280 NSError* localerror = nil;
281 bool isSigned = [self verifySignature:self.signature
286 ckkserror("ckksshare", self.zoneID, "signature didn't verify for %@ %@: %@", self, peer, localerror);
287 lastVerificationError = localerror;
296 if(lastVerificationError) {
297 *error = lastVerificationError;
300 errorWithDomain:CKKSErrorDomain
301 code:CKKSNoTrustedTLKShares
302 description:[NSString stringWithFormat:@"No TLK share from %@", self.senderPeerID]];
308 - (instancetype)copyWithZone:(NSZone*)zone
310 CKKSTLKShare* share = [[[self class] allocWithZone:zone] init];
311 share.curve = self.curve;
312 share.version = self.version;
313 share.tlkUUID = [self.tlkUUID copy];
314 share.senderPeerID = [self.senderPeerID copy];
315 share.epoch = self.epoch;
316 share.poisoned = self.poisoned;
317 share.wrappedTLK = [self.wrappedTLK copy];
318 share.signature = [self.signature copy];
320 share.receiverPeerID = [self.receiverPeerID copy];
321 share.receiverPublicEncryptionKeySPKI = [self.receiverPublicEncryptionKeySPKI copy];
326 + (BOOL)supportsSecureCoding {
330 - (void)encodeWithCoder:(nonnull NSCoder*)coder
332 [coder encodeObject:self.zoneID forKey:@"zoneID"];
333 [coder encodeInt64:(int64_t)self.curve forKey:@"curve"];
334 [coder encodeInt64:self.version forKey:@"version"];
335 [coder encodeObject:self.tlkUUID forKey:@"tlkUUID"];
336 [coder encodeObject:self.senderPeerID forKey:@"senderPeerID"];
337 [coder encodeInt64:self.epoch forKey:@"epoch"];
338 [coder encodeInt64:self.poisoned forKey:@"poisoned"];
339 [coder encodeObject:self.wrappedTLK forKey:@"wrappedTLK"];
340 [coder encodeObject:self.signature forKey:@"signature"];
342 [coder encodeObject:self.receiverPeerID forKey:@"receiverPeerID"];
343 [coder encodeObject:self.receiverPublicEncryptionKeySPKI forKey:@"receiverSPKI"];
346 - (nullable instancetype)initWithCoder:(nonnull NSCoder*)decoder
348 if ((self = [super init])) {
349 _zoneID = [decoder decodeObjectOfClass:[CKRecordZoneID class] forKey:@"zoneID"];
350 _curve = (SFEllipticCurve) [decoder decodeInt64ForKey:@"curve"];
351 _version = (SecCKKSTLKShareVersion)[decoder decodeInt64ForKey:@"version"];
352 _tlkUUID = [decoder decodeObjectOfClass:[NSString class] forKey:@"tlkUUID"];
353 _senderPeerID = [decoder decodeObjectOfClass:[NSString class] forKey:@"senderPeerID"];
354 _epoch = (NSInteger)[decoder decodeInt64ForKey:@"epoch"];
355 _poisoned = (NSInteger)[decoder decodeInt64ForKey:@"poisoned"];
356 _wrappedTLK = [decoder decodeObjectOfClass:[NSData class] forKey:@"wrappedTLK"];
357 _signature = [decoder decodeObjectOfClass:[NSData class] forKey:@"signature"];
360 _receiverPeerID = [decoder decodeObjectOfClass:[NSString class] forKey:@"receiverPeerID"];
361 _receiverPublicEncryptionKeySPKI = [decoder decodeObjectOfClass:[NSData class] forKey:@"receiverSPKI"];
366 - (BOOL)isEqual:(id)object
368 if(![object isKindOfClass:[CKKSTLKShare class]]) {
372 CKKSTLKShare* obj = (CKKSTLKShare*)object;
374 return ([self.tlkUUID isEqualToString:obj.tlkUUID] && [self.zoneID isEqual:obj.zoneID] &&
375 [self.senderPeerID isEqualToString:obj.senderPeerID] &&
376 ((self.receiverPeerID == nil && obj.receiverPeerID == nil) ||
377 [self.receiverPeerID isEqual:obj.receiverPeerID]) &&
378 ((self.receiverPublicEncryptionKeySPKI == nil && obj.receiverPublicEncryptionKeySPKI == nil) ||
379 [self.receiverPublicEncryptionKeySPKI isEqual:obj.receiverPublicEncryptionKeySPKI]) &&
380 self.epoch == obj.epoch && self.curve == obj.curve && self.poisoned == obj.poisoned &&
381 ((self.wrappedTLK == nil && obj.wrappedTLK == nil) || [self.wrappedTLK isEqual:obj.wrappedTLK]) &&
382 ((self.signature == nil && obj.signature == nil) || [self.signature isEqual:obj.signature]) && true)
387 + (CKKSTLKShare* _Nullable)share:(CKKSKeychainBackedKey*)key
388 as:(id<CKKSSelfPeer>)sender
389 to:(id<CKKSPeer>)receiver
390 epoch:(NSInteger)epoch
391 poisoned:(NSInteger)poisoned
392 error:(NSError* __autoreleasing*)error
394 NSError* localerror = nil;
395 CKKSTLKShare* share = [[CKKSTLKShare alloc] init:key
398 curve:SFEllipticCurveNistp384
399 version:SecCKKSTLKShareCurrentVersion
405 [share wrap:key publicKey:receiver.publicEncryptionKey error:&localerror];
407 ckkserror("ckksshare", key.zoneID, "couldn't share %@ (wrap failed): %@", key, localerror);
414 share.signature = [share signRecord:sender.signingKey
418 ckkserror("ckksshare", key.zoneID, "couldn't share %@ (signing failed): %@", key, localerror);
428 - (CKKSKeychainBackedKey* _Nullable)recoverTLK:(id<CKKSSelfPeer>)recoverer
429 trustedPeers:(NSSet<id<CKKSPeer>>*)peers
430 ckrecord:(CKRecord* _Nullable)ckrecord
431 error:(NSError* __autoreleasing*)error
433 NSError* localerror = nil;
435 id<CKKSPeer> peer = nil;
436 for(id<CKKSPeer> p in peers) {
437 if([p.peerID isEqualToString:self.senderPeerID]) {
443 localerror = [NSError
444 errorWithDomain:CKKSErrorDomain
445 code:CKKSNoTrustedPeer
446 description:[NSString stringWithFormat:@"No trusted peer signed %@", self]];
453 bool isSigned = [self verifySignature:self.signature
461 CKKSKeychainBackedKey* tlkTrial = [self unwrapUsing:recoverer error:error];
466 if(![self.tlkUUID isEqualToString:tlkTrial.uuid]) {
467 localerror = [NSError errorWithDomain:CKKSErrorDomain
468 code:CKKSDataMismatch
469 description:[NSString stringWithFormat:@"Signed UUID doesn't match unsigned UUID for %@",