]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSTLKShare.m
Security-59306.80.4.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
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>
33
34 #import "keychain/categories/NSError+UsefulConstructors.h"
35 #import "keychain/ckks/CKKSPeer.h"
36 #import "keychain/ckks/CloudKitCategories.h"
37
38 @interface CKKSTLKShare ()
39 @end
40
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
50 {
51 if((self = [super init])) {
52 _zoneID = zoneID;
53
54 _curve = curve;
55 _version = version;
56 _tlkUUID = key.uuid;
57
58 _receiverPeerID = receiver.peerID;
59 _receiverPublicEncryptionKeySPKI = receiver.publicEncryptionKey.keyData;
60
61 _senderPeerID = sender.peerID;
62
63 _epoch = epoch;
64 _poisoned = poisoned;
65 }
66 return self;
67 }
68
69 - (instancetype)initForKey:(NSString*)tlkUUID
70 senderPeerID:(NSString*)senderPeerID
71 recieverPeerID:(NSString*)receiverPeerID
72 receiverEncPublicKeySPKI:(NSData*)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
80 {
81 if((self = [super init])) {
82 _zoneID = zoneID;
83 _tlkUUID = tlkUUID;
84 _senderPeerID = senderPeerID;
85
86 _receiverPeerID = receiverPeerID;
87 _receiverPublicEncryptionKeySPKI = publicKeySPKI;
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 {
102 return [NSString stringWithFormat:@"<CKKSTLKShareCore(%@): recv:%@ send:%@>",
103 self.tlkUUID,
104 self.receiverPeerID,
105 self.senderPeerID];
106 }
107
108 - (NSData*)wrap:(CKKSKeychainBackedKey*)key
109 publicKey:(SFECPublicKey*)receiverPublicKey
110 error:(NSError* __autoreleasing*)error
111 {
112 NSData* plaintext = [key serializeAsProtobuf:error];
113 if(!plaintext) {
114 return nil;
115 }
116
117 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
118 SFIESCiphertext* ciphertext =
119 [sfieso encrypt:plaintext withKey:receiverPublicKey error:error];
120
121 // Now use NSCoding to turn the ciphertext into something transportable
122 NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
123 [ciphertext encodeWithCoder:archiver];
124
125 return archiver.encodedData;
126 }
127
128 - (CKKSKeychainBackedKey* _Nullable)unwrapUsing:(id<CKKSSelfPeer>)localPeer
129 error:(NSError* __autoreleasing*)error
130 {
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];
136
137 SFIESOperation* sfieso = [[SFIESOperation alloc] initWithCurve:self.curve];
138
139 NSError* localerror = nil;
140 NSData* plaintext =
141 [sfieso decrypt:ciphertext withKey:localPeer.encryptionKey error:&localerror];
142 if(!plaintext || localerror) {
143 if(error) {
144 *error = localerror;
145 }
146 return nil;
147 }
148
149 return [CKKSKeychainBackedKey loadFromProtobuf:plaintext error:error];
150 }
151
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
155 {
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];
159
160 uint64_t version = OSSwapHostToLittleConstInt64(self.version);
161 [dataToSign appendBytes:&version length:sizeof(version)];
162
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]];
167
168 [dataToSign appendData:self.wrappedTLK];
169
170 uint64_t curve = OSSwapHostToLittleConstInt64(self.curve);
171 [dataToSign appendBytes:&curve length:sizeof(curve)];
172
173 uint64_t epoch = OSSwapHostToLittleConstInt64(self.epoch);
174 [dataToSign appendBytes:&epoch length:sizeof(epoch)];
175
176 uint64_t poisoned = OSSwapHostToLittleConstInt64(self.poisoned);
177 [dataToSign appendBytes:&poisoned length:sizeof(poisoned)];
178
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
181 if(record) {
182 NSMutableDictionary<NSString*, id>* extraData = [NSMutableDictionary dictionary];
183
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.
194 continue;
195 }
196
197 if([key hasPrefix:@"server_"]) {
198 // Ignore all fields prefixed by "server_"
199 continue;
200 }
201
202 extraData[key] = record[key];
203 }
204
205 NSArray* extraKeys = [[extraData allKeys] sortedArrayUsingSelector:@selector(compare:)];
206 for(NSString* extraKey in extraKeys) {
207 id obj = extraData[extraKey];
208
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]]) {
219 // Add an NSNumber
220 uint64_t n64 = OSSwapHostToLittleConstInt64([obj unsignedLongLongValue]);
221 [dataToSign appendBytes:&n64 length:sizeof(n64)];
222 }
223 }
224 }
225
226 return dataToSign;
227 }
228
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
233 {
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]];
238
239 NSData* data = [self dataForSigning:ckrecord];
240 SFSignedData* signedData = [xso sign:data withKey:signingKey error:error];
241
242 return signedData.signature;
243 }
244
245 - (bool)verifySignature:(NSData*)signature
246 verifyingPeer:(id<CKKSPeer>)peer
247 ckrecord:(CKRecord* _Nullable)ckrecord
248 error:(NSError* __autoreleasing*)error
249 {
250 if(!peer.publicSigningKey) {
251 secerror("ckksshare: no signing key for peer: %@", peer);
252 if(error) {
253 *error = [NSError
254 errorWithDomain:CKKSErrorDomain
255 code:CKKSNoSigningKey
256 description:[NSString stringWithFormat:@"Peer(%@) has no signing key", peer]];
257 }
258 return false;
259 }
260
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];
267
268 bool ret = [xso verify:signedData withKey:peer.publicSigningKey error:error];
269 return ret;
270 }
271
272 - (bool)signatureVerifiesWithPeerSet:(NSSet<id<CKKSPeer>>*)peerSet
273 ckrecord:(CKRecord* _Nullable)ckrecord
274 error:(NSError**)error
275 {
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
282 verifyingPeer:peer
283 ckrecord:ckrecord
284 error:&localerror];
285 if(localerror) {
286 secerror("ckksshare: signature didn't verify for %@ %@: %@", self, peer, localerror);
287 lastVerificationError = localerror;
288 }
289 if(isSigned) {
290 return true;
291 }
292 }
293 }
294
295 if(error) {
296 if(lastVerificationError) {
297 *error = lastVerificationError;
298 } else {
299 *error = [NSError
300 errorWithDomain:CKKSErrorDomain
301 code:CKKSNoTrustedTLKShares
302 description:[NSString stringWithFormat:@"No TLK share from %@", self.senderPeerID]];
303 }
304 }
305 return false;
306 }
307
308 - (instancetype)copyWithZone:(NSZone*)zone
309 {
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];
319
320 share.receiverPeerID = [self.receiverPeerID copy];
321 share.receiverPublicEncryptionKeySPKI = [self.receiverPublicEncryptionKeySPKI copy];
322
323 return share;
324 }
325
326 + (BOOL)supportsSecureCoding {
327 return YES;
328 }
329
330 - (void)encodeWithCoder:(nonnull NSCoder*)coder
331 {
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"];
341
342 [coder encodeObject:self.receiverPeerID forKey:@"receiverPeerID"];
343 [coder encodeObject:self.receiverPublicEncryptionKeySPKI forKey:@"receiverSPKI"];
344 }
345
346 - (nullable instancetype)initWithCoder:(nonnull NSCoder*)decoder
347 {
348 self = [super init];
349 if(self) {
350 _zoneID = [decoder decodeObjectOfClass:[CKRecordZoneID class] forKey:@"zoneID"];
351 _curve = (SFEllipticCurve) [decoder decodeInt64ForKey:@"curve"];
352 _version = (SecCKKSTLKShareVersion)[decoder decodeInt64ForKey:@"version"];
353 _tlkUUID = [decoder decodeObjectOfClass:[NSString class] forKey:@"tlkUUID"];
354 _senderPeerID = [decoder decodeObjectOfClass:[NSString class] forKey:@"senderPeerID"];
355 _epoch = (NSInteger)[decoder decodeInt64ForKey:@"epoch"];
356 _poisoned = (NSInteger)[decoder decodeInt64ForKey:@"poisoned"];
357 _wrappedTLK = [decoder decodeObjectOfClass:[NSData class] forKey:@"wrappedTLK"];
358 _signature = [decoder decodeObjectOfClass:[NSData class] forKey:@"signature"];
359
360
361 _receiverPeerID = [decoder decodeObjectOfClass:[NSString class] forKey:@"receiverPeerID"];
362 _receiverPublicEncryptionKeySPKI = [decoder decodeObjectOfClass:[NSData class] forKey:@"receiverSPKI"];
363 }
364 return self;
365 }
366
367 - (BOOL)isEqual:(id)object
368 {
369 if(![object isKindOfClass:[CKKSTLKShare class]]) {
370 return NO;
371 }
372
373 CKKSTLKShare* obj = (CKKSTLKShare*)object;
374
375 return ([self.tlkUUID isEqualToString:obj.tlkUUID] && [self.zoneID isEqual:obj.zoneID] &&
376 [self.senderPeerID isEqualToString:obj.senderPeerID] &&
377 ((self.receiverPeerID == nil && obj.receiverPeerID == nil) ||
378 [self.receiverPeerID isEqual:obj.receiverPeerID]) &&
379 ((self.receiverPublicEncryptionKeySPKI == nil && obj.receiverPublicEncryptionKeySPKI == nil) ||
380 [self.receiverPublicEncryptionKeySPKI isEqual:obj.receiverPublicEncryptionKeySPKI]) &&
381 self.epoch == obj.epoch && self.curve == obj.curve && self.poisoned == obj.poisoned &&
382 ((self.wrappedTLK == nil && obj.wrappedTLK == nil) || [self.wrappedTLK isEqual:obj.wrappedTLK]) &&
383 ((self.signature == nil && obj.signature == nil) || [self.signature isEqual:obj.signature]) && true)
384 ? YES
385 : NO;
386 }
387
388 + (CKKSTLKShare* _Nullable)share:(CKKSKeychainBackedKey*)key
389 as:(id<CKKSSelfPeer>)sender
390 to:(id<CKKSPeer>)receiver
391 epoch:(NSInteger)epoch
392 poisoned:(NSInteger)poisoned
393 error:(NSError* __autoreleasing*)error
394 {
395 NSError* localerror = nil;
396 CKKSTLKShare* share = [[CKKSTLKShare alloc] init:key
397 sender:sender
398 receiver:receiver
399 curve:SFEllipticCurveNistp384
400 version:SecCKKSTLKShareCurrentVersion
401 epoch:epoch
402 poisoned:poisoned
403 zoneID:key.zoneID];
404
405 share.wrappedTLK =
406 [share wrap:key publicKey:receiver.publicEncryptionKey error:&localerror];
407 if(localerror) {
408 secerror("ckksshare: couldn't share %@ (wrap failed): %@", key, localerror);
409 if(error) {
410 *error = localerror;
411 }
412 return nil;
413 }
414
415 share.signature = [share signRecord:sender.signingKey
416 ckrecord:nil
417 error:&localerror];
418 if(localerror) {
419 secerror("ckksshare: couldn't share %@ (signing failed): %@", key, localerror);
420 if(error) {
421 *error = localerror;
422 }
423 return nil;
424 }
425
426 return share;
427 }
428
429 - (CKKSKeychainBackedKey* _Nullable)recoverTLK:(id<CKKSSelfPeer>)recoverer
430 trustedPeers:(NSSet<id<CKKSPeer>>*)peers
431 ckrecord:(CKRecord* _Nullable)ckrecord
432 error:(NSError* __autoreleasing*)error
433 {
434 NSError* localerror = nil;
435
436 id<CKKSPeer> peer = nil;
437 for(id<CKKSPeer> p in peers) {
438 if([p.peerID isEqualToString:self.senderPeerID]) {
439 peer = p;
440 }
441 }
442
443 if(!peer) {
444 localerror = [NSError
445 errorWithDomain:CKKSErrorDomain
446 code:CKKSNoTrustedPeer
447 description:[NSString stringWithFormat:@"No trusted peer signed %@", self]];
448 if(error) {
449 *error = localerror;
450 }
451 return nil;
452 }
453
454 bool isSigned = [self verifySignature:self.signature
455 verifyingPeer:peer
456 ckrecord:ckrecord
457 error:error];
458 if(!isSigned) {
459 return nil;
460 }
461
462 CKKSKeychainBackedKey* tlkTrial = [self unwrapUsing:recoverer error:error];
463 if(!tlkTrial) {
464 return nil;
465 }
466
467 if(![self.tlkUUID isEqualToString:tlkTrial.uuid]) {
468 localerror = [NSError errorWithDomain:CKKSErrorDomain
469 code:CKKSDataMismatch
470 description:[NSString stringWithFormat:@"Signed UUID doesn't match unsigned UUID for %@",
471 self]];
472 if(error) {
473 *error = localerror;
474 }
475 return nil;
476 }
477
478 return tlkTrial;
479 }
480
481 @end
482
483 #endif // OCTAGON