2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
24 #include <AssertMacros.h>
25 #import <Foundation/Foundation.h>
26 #import <CoreFoundation/CoreFoundation.h>
27 #import <CoreFoundation/CFPropertyList_Private.h>
29 #include <securityd/SecItemSchema.h>
31 #import "CKKSItemEncrypter.h"
32 #import "CKKSKeychainView.h"
34 #import "CKKSOutgoingQueueEntry.h"
40 @implementation CKKSItemEncrypter
42 // Make it harder to distinguish password lengths especially when password is short.
43 + (NSData *)padData:(NSData * _Nonnull)input blockSize:(NSUInteger)blockSize additionalBlock:(BOOL)extra {
44 // Must apply padding otherwise unpadding will choke
47 secwarning("CKKS padding function received invalid blocksize 0, using 1 instead");
49 NSMutableData *data = [NSMutableData dataWithData:input];
50 NSUInteger paddingLength = blockSize - (data.length % blockSize);
51 // Short password as determined by caller: make sure there's at least one block of padding
53 paddingLength += blockSize;
55 [data appendData:[NSMutableData dataWithLength:paddingLength]];
56 uint8_t *bytes = [data mutableBytes];
57 bytes[data.length - paddingLength] = CKKS_PADDING_MARK_BYTE;
61 + (NSData *)removePaddingFromData:(NSData * _Nonnull)input {
62 size_t len = input.length;
63 uint8_t const *bytes = [input bytes];
70 case CKKS_PADDING_MARK_BYTE:
71 return [input subdataWithRange:NSMakeRange(0, idx)];
79 +(CKKSItem*)encryptCKKSItem:(CKKSItem*)baseitem
80 dataDictionary:(NSDictionary *)dict
81 updatingCKKSItem:(CKKSItem*)olditem
82 parentkey:(CKKSKey *)parentkey
83 error:(NSError * __autoreleasing *) error
85 CKKSKey *itemkey = nil;
87 // If we're updating a CKKSItem, extract its dictionary and overlay our new one on top
89 NSMutableDictionary* oldDictionary = [[CKKSItemEncrypter decryptItemToDictionary: olditem error:error] mutableCopy];
91 secerror("Couldn't decrypt old CKMirror entry: %@", (error ? *error : @"null error passed in"));
95 // Use oldDictionary as a base, and copy in the objects from dict
96 [oldDictionary addEntriesFromDictionary: dict];
97 // and replace the dictionary with the new one
101 // generate a new key and wrap it
102 itemkey = [CKKSKey randomKeyWrappedByParent: parentkey error:error];
107 // Prepare the authenticated data dictionary. It includes the wrapped key, so prepare that first
108 CKKSItem* encryptedItem = [[CKKSItem alloc] initCopyingCKKSItem: baseitem];
109 encryptedItem.parentKeyUUID = parentkey.uuid;
110 encryptedItem.wrappedkey = itemkey.wrappedkey;
112 // if we're updating a v2 item, use v2
113 if(olditem && olditem.encver == CKKSItemEncryptionVersion2 && (int)CKKSItemEncryptionVersion2 >= (int)currentCKKSItemEncryptionVersion) {
114 encryptedItem.encver = CKKSItemEncryptionVersion2;
116 encryptedItem.encver = currentCKKSItemEncryptionVersion;
119 // Copy over the old item's CKRecord, updated for the new item data
120 if(olditem.storedCKRecord) {
121 encryptedItem.storedCKRecord = [encryptedItem updateCKRecord:olditem.storedCKRecord zoneID:olditem.storedCKRecord.recordID.zoneID];
124 NSDictionary<NSString*, NSData*>* authenticatedData = [encryptedItem makeAuthenticatedDataDictionaryUpdatingCKKSItem: olditem encryptionVersion:encryptedItem.encver];
126 encryptedItem.encitem = [self encryptDictionary: dict key:itemkey.aessivkey authenticatedData:authenticatedData error:error];
127 if(!encryptedItem.encitem) {
131 return encryptedItem;
134 + (NSDictionary*) decryptItemToDictionaryVersionNone: (CKKSItem*) item error: (NSError * __autoreleasing *) error {
135 return [NSPropertyListSerialization propertyListWithData:item.encitem
136 options:(NSPropertyListReadOptions)kCFPropertyListSupportedFormatBinary_v1_0
141 // Note that the only difference between v1 and v2 is the authenticated data selection, so we can happily pass encver along
142 + (NSDictionary*) decryptItemToDictionaryVersion1or2: (CKKSItem*) item error: (NSError * __autoreleasing *) error {
143 NSDictionary* authenticatedData = nil;
145 CKKSAESSIVKey* itemkey = nil;
147 CKKSKey* key = [CKKSKey loadKeyWithUUID: item.parentKeyUUID zoneID:item.zoneID error:error];
152 itemkey = [key unwrapAESKey:item.wrappedkey error:error];
157 // Prepare the authenticated data dictionary (pass the item as the futureproofed object, so we'll authenticate any new fields in this particular item).
158 authenticatedData = [item makeAuthenticatedDataDictionaryUpdatingCKKSItem:item encryptionVersion:item.encver];
160 NSDictionary* result = [self decryptDictionary: item.encitem key:itemkey authenticatedData:authenticatedData error:error];
162 secwarning("ckks: couldn't decrypt item %@", *error);
167 + (NSDictionary*) decryptItemToDictionary: (CKKSItem*) item error: (NSError * __autoreleasing *) error {
168 switch (item.encver) {
169 case CKKSItemEncryptionVersion1:
170 return [CKKSItemEncrypter decryptItemToDictionaryVersion1or2:item error:error];
171 case CKKSItemEncryptionVersion2:
172 return [CKKSItemEncrypter decryptItemToDictionaryVersion1or2:item error:error];
173 case CKKSItemEncryptionVersionNone: /* version 0 was no encrypted, no longer supported */
176 NSError *localError = [NSError errorWithDomain:@"securityd"
178 userInfo:@{NSLocalizedDescriptionKey:
179 [NSString stringWithFormat:@"Unrecognized encryption version: %lu", (unsigned long)item.encver]}];
180 secerror("decryptItemToDictionary %@", localError);
189 + (NSData*)encryptDictionary: (NSDictionary*) dict key: (CKKSAESSIVKey*) key authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error {
190 NSData* data = [NSPropertyListSerialization dataWithPropertyList:dict
191 format:NSPropertyListBinaryFormat_v1_0
197 // Hide password length. Apply extra padding to short passwords.
198 data = [CKKSItemEncrypter padData:data
199 blockSize:SecCKKSItemPaddingBlockSize
200 additionalBlock:[(NSData *)dict[@"v_Data"] length] < SecCKKSItemPaddingBlockSize];
202 return [key encryptData: data authenticatedData: ad error: error];
205 + (NSDictionary*)decryptDictionary: (NSData*) encitem key: (CKKSAESSIVKey*) aeskey authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error {
206 NSData* plaintext = [aeskey decryptData: encitem authenticatedData: ad error: error];
210 plaintext = [CKKSItemEncrypter removePaddingFromData:plaintext];
212 if (error) *error = [NSError errorWithDomain:@"securityd"
213 code:errSecInvalidData
214 userInfo:@{NSLocalizedDescriptionKey: @"Could not remove padding from decrypted item: malformed data"}];
217 return [NSPropertyListSerialization propertyListWithData:plaintext
218 options:(NSPropertyListReadOptions)kCFPropertyListSupportedFormatBinary_v1_0