]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSTLKShare.m
Security-59754.41.1.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* _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
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 ckkserror("ckksshare", self.zoneID, "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 ckkserror("ckksshare", self.zoneID, "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 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"];
358
359
360 _receiverPeerID = [decoder decodeObjectOfClass:[NSString class] forKey:@"receiverPeerID"];
361 _receiverPublicEncryptionKeySPKI = [decoder decodeObjectOfClass:[NSData class] forKey:@"receiverSPKI"];
362 }
363 return self;
364 }
365
366 - (BOOL)isEqual:(id)object
367 {
368 if(![object isKindOfClass:[CKKSTLKShare class]]) {
369 return NO;
370 }
371
372 CKKSTLKShare* obj = (CKKSTLKShare*)object;
373
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)
383 ? YES
384 : NO;
385 }
386
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
393 {
394 NSError* localerror = nil;
395 CKKSTLKShare* share = [[CKKSTLKShare alloc] init:key
396 sender:sender
397 receiver:receiver
398 curve:SFEllipticCurveNistp384
399 version:SecCKKSTLKShareCurrentVersion
400 epoch:epoch
401 poisoned:poisoned
402 zoneID:key.zoneID];
403
404 share.wrappedTLK =
405 [share wrap:key publicKey:receiver.publicEncryptionKey error:&localerror];
406 if(localerror) {
407 ckkserror("ckksshare", key.zoneID, "couldn't share %@ (wrap failed): %@", key, localerror);
408 if(error) {
409 *error = localerror;
410 }
411 return nil;
412 }
413
414 share.signature = [share signRecord:sender.signingKey
415 ckrecord:nil
416 error:&localerror];
417 if(localerror) {
418 ckkserror("ckksshare", key.zoneID, "couldn't share %@ (signing failed): %@", key, localerror);
419 if(error) {
420 *error = localerror;
421 }
422 return nil;
423 }
424
425 return share;
426 }
427
428 - (CKKSKeychainBackedKey* _Nullable)recoverTLK:(id<CKKSSelfPeer>)recoverer
429 trustedPeers:(NSSet<id<CKKSPeer>>*)peers
430 ckrecord:(CKRecord* _Nullable)ckrecord
431 error:(NSError* __autoreleasing*)error
432 {
433 NSError* localerror = nil;
434
435 id<CKKSPeer> peer = nil;
436 for(id<CKKSPeer> p in peers) {
437 if([p.peerID isEqualToString:self.senderPeerID]) {
438 peer = p;
439 }
440 }
441
442 if(!peer) {
443 localerror = [NSError
444 errorWithDomain:CKKSErrorDomain
445 code:CKKSNoTrustedPeer
446 description:[NSString stringWithFormat:@"No trusted peer signed %@", self]];
447 if(error) {
448 *error = localerror;
449 }
450 return nil;
451 }
452
453 bool isSigned = [self verifySignature:self.signature
454 verifyingPeer:peer
455 ckrecord:ckrecord
456 error:error];
457 if(!isSigned) {
458 return nil;
459 }
460
461 CKKSKeychainBackedKey* tlkTrial = [self unwrapUsing:recoverer error:error];
462 if(!tlkTrial) {
463 return nil;
464 }
465
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 %@",
470 self]];
471 if(error) {
472 *error = localerror;
473 }
474 return nil;
475 }
476
477 return tlkTrial;
478 }
479
480 @end
481
482 #endif // OCTAGON