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