]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSAESSIVEncryptionTests.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSAESSIVEncryptionTests.m
1 /*
2 * Copyright (c) 2016 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 <XCTest/XCTest.h>
27 #import "CloudKitMockXCTest.h"
28
29 #import "keychain/ckks/CKKS.h"
30 #import "keychain/ckks/CKKSKey.h"
31 #import "keychain/ckks/CKKSItem.h"
32 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
33 #import "keychain/ckks/CKKSIncomingQueueEntry.h"
34 #import "keychain/ckks/CKKSItemEncrypter.h"
35
36 #include "keychain/securityd/SecItemServer.h"
37 #include <Security/SecItemPriv.h>
38 #include "OSX/sec/Security/SecItemShim.h"
39
40 @interface CloudKitKeychainAESSIVEncryptionTests : CloudKitMockXCTest
41 @end
42
43 @implementation CloudKitKeychainAESSIVEncryptionTests
44
45 + (void)setUp {
46 // We don't really want to spin up the whole machinery for the encryption tests
47 SecCKKSDisable();
48
49 [super setUp];
50 }
51
52 - (void)setUp {
53 [super setUp];
54 }
55
56 - (void)tearDown {
57 [super tearDown];
58 }
59
60 + (void)tearDown {
61 [super tearDown];
62 SecCKKSResetSyncing();
63 }
64
65 - (void)testKeyGeneration {
66 NSError* error = nil;
67 CKKSAESSIVKey* key1 = [CKKSAESSIVKey randomKey:&error];
68 XCTAssertNil(error, "Should be no error creating random key");
69 CKKSAESSIVKey* key2 = [CKKSAESSIVKey randomKey:&error];
70 XCTAssertNil(error, "Should be no error creating random key");
71
72 CKKSAESSIVKey* fixedkey1 = [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="];
73 XCTAssertNotNil(fixedkey1, "fixedkey1 generated from base64");
74 CKKSAESSIVKey* fixedkey2 = [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="];
75 XCTAssertNotNil(fixedkey2, "fixedkey2 generated from base64");
76
77 XCTAssertEqualObjects(fixedkey1, fixedkey2, "matching fixed keys match");
78 XCTAssertNotEqualObjects(fixedkey1, key1, "fixed key and random key do not match");
79 XCTAssertNotEqualObjects(key1, key2, "two random keys do not match");
80
81 XCTAssertNil([[CKKSAESSIVKey alloc] initWithBase64: @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA------AAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="], "Invalid base64 does not generate a key");
82 }
83
84 - (void)testBasicAESSIVEncryption {
85 NSString* plaintext = @"plaintext is plain";
86 NSData* plaintextData = [plaintext dataUsingEncoding: NSUTF8StringEncoding];
87
88 NSError* error = nil;
89
90 CKKSKey* key = [[CKKSKey alloc] initSelfWrappedWithAESKey: [[CKKSAESSIVKey alloc] initWithBase64: @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="]
91 uuid:@"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7"
92 keyclass:SecCKKSKeyClassC
93 state: SecCKKSProcessedStateLocal
94 zoneID:[[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName]
95 encodedCKRecord: nil
96 currentkey: true];
97
98 NSData* ciphertext = [key encryptData: plaintextData authenticatedData: nil error: &error];
99 XCTAssertNil(error, "No error encrypting plaintext");
100 XCTAssertNotNil(ciphertext, "Received a ciphertext");
101 NSData* roundtrip = [key decryptData: ciphertext authenticatedData: nil error: &error];
102 XCTAssertNil(error, "No error decrypting roundtrip");
103 XCTAssertNotNil(roundtrip, "Received a plaintext");
104 XCTAssertEqualObjects(plaintextData, roundtrip, "roundtripped data matches input");
105
106 NSData* shortDecrypt = [key decryptData: [@"asdf" dataUsingEncoding:NSUTF8StringEncoding] authenticatedData:nil error:&error];
107 XCTAssertNotNil(error, "Decrypting a short plaintext returned an error");
108 XCTAssertNil(shortDecrypt, "Decrypting a short plaintext returned nil");
109 error = nil;
110
111 // Check that we're adding enough entropy
112 NSData* ciphertextAgain = [key encryptData: plaintextData authenticatedData: nil error: &error];
113 XCTAssertNil(error, "No error encrypting plaintext");
114 XCTAssertNotNil(ciphertextAgain, "Received a ciphertext");
115 NSData* roundtripAgain = [key decryptData: ciphertextAgain authenticatedData: nil error: &error];
116 XCTAssertNil(error, "No error decrypting roundtrip");
117 XCTAssertNotNil(roundtripAgain, "Received a plaintext");
118 XCTAssertEqualObjects(plaintextData, roundtripAgain, "roundtripped data matches input");
119
120 XCTAssertNotEqualObjects(ciphertext, ciphertextAgain, "two encryptions of same input produce different outputs");
121
122 // Do it all again
123 CKKSKey* key2 = [[CKKSKey alloc] initSelfWrappedWithAESKey: [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="]
124 uuid:@"f5e7f20f-0885-48f9-b75d-9f0cfd2171b6"
125 keyclass:SecCKKSKeyClassC
126 state: SecCKKSProcessedStateLocal
127 zoneID:[[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName]
128 encodedCKRecord: nil
129 currentkey: true];
130
131 NSData* ciphertext2 = [key2 encryptData: plaintextData authenticatedData: nil error: &error];
132 XCTAssertNil(error, "No error encrypting plaintext");
133 XCTAssertNotNil(ciphertext2, "Received a ciphertext");
134 NSData* roundtrip2 = [key decryptData: ciphertext authenticatedData: nil error: &error];
135 XCTAssertNil(error, "No error decrypting roundtrip");
136 XCTAssertNotNil(roundtrip2, "Received a plaintext");
137 XCTAssertEqualObjects(plaintextData, roundtrip2, "roundtripped data matches input");
138
139 XCTAssertNotEqualObjects(ciphertext, ciphertext2, "ciphertexts with distinct keys are distinct");
140 }
141
142 - (void)testAuthEncryption {
143 NSString* plaintext = @"plaintext is plain";
144 NSData* plaintextData = [plaintext dataUsingEncoding: NSUTF8StringEncoding];
145
146 NSError* error = nil;
147
148 CKKSKey* key = [[CKKSKey alloc] initSelfWrappedWithAESKey: [[CKKSAESSIVKey alloc] initWithBase64: @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="]
149 uuid:@"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7"
150 keyclass:SecCKKSKeyClassC
151 state:SecCKKSProcessedStateLocal
152 zoneID:[[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName]
153 encodedCKRecord:nil
154 currentkey:true];
155 NSDictionary<NSString*, NSData*>* ad = @{ @"test": [@"data" dataUsingEncoding: NSUTF8StringEncoding] };
156
157 NSData* ciphertext = [key encryptData: plaintextData authenticatedData: ad error: &error];
158 XCTAssertNil(error, "No error encrypting plaintext");
159 XCTAssertNotNil(ciphertext, "Received a ciphertext");
160 NSData* roundtrip = [key decryptData: ciphertext authenticatedData: ad error: &error];
161 XCTAssertNil(error, "No error decrypting roundtrip");
162 XCTAssertNotNil(roundtrip, "Received a plaintext");
163 XCTAssertEqualObjects(plaintextData, roundtrip, "roundtripped data matches input");
164
165 // Without AD, decryption should fail
166 roundtrip = [key decryptData: ciphertext authenticatedData: nil error: &error];
167 XCTAssertNotNil(error, "Not passing in the authenticated data causes break");
168 XCTAssertNil(roundtrip, "on error, don't receive plaintext");
169 error = nil;
170
171 roundtrip = [key decryptData: ciphertext authenticatedData: @{ @"test": [@"wrongdata" dataUsingEncoding: NSUTF8StringEncoding] } error: &error];
172 XCTAssertNotNil(error, "Wrong authenticated data causes break");
173 XCTAssertNil(roundtrip, "on error, don't receive plaintext");
174 error = nil;
175 }
176
177 - (void)testDictionaryEncryption {
178 NSDictionary<NSString*, NSData*>* plaintext = @{ @"test": [@"data" dataUsingEncoding: NSUTF8StringEncoding],
179 @"more": [@"testdata" dataUsingEncoding: NSUTF8StringEncoding] };
180 NSDictionary<NSString*, NSData*>* roundtrip;
181
182 NSError* error = nil;
183
184 CKKSKey* key = [[CKKSKey alloc] initSelfWrappedWithAESKey: [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="]
185 uuid:@"f5e7f20f-0885-48f9-b75d-9f0cfd2171b6"
186 keyclass:SecCKKSKeyClassC
187 state: SecCKKSProcessedStateLocal
188 zoneID:[[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName]
189 encodedCKRecord: nil
190 currentkey: true];
191
192 NSData* ciphertext = [CKKSItemEncrypter encryptDictionary: plaintext key: key.aessivkey authenticatedData: nil error: &error];
193 XCTAssertNil(error, "No error encrypting plaintext");
194 XCTAssertNotNil(ciphertext, "Received a ciphertext");
195 roundtrip = [CKKSItemEncrypter decryptDictionary: ciphertext key: key.aessivkey authenticatedData: nil error: &error];
196 XCTAssertNil(error, "No error decrypting roundtrip");
197 XCTAssertNotNil(roundtrip, "Received a plaintext");
198 XCTAssertEqualObjects(plaintext, roundtrip, "roundtripped dictionary matches input");
199
200 NSDictionary* authenticatedData = @{@"data": [@"auth" dataUsingEncoding: NSUTF8StringEncoding], @"moredata": [@"unauth" dataUsingEncoding: NSUTF8StringEncoding]};
201 NSDictionary* unauthenticatedData = @{@"data": [@"notequal" dataUsingEncoding: NSUTF8StringEncoding], @"moredata": [@"unauth" dataUsingEncoding: NSUTF8StringEncoding]};
202
203 NSData* authciphertext = [CKKSItemEncrypter encryptDictionary: plaintext key: key.aessivkey authenticatedData: authenticatedData error: &error];
204 XCTAssertNil(error, "No error encrypting plaintext with authenticated data");
205 XCTAssertNotNil(authciphertext, "Received a ciphertext");
206 roundtrip = [CKKSItemEncrypter decryptDictionary: authciphertext key: key.aessivkey authenticatedData: authenticatedData error: &error];
207 XCTAssertNil(error, "No error decrypting roundtrip with authenticated data");
208 XCTAssertNotNil(roundtrip, "Received a plaintext");
209 XCTAssertEqualObjects(plaintext, roundtrip, "roundtripped dictionary matches input");
210
211 roundtrip = [CKKSItemEncrypter decryptDictionary: authciphertext key: key.aessivkey authenticatedData: unauthenticatedData error: &error];
212 XCTAssertNotNil(error, "Error decrypting roundtrip with bad authenticated data");
213 XCTAssertNil(roundtrip, "Did not receive a plaintext when authenticated data is wrong");
214 }
215
216 - (void)testKeyWrapping {
217 CKKSAESSIVKey* key = [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="];
218
219 CKKSAESSIVKey* keyToWrap = [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="];
220
221 NSError* error = nil;
222
223 CKKSWrappedAESSIVKey* wrappedKey = [key wrapAESKey: keyToWrap error:&error];
224 XCTAssertNil(error, "no error wrapping key");
225 XCTAssertNotNil(wrappedKey, "wrapped key was returned");
226
227 XCTAssert(0 != memcmp(keyToWrap->key, (wrappedKey->key)+(CKKSWrappedKeySize - CKKSKeySize), CKKSKeySize), "wrapped key is different from original key");
228
229 CKKSAESSIVKey* unwrappedKey = [key unwrapAESKey: wrappedKey error:&error];
230 XCTAssertNil(error, "no error unwrapping key");
231 XCTAssertNotNil(unwrappedKey, "unwrapped key was returned");
232
233 XCTAssert(0 == memcmp(keyToWrap->key, unwrappedKey->key, CKKSKeySize), "unwrapped key matches original key");
234 XCTAssertEqualObjects(keyToWrap, unwrappedKey, "unwrapped key matches original key");
235 }
236
237 - (void)testKeyWrappingFailure {
238 CKKSAESSIVKey* key = [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="];
239
240 CKKSAESSIVKey* keyToWrap = [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="];
241
242 NSError* error = nil;
243
244 CKKSWrappedAESSIVKey* wrappedKey = [key wrapAESKey: keyToWrap error:&error];
245 XCTAssertNil(error, "no error wrapping key");
246 XCTAssertNotNil(wrappedKey, "wrapped key was returned");
247
248 XCTAssert(0 != memcmp(keyToWrap->key, (wrappedKey->key)+(CKKSWrappedKeySize - CKKSKeySize), CKKSKeySize), "wrapped key is different from original key");
249 wrappedKey->key[0] ^= 0x1;
250
251 CKKSAESSIVKey* unwrappedKey = [key unwrapAESKey: wrappedKey error:&error];
252 XCTAssertNotNil(error, "error unwrapping key");
253 XCTAssertNil(unwrappedKey, "unwrapped key was not returned in error case");
254 }
255
256 - (void)testKeyKeychainSaving {
257 NSError* error = nil;
258 CKKSKey* tlk = [self fakeTLK:self.testZoneID];
259
260 XCTAssertTrue([tlk saveKeyMaterialToKeychain:false error:&error], "should be able to save key material to keychain (without stashing)");
261 XCTAssertNil(error, "tlk should save to database without error");
262 XCTAssertTrue([tlk loadKeyMaterialFromKeychain:&error], "Should be able to reload key material");
263 XCTAssertNil(error, "should be no error loading the tlk from the keychain");
264
265 XCTAssertTrue([tlk saveKeyMaterialToKeychain:false error:&error], "should be able to save key material to keychain (without stashing)");
266 XCTAssertNil(error, "tlk should save again to database without error");
267 XCTAssertTrue([tlk loadKeyMaterialFromKeychain:&error], "Should be able to reload key material");
268 XCTAssertNil(error, "should be no error loading the tlk from the keychain");
269
270 [tlk deleteKeyMaterialFromKeychain:&error];
271 XCTAssertNil(error, "tlk should be able to delete itself without error");
272
273 XCTAssertFalse([tlk loadKeyMaterialFromKeychain:&error], "Should not able to reload key material");
274 XCTAssertNotNil(error, "should be error loading the tlk from the keychain");
275 error = nil;
276
277 NSData* keydata = [@"asdf" dataUsingEncoding:NSUTF8StringEncoding];
278
279 // Add an item using no viewhint that will conflict with itself upon a SecItemUpdate (internal builds only)
280 NSMutableDictionary* query = [@{
281 (id)kSecClass : (id)kSecClassInternetPassword,
282 (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked,
283 (id)kSecUseDataProtectionKeychain : @YES,
284 (id)kSecAttrAccessGroup: @"com.apple.security.ckks",
285 (id)kSecAttrDescription: tlk.keyclass,
286 (id)kSecAttrServer: tlk.zoneID.zoneName,
287 (id)kSecAttrAccount: tlk.uuid,
288 (id)kSecAttrPath: tlk.parentKeyUUID,
289 (id)kSecAttrIsInvisible: @YES,
290 (id)kSecValueData : keydata,
291 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
292 } mutableCopy];
293 XCTAssertEqual(errSecSuccess, SecItemAdd((__bridge CFDictionaryRef)query, NULL), "Should be able to add a conflicting item");
294
295 XCTAssertTrue([tlk saveKeyMaterialToKeychain:false error:&error], "should be able to save key material to keychain (without stashing)");
296 XCTAssertNil(error, "tlk should save to database without error");
297 XCTAssertTrue([tlk loadKeyMaterialFromKeychain:&error], "Should be able to reload key material");
298 XCTAssertNil(error, "should be no error loading the tlk from the keychain");
299
300 XCTAssertTrue([tlk saveKeyMaterialToKeychain:false error:&error], "should be able to save key material to keychain (without stashing)");
301 XCTAssertNil(error, "tlk should save again to database without error");
302 XCTAssertTrue([tlk loadKeyMaterialFromKeychain:&error], "Should be able to reload key material");
303 XCTAssertNil(error, "should be no error loading the tlk from the keychain");
304 }
305
306 - (void)testKeyHierarchy {
307 NSError* error = nil;
308 NSData* testCKRecord = [@"nonsense" dataUsingEncoding:NSUTF8StringEncoding];
309 CKKSKey* tlk = [self fakeTLK:self.testZoneID];
310
311 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
312 NSError* saveError = nil;
313 [tlk saveToDatabase:&saveError];
314 XCTAssertNil(saveError, "tlk saved to database without error");
315 return CKKSDatabaseTransactionCommit;
316 }];
317
318 [tlk saveKeyMaterialToKeychain:&error];
319 XCTAssertNil(error, "tlk saved to database without error");
320
321 CKKSKey* level1 = [CKKSKey randomKeyWrappedByParent: tlk keyclass:SecCKKSKeyClassA error:&error];
322 level1.encodedCKRecord = testCKRecord;
323 XCTAssertNotNil(level1, "level 1 key created");
324 XCTAssertNil(error, "level 1 key created");
325
326 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
327 NSError* saveError = nil;
328 [level1 saveToDatabase:&saveError];
329 XCTAssertNil(saveError, "level 1 key saved to database without error");
330 return CKKSDatabaseTransactionCommit;
331 }];
332
333 CKKSKey* level2 = [CKKSKey randomKeyWrappedByParent: level1 error:&error];
334 level2.encodedCKRecord = testCKRecord;
335 XCTAssertNotNil(level2, "level 2 key created");
336 XCTAssertNil(error, "no error creating level 2 key");
337
338 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
339 NSError* saveError = nil;
340 [level2 saveToDatabase:&saveError];
341 XCTAssertNil(saveError, "level 2 key saved to database without error");
342 return CKKSDatabaseTransactionCommit;
343 }];
344
345 NSString* level2UUID = level2.uuid;
346
347 // Fetch the level2 key from the database.
348 CKKSKey* extractedkey = [CKKSKey fromDatabase:level2UUID zoneID:self.testZoneID error:&error];
349 [extractedkey unwrapViaKeyHierarchy: &error];
350 XCTAssertNotNil(extractedkey, "could fetch key again");
351 XCTAssertNil(error, "no error fetching key from database");
352
353 CKKSAESSIVKey* extracedaeskey = [extractedkey ensureKeyLoaded:&error];
354 XCTAssertNotNil(extractedkey, "fetched key could unwrap");
355 XCTAssertNil(error, "no error forcing unwrap on fetched key");
356
357 XCTAssertEqualObjects(level2.aessivkey, extracedaeskey, @"fetched aes key is equal to saved key");
358 }
359
360 - (void)ensureKeychainSaveLoad: (CKKSKey*) key {
361 NSError* error = nil;
362
363 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
364 NSError* saveError = nil;
365 [key saveToDatabase:&saveError];
366 XCTAssertNil(saveError, "no error saving to database");
367 return CKKSDatabaseTransactionCommit;
368 }];
369
370 [key saveKeyMaterialToKeychain:&error];
371 XCTAssertNil(error, "no error saving to keychain");
372
373 CKKSKey* loadedKey = [CKKSKey fromDatabase:key.uuid zoneID:self.testZoneID error:&error];
374 XCTAssertNil(error, "no error loading from database");
375 XCTAssertNotNil(loadedKey, "Received an item back from the database");
376
377 XCTAssert([loadedKey loadKeyMaterialFromKeychain:&error], "could load key material back from keychain");
378 XCTAssertNil(error, "no error loading key from keychain");
379
380 XCTAssertEqualObjects(loadedKey.aessivkey, key.aessivkey, "Loaded key is identical after save/load");
381 }
382
383 - (void)testKeychainSave {
384 NSError* error = nil;
385 NSData* testCKRecord = [@"nonsense" dataUsingEncoding:NSUTF8StringEncoding];
386 CKKSKey* tlk = [self fakeTLK:self.testZoneID];
387 [self ensureKeychainSaveLoad: tlk];
388
389 // Ensure that Class A and Class C can do the same thing
390 CKKSKey* classA = [CKKSKey randomKeyWrappedByParent: tlk keyclass:SecCKKSKeyClassA error:&error];
391 classA.encodedCKRecord = testCKRecord;
392 XCTAssertNil(error, "No error creating random class A key");
393 [self ensureKeychainSaveLoad: classA];
394 CKKSKey* classC = [CKKSKey randomKeyWrappedByParent: tlk keyclass:SecCKKSKeyClassC error:&error];
395 classC.encodedCKRecord = testCKRecord;
396 XCTAssertNil(error, "No error creating random class C key");
397 [self ensureKeychainSaveLoad: classC];
398 }
399
400 - (void)testCKKSKeyProtobuf {
401 NSError* error = nil;
402 CKKSKey* tlk = [self fakeTLK:self.testZoneID];
403
404 NSData* tlkPersisted = [tlk serializeAsProtobuf:&error];
405 XCTAssertNil(error, "Shouldn't have been an error serializing to protobuf");
406 XCTAssertNotNil(tlkPersisted, "Should have gotten some protobuf data back");
407
408 CKKSKey* otherKey = [CKKSKey loadFromProtobuf:tlkPersisted error:&error];
409 XCTAssertNil(error, "Shouldn't have been an error serializing from protobuf");
410 XCTAssertNotNil(otherKey, "Should have gotten some protobuf data back");
411
412 XCTAssertEqualObjects(tlk.uuid, otherKey.uuid, "Should have gotten the same UUID");
413 XCTAssertEqualObjects(tlk.keyclass, otherKey.keyclass, "Should have gotten the same key class");
414 XCTAssertEqualObjects(tlk.zoneID, otherKey.zoneID, "Should have gotten the same zoneID");
415 XCTAssertEqualObjects(tlk.aessivkey, otherKey.aessivkey, "Should have gotten the same underlying key back");
416 XCTAssertEqualObjects(tlk, otherKey, "Should have gotten the same key");
417 }
418
419 - (BOOL)tryDecryptWithProperAuthData:(CKKSItem*)ciphertext plaintext:(NSDictionary<NSString*, NSData*>*)plaintext {
420 NSDictionary<NSString*, NSData*>* roundtrip;
421 NSError *error = nil;
422 roundtrip = [CKKSItemEncrypter decryptItemToDictionary: (CKKSItem*) ciphertext error: &error];
423 XCTAssertNil(error, "No error decrypting roundtrip");
424 XCTAssertNotNil(roundtrip, "Received a plaintext");
425 XCTAssertEqualObjects(plaintext, roundtrip, "roundtripped dictionary matches input");
426 return error == nil && roundtrip != nil && [plaintext isEqualToDictionary:roundtrip];
427 }
428
429 - (BOOL)tryDecryptWithBrokenAuthData:(CKKSItem *)ciphertext {
430 NSDictionary<NSString*, NSData*>* brokenAuthentication;
431 NSError *error = nil;
432 brokenAuthentication = [CKKSItemEncrypter decryptItemToDictionary: (CKKSItem*) ciphertext error: &error];
433 XCTAssertNotNil(error, "Error exists decrypting ciphertext with bad authenticated data: %@", error);
434 XCTAssertNil(brokenAuthentication, "Did not receive a plaintext if authenticated data was mucked with");
435 return error != nil && brokenAuthentication == nil;
436 }
437
438 - (void)testItemDictionaryEncryption {
439 NSDictionary<NSString*, NSData*>* plaintext = @{ @"test": [@"data" dataUsingEncoding: NSUTF8StringEncoding],
440 @"more": [@"testdata" dataUsingEncoding: NSUTF8StringEncoding] };
441 NSError* error = nil;
442 NSString *uuid = @"8b2aeb7f-4af3-43e9-b6e6-70d5c728ebf7";
443
444 CKKSKey* key = [self fakeTLK:self.testZoneID];
445 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
446 NSError* saveError = nil;
447 [key saveToDatabase:&saveError];
448 XCTAssertNil(saveError, "no error saving to database");
449 return CKKSDatabaseTransactionCommit;
450 }];
451 [key saveKeyMaterialToKeychain:&error];
452 XCTAssertNil(error, @"could save the fake TLK to the database");
453
454 CKKSItem* ciphertext = [CKKSItemEncrypter encryptCKKSItem: [[CKKSItem alloc] initWithUUID:uuid
455 parentKeyUUID:key.uuid
456 zoneID:self.testZoneID]
457 dataDictionary:plaintext
458 updatingCKKSItem:nil
459 parentkey:key
460 error:&error];
461 XCTAssertNil(error, "No error encrypting plaintext");
462 XCTAssertNotNil(ciphertext, "Received a ciphertext");
463 XCTAssertEqual(ciphertext.encver, currentCKKSItemEncryptionVersion, "Encryption sets the current protocol version");
464
465 [self tryDecryptWithProperAuthData:ciphertext plaintext:plaintext];
466
467 // Make sure these fields are authenticated and that authentication works.
468 // Messing with them should make the item not decrypt.
469 ciphertext.generationCount = 100;
470 XCTAssertTrue([self tryDecryptWithBrokenAuthData:ciphertext], "Decryption with broken authentication data fails");
471 ciphertext.generationCount = 0;
472 XCTAssertTrue([self tryDecryptWithProperAuthData:ciphertext plaintext:plaintext], "Decryption with authentication data succeeds");
473
474 ciphertext.encver += 1;
475 XCTAssertTrue([self tryDecryptWithBrokenAuthData:ciphertext], "Decryption with broken authentication data fails");
476 ciphertext.encver -= 1;
477 XCTAssertTrue([self tryDecryptWithProperAuthData:ciphertext plaintext:plaintext], "Decryption with authentication data succeeds");
478
479 ciphertext.uuid = @"x";
480 XCTAssertTrue([self tryDecryptWithBrokenAuthData:ciphertext], "Decryption with broken authentication data fails");
481 ciphertext.uuid = uuid;
482 XCTAssertTrue([self tryDecryptWithProperAuthData:ciphertext plaintext:plaintext], "Decryption with authentication data succeeds");
483 }
484
485 - (void)testEncryptionVersions {
486 NSDictionary<NSString*, NSData*>* plaintext = @{ @"test": [@"data" dataUsingEncoding: NSUTF8StringEncoding],
487 @"more": [@"testdata" dataUsingEncoding: NSUTF8StringEncoding] };
488 NSDictionary<NSString*, NSData*>* output;
489 NSError *error = nil;
490 NSData* data = [NSPropertyListSerialization dataWithPropertyList:plaintext
491 format:NSPropertyListBinaryFormat_v1_0
492 options:0
493 error:&error];
494 XCTAssertNil(error);
495 CKKSKey* key = [self fakeTLK:self.testZoneID];
496
497 [CKKSSQLDatabaseObject performCKKSTransaction:^CKKSDatabaseTransactionResult {
498 NSError* saveError = nil;
499 [key saveToDatabase:&saveError];
500 XCTAssertNil(saveError, "could save the fake TLK to the database");
501 return CKKSDatabaseTransactionCommit;
502 }];
503 [key saveKeyMaterialToKeychain:&error];
504 XCTAssertNil(error, @"could save the fake TLK to the keychain");
505
506 CKKSAESSIVKey* keyToWrap = [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="];
507 CKKSWrappedAESSIVKey* wrappedKey = [key wrapAESKey: keyToWrap error:&error];
508 XCTAssertNil(error, "no error wrapping key");
509 XCTAssertNotNil(wrappedKey, "wrapped key was returned");
510 CKKSItem* baseitem = [[CKKSItem alloc] initWithUUID:@"abc"
511 parentKeyUUID:key.uuid
512 zoneID:self.testZoneID
513 encItem:data
514 wrappedkey:wrappedKey
515 generationCount:0
516 encver:CKKSItemEncryptionVersionNone];
517 XCTAssertNotNil(baseitem, "Constructed CKKSItem");
518
519 // First try versionNone. Should fail, we don't support unencrypted data
520 output = [CKKSItemEncrypter decryptItemToDictionary:baseitem error:&error];
521 XCTAssert(error, "Did not failed to decrypt v0 item");
522 XCTAssertNil(output, "Did not failed to decrypt v0 item");
523 error = nil;
524 output = nil;
525
526 // Then try version1. Should take actual decryption path and fail because there's no properly encrypted data.
527 baseitem.encver = CKKSItemEncryptionVersion1;
528 output = [CKKSItemEncrypter decryptItemToDictionary:baseitem error:&error];
529 XCTAssertNotNil(error, "Taking v1 codepath without encrypted item fails");
530 XCTAssertEqualObjects(error.localizedDescription, @"could not ccsiv_crypt", "Error specifically failure to ccsiv_crypt");
531 XCTAssertNil(output, "Did not receive output from failed decryption call");
532 error = nil;
533 output = nil;
534
535 // Finally, some unknown version should fail immediately
536 baseitem.encver = 100;
537 output = [CKKSItemEncrypter decryptItemToDictionary:baseitem error:&error];
538 XCTAssertNotNil(error);
539 NSString *errstr = [NSString stringWithFormat:@"%@", error.localizedDescription];
540 NSString *expected = @"Unrecognized encryption version: 100";
541 XCTAssertEqualObjects(expected, errstr, "Error is specific to unrecognized version failure");
542 XCTAssertNil(output);
543 }
544
545 - (void)testKeychainPersistence {
546
547 NSString* plaintext = @"plaintext is plain";
548 NSData* plaintextData = [plaintext dataUsingEncoding: NSUTF8StringEncoding];
549
550 NSError* error = nil;
551
552 NSString* uuid = @"f5e7f20f-0885-48f9-b75d-9f0cfd2171b6";
553
554 CKKSKey* key = [[CKKSKey alloc] initSelfWrappedWithAESKey: [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="]
555 uuid:uuid
556 keyclass:SecCKKSKeyClassA
557 state:SecCKKSProcessedStateLocal
558 zoneID:[[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName]
559 encodedCKRecord: nil
560 currentkey: true];
561
562 NSData* ciphertext = [key encryptData: plaintextData authenticatedData: nil error: &error];
563 XCTAssertNil(error, "No error encrypting plaintext");
564 XCTAssertNotNil(ciphertext, "Received a ciphertext");
565 NSData* roundtrip = [key decryptData: ciphertext authenticatedData: nil error: &error];
566 XCTAssertNil(error, "No error decrypting roundtrip");
567 XCTAssertNotNil(roundtrip, "Received a plaintext");
568 XCTAssertEqualObjects(plaintextData, roundtrip, "roundtripped data matches input");
569
570 // Check that there is no key material in the keychain
571 CKKSKey* reloadedKey = [CKKSKey keyFromKeychain:uuid
572 parentKeyUUID:uuid
573 keyclass:SecCKKSKeyClassA
574 state:SecCKKSProcessedStateLocal
575 zoneID:[[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName]
576 encodedCKRecord:nil
577 currentkey:true
578 error:&error];
579
580 XCTAssertNotNil(error, "error exists when there's nothing in the keychain");
581 XCTAssertNil(reloadedKey, "no key object when there's nothing in the keychain");
582 error = nil;
583
584 [key saveKeyMaterialToKeychain:&error];
585 XCTAssertNil(error, "Could save key material to keychain");
586
587 // Reload the key material and check that it works
588 reloadedKey = [CKKSKey keyFromKeychain:uuid
589 parentKeyUUID:uuid
590 keyclass:SecCKKSKeyClassA
591 state:SecCKKSProcessedStateLocal
592 zoneID:[[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName]
593 encodedCKRecord:nil
594 currentkey:true
595 error:&error];
596
597 XCTAssertNil(error, "No error loading key from keychain");
598 XCTAssertNotNil(reloadedKey, "Could load key from keychain");
599
600 NSData* ciphertext2 = [reloadedKey encryptData: plaintextData authenticatedData: nil error: &error];
601 XCTAssertNil(error, "No error encrypting plaintext");
602 XCTAssertNotNil(ciphertext2, "Received a ciphertext");
603 NSData* roundtrip2 = [reloadedKey decryptData: ciphertext2 authenticatedData: nil error: &error];
604 XCTAssertNil(error, "No error decrypting roundtrip");
605 XCTAssertNotNil(roundtrip2, "Received a plaintext");
606 XCTAssertEqualObjects(plaintextData, roundtrip2, "roundtripped data matches input");
607
608 XCTAssertEqualObjects(key.aessivkey, reloadedKey.aessivkey, "reloaded AES key is equal to generated key");
609
610 [key deleteKeyMaterialFromKeychain: &error];
611 XCTAssertNil(error, "could delete key material from keychain");
612
613 // Check that there is no key material in the keychain
614 // Note that TLKs will be stashed (and deleteKeyMaterial won't delete the stash), and so this test would fail for a TLK
615
616 reloadedKey = [CKKSKey keyFromKeychain:uuid
617 parentKeyUUID:uuid
618 keyclass:SecCKKSKeyClassA
619 state:SecCKKSProcessedStateLocal
620 zoneID:[[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName]
621 encodedCKRecord:nil
622 currentkey:true
623 error:&error];
624
625 XCTAssertNotNil(error, "error exists when there's nothing in the keychain");
626 XCTAssertNil(reloadedKey, "no key object when there's nothing in the keychain");
627 error = nil;
628 }
629
630 - (void)testCKKSKeyTrialSelfWrapped {
631 NSError* error = nil;
632 CKKSKey* tlk = [self fakeTLK:self.testZoneID];
633 XCTAssertTrue([tlk wrapsSelf], "TLKs should wrap themselves");
634
635 CKRecord* record = [tlk CKRecordWithZoneID:self.testZoneID];
636 XCTAssertNotNil(record, "TLKs should know how to turn themselves into CKRecords");
637 CKKSKey* receivedTLK = [[CKKSKey alloc] initWithCKRecord:record];
638 XCTAssertNotNil(receivedTLK, "Keys should know how to recover themselves from CKRecords");
639
640 XCTAssertTrue([receivedTLK wrapsSelf], "TLKs should wrap themselves, even when received from CloudKit");
641
642 XCTAssertFalse([receivedTLK ensureKeyLoaded:&error], "Received keys can't load themselves when there's no key data");
643 XCTAssertNotNil(error, "Error should exist when a key fails to load itself");
644 error = nil;
645
646 XCTAssertTrue([receivedTLK trySelfWrappedKeyCandidate:tlk.aessivkey error:&error], "Shouldn't be an error when we give a CKKSKey its key");
647 XCTAssertNil(error, "Shouldn't be an error giving a CKKSKey its key material");
648
649 XCTAssertTrue([receivedTLK ensureKeyLoaded:&error], "Once a CKKSKey has its key material, it doesn't need to load it again");
650 XCTAssertNil(error, "Shouldn't be an error loading a loaded CKKSKey");
651 }
652
653 - (void)testCKKSKeyTrialSelfWrappedFailure {
654 NSError* error = nil;
655 CKKSKey* tlk = [self fakeTLK:self.testZoneID];
656 XCTAssertTrue([tlk wrapsSelf], "TLKs should wrap themselves");
657
658 CKRecord* record = [tlk CKRecordWithZoneID:self.testZoneID];
659 XCTAssertNotNil(record, "TLKs should know how to turn themselves into CKRecords");
660 CKKSKey* receivedTLK = [[CKKSKey alloc] initWithCKRecord:record];
661 XCTAssertNotNil(receivedTLK, "Keys should know how to recover themselves from CKRecords");
662
663 XCTAssertTrue([receivedTLK wrapsSelf], "TLKs should wrap themselves, even when received from CloudKit");
664
665 XCTAssertFalse([receivedTLK ensureKeyLoaded:&error], "Received keys can't load themselves when there's no key data");
666 XCTAssertNotNil(error, "Error should exist when a key fails to load itself");
667 error = nil;
668
669 XCTAssertFalse([receivedTLK trySelfWrappedKeyCandidate:[[CKKSAESSIVKey alloc] initWithBase64: @"aaaaaZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="] error:&error], "Should be an error when we give a CKKSKey the wrong key");
670 XCTAssertNotNil(error, "Should be an error giving a CKKSKey the wrong key material");
671
672 XCTAssertFalse([receivedTLK ensureKeyLoaded:&error], "Received keys can't load themselves when there's no key data");
673 XCTAssertNotNil(error, "Error should exist when a key fails to load itself");
674 error = nil;
675 }
676
677 - (void)testCKKSKeyTrialNotSelfWrappedFailure {
678 NSError* error = nil;
679 CKKSKey* tlk = [self fakeTLK:self.testZoneID];
680 XCTAssertTrue([tlk wrapsSelf], "TLKs should wrap themselves");
681
682 CKKSKey* classC = [CKKSKey randomKeyWrappedByParent: tlk keyclass:SecCKKSKeyClassC error:&error];
683 XCTAssertFalse([classC wrapsSelf], "Wrapped keys should not wrap themselves");
684
685 XCTAssertTrue([classC ensureKeyLoaded:&error], "Once a CKKSKey has its key material, it doesn't need to load it again");
686 XCTAssertNil(error, "Shouldn't be an error loading a loaded CKKSKey");
687
688 XCTAssertFalse([classC trySelfWrappedKeyCandidate:classC.aessivkey error:&error], "Should be an error when we attempt to trial a key on a non-self-wrapped key");
689 XCTAssertNotNil(error, "Should be an error giving a CKKSKey the wrong key material");
690 XCTAssertEqual(error.code, CKKSKeyNotSelfWrapped, "Should have gotten CKKSKeyNotSelfWrapped as an error");
691 error = nil;
692
693 // But, since we didn't throw away its key, it's still loaded
694 XCTAssertTrue([classC ensureKeyLoaded:&error], "Once a CKKSKey has its key material, it doesn't need to load it again");
695 XCTAssertNil(error, "Shouldn't be an error loading a loaded CKKSKey");
696 }
697
698 - (BOOL)padAndUnpadDataWithLength:(NSUInteger)dataLength blockSize:(NSUInteger)blockSize extra:(BOOL)extra {
699 // Test it works
700 NSMutableData *data = [NSMutableData dataWithLength:dataLength];
701 memset((unsigned char *)[data mutableBytes], 0x55, dataLength);
702 NSMutableData *orig = [data mutableCopy];
703 NSData *padded = [CKKSItemEncrypter padData:data blockSize:blockSize additionalBlock:extra];
704 XCTAssertNotNil(padded, "Padding never returns nil");
705 XCTAssertEqualObjects(data, orig, "Input object unmodified");
706 XCTAssertTrue(padded.length % blockSize == 0, "Padded data aligns on %lu-byte blocksize", (unsigned long)blockSize);
707 XCTAssertTrue(padded.length > data.length, "At least one byte of padding has been added");
708 NSData *unpadded = [CKKSItemEncrypter removePaddingFromData:padded];
709 XCTAssertNotNil(unpadded, "Successfully removed padding again");
710
711 // Test it fails by poking some byte in the padding
712 NSMutableData *glitch = [NSMutableData dataWithData:padded];
713 NSUInteger offsetFromTop = glitch.length - arc4random_uniform((unsigned)(glitch.length - data.length)) - 1;
714 uint8_t poke = ((uint8_t)arc4random_uniform(0xFF) & 0x7E) + 1; // This gets most of the values while excluding 0 and 0x80
715 unsigned char *bytes = [glitch mutableBytes];
716 bytes[offsetFromTop] = poke;
717 XCTAssertNil([CKKSItemEncrypter removePaddingFromData:glitch], "Cannot remove broken padding (len %lu, dlen %lu, plen %lu glitchidx %lu, glitchval 0x%x)", (unsigned long)glitch.length, (unsigned long)data.length, (unsigned long)glitch.length - data.length, (unsigned long)offsetFromTop, poke);
718
719 return padded && unpadded && [unpadded isEqual:data];
720 }
721
722 - (void)testPadding {
723 [self runPaddingTest:NO];
724 [self runPaddingTest:YES];
725
726 NSData *data = nil;
727 XCTAssertNil([CKKSItemEncrypter removePaddingFromData:[NSData data]], "zero data valid ?");
728
729 data = [CKKSItemEncrypter removePaddingFromData:[NSData dataWithBytes:"\x80" length:1]];
730 XCTAssert(data && data.length == 0, "data wrong size");
731
732 data = [CKKSItemEncrypter removePaddingFromData:[NSData dataWithBytes:"\x80\x00" length:2]];
733 XCTAssert(data && data.length == 0, "data wrong size");
734 data = [CKKSItemEncrypter removePaddingFromData:[NSData dataWithBytes:"\x80\x00\x00" length:3]];
735 XCTAssert(data && data.length == 0, "data wrong size");
736 data = [CKKSItemEncrypter removePaddingFromData:[NSData dataWithBytes:"\x80\x80\x80" length:3]];
737 XCTAssert(data && data.length == 2, "data wrong size");
738 data = [CKKSItemEncrypter removePaddingFromData:[NSData dataWithBytes:"\x80\x80\x00" length:3]];
739 XCTAssert(data && data.length == 1, "data wrong size");
740 data = [CKKSItemEncrypter removePaddingFromData:[NSData dataWithBytes:"\x00\x80\x00" length:3]];
741 XCTAssert(data && data.length == 1, "data wrong size");
742
743 }
744
745 - (void)runPaddingTest:(BOOL)extra {
746
747 // Aligned, arbitrary lengths
748 for (int idx = 1; idx <= 128; ++idx) {
749 XCTAssertTrue([self padAndUnpadDataWithLength:idx blockSize:idx extra:extra], "Padding aligned data succeeds");
750 }
751
752 // Off-by-one, arbitrary lengths
753 for (int idx = 1; idx <= 128; ++idx) {
754 XCTAssertTrue([self padAndUnpadDataWithLength:idx - 1 blockSize:idx extra:extra], "Padding aligned data succeeds");
755 XCTAssertTrue([self padAndUnpadDataWithLength:idx + 1 blockSize:idx extra:extra], "Padding aligned data succeeds");
756 }
757
758 // Misaligned, arbitrary lengths
759 for (int idx = 1; idx <= 1000; ++idx) {
760 NSUInteger dataSize = arc4random_uniform(128) + 1;
761 NSUInteger blockSize = arc4random_uniform(128) + 1;
762 XCTAssertTrue([self padAndUnpadDataWithLength:dataSize blockSize:blockSize extra:extra], "Padding data lenght %lu to blockSize %lu succeeds", (unsigned long)dataSize, (unsigned long)blockSize);
763 }
764
765 // Special case: blocksize 0 results in 1 byte of padding always
766 NSMutableData *data = [NSMutableData dataWithLength:23];
767 memset((unsigned char *)[data mutableBytes], 0x55, 23);
768 NSData *padded = [CKKSItemEncrypter padData:data blockSize:0 additionalBlock:extra];
769 XCTAssertNotNil(padded, "Padding never returns nil");
770 XCTAssertTrue(padded.length == data.length + (extra ? 2 : 1), "One byte of padding has been added, 2 if extra padding");
771 NSData *unpadded = [CKKSItemEncrypter removePaddingFromData:padded];
772 XCTAssertNotNil(unpadded, "Successfully removed padding again");
773 XCTAssertEqualObjects(data, unpadded, "Data effectively unmodified through padding-unpadding trip");
774
775 // Nonpadded data
776 unpadded = [CKKSItemEncrypter removePaddingFromData:data];
777 XCTAssertNil(unpadded, "Cannot remove padding where none exists");
778
779 // Feeding nil
780 #pragma clang diagnostic push
781 #pragma clang diagnostic ignored "-Wnonnull"
782 padded = [CKKSItemEncrypter padData:nil blockSize:0 additionalBlock:extra];
783 XCTAssertNotNil(padded, "padData always returns a data object");
784 XCTAssertEqual(padded.length, extra ? 2ul : 1ul, "Length of padded nil object is padding byte only--two if extra");
785 unpadded = [CKKSItemEncrypter removePaddingFromData:nil];
786 XCTAssertNil(unpadded, "Removing padding from nil is senseless");
787 #pragma clang diagnostic pop
788 }
789
790 - (BOOL)encryptAndDecryptDictionary:(NSDictionary<NSString*, NSData*>*)data key:(CKKSKey *)key {
791 NSDictionary<NSString*, NSData*>* roundtrip;
792 NSError *error = nil;
793 NSData* ciphertext = [CKKSItemEncrypter encryptDictionary: data key: key.aessivkey authenticatedData: nil error: &error];
794 XCTAssertNil(error, "No error encrypting plaintext");
795 XCTAssertNotNil(ciphertext, "Received a ciphertext");
796 // AES-SIV adds 32 bytes, need to subtract them
797 XCTAssertTrue((ciphertext.length - 32) % SecCKKSItemPaddingBlockSize == 0, "Ciphertext aligned on %lu-byte boundary", (unsigned long)SecCKKSItemPaddingBlockSize);
798 roundtrip = [CKKSItemEncrypter decryptDictionary: ciphertext key: key.aessivkey authenticatedData: nil error: &error];
799 XCTAssertNil(error, "No error decrypting roundtrip");
800 XCTAssertNotNil(roundtrip, "Received a plaintext");
801 XCTAssertEqualObjects(data, roundtrip, "roundtripped dictionary matches input");
802 return (ciphertext.length - 32) % SecCKKSItemPaddingBlockSize == 0 && roundtrip && error == nil && [data isEqualToDictionary:roundtrip];
803 }
804
805 - (void)testDictionaryPadding {
806 // Pad a bunch of bytes to nearest boundary
807 NSDictionary<NSString*, NSData*>* unaligned_74 = @{ @"test": [@"data" dataUsingEncoding: NSUTF8StringEncoding],
808 @"more": [@"testdata" dataUsingEncoding: NSUTF8StringEncoding] };
809 // Pad precisely one byte
810 NSDictionary<NSString*, NSData*>* unaligned_79 = @{ @"test12345": [@"data" dataUsingEncoding: NSUTF8StringEncoding],
811 @"more": [@"testdata" dataUsingEncoding: NSUTF8StringEncoding] };
812 // Already on boundary, pad until next boundary
813 NSDictionary<NSString*, NSData*>* aligned_80 = @{ @"test123456": [@"data" dataUsingEncoding: NSUTF8StringEncoding],
814 @"more": [@"testdata" dataUsingEncoding: NSUTF8StringEncoding] };
815 CKKSKey* key = [[CKKSKey alloc] initSelfWrappedWithAESKey: [[CKKSAESSIVKey alloc] initWithBase64: @"uImdbZ7Zg+6WJXScTnRBfNmoU1UiMkSYxWc+d1Vuq3IFn2RmTRkTdWTe3HmeWo1pAomqy+upK8KHg2PGiRGhqg=="]
816 uuid:@"f5e7f20f-0885-48f9-b75d-9f0cfd2171b6"
817 keyclass:SecCKKSKeyClassC
818 state:SecCKKSProcessedStateLocal
819 zoneID:[[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName]
820 encodedCKRecord:nil
821 currentkey:true];
822
823 XCTAssertTrue([self encryptAndDecryptDictionary:unaligned_74 key:key], "Roundtrip with unaligned data succeeds");
824 XCTAssertTrue([self encryptAndDecryptDictionary:unaligned_79 key:key], "Roundtrip with unaligned data succeeds");
825 XCTAssertTrue([self encryptAndDecryptDictionary:aligned_80 key:key], "Roundtrip with aligned data succeeds");
826 }
827
828 - (void)testCKKSKeychainBackedKeySerialization {
829 NSError* error = nil;
830 CKKSKeychainBackedKey* tlk = [self fakeTLK:self.testZoneID].keycore;
831 CKKSKeychainBackedKey* classC = [CKKSKeychainBackedKey randomKeyWrappedByParent:tlk keyclass:SecCKKSKeyClassC error:&error];
832 XCTAssertNil(error, "Should be no error creating classC key");
833
834 XCTAssertTrue([tlk saveKeyMaterialToKeychain:&error], "Should be able to save tlk key material to keychain");
835 XCTAssertNil(error, "Should be no error saving key material");
836
837 XCTAssertTrue([classC saveKeyMaterialToKeychain:&error], "Should be able to save classC key material to keychain");
838 XCTAssertNil(error, "Should be no error saving key material");
839
840 NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
841 [encoder encodeObject:tlk forKey:@"tlk"];
842 [encoder encodeObject:classC forKey:@"classC"];
843 NSData* data = encoder.encodedData;
844 XCTAssertNotNil(data, "encoding should have produced some data");
845
846 NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:nil];
847 CKKSKeychainBackedKey* decodedTLK = [decoder decodeObjectOfClass: [CKKSKeychainBackedKey class] forKey:@"tlk"];
848 CKKSKeychainBackedKey* decodedClassC = [decoder decodeObjectOfClass: [CKKSKeychainBackedKey class] forKey:@"classC"];
849
850 XCTAssertEqualObjects(tlk, decodedTLK, "TLKs should transit NSSecureCoding without changes");
851 XCTAssertEqualObjects(classC, decodedClassC, "Class C keys should transit NSSecureCoding without changes");
852
853 // Now, check that they can load the key material
854
855 XCTAssertTrue([decodedTLK loadKeyMaterialFromKeychain:&error], "Should be able to load tlk key material from keychain");
856 XCTAssertNil(error, "Should be no error from loading key material");
857
858 XCTAssertTrue([decodedClassC loadKeyMaterialFromKeychain:&error], "Should be able to load classC key material from keychain");
859 XCTAssertNil(error, "Should be no error from loading key material");
860 }
861
862 @end
863
864 #endif // OCTAGON