]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSItemEncrypter.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ckks / CKKSItemEncrypter.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 #include <AssertMacros.h>
25 #import <Foundation/Foundation.h>
26 #import <CoreFoundation/CoreFoundation.h>
27 #import <CoreFoundation/CFPropertyList_Private.h>
28
29 #include "keychain/securityd/SecItemSchema.h"
30
31 #import "CKKSItemEncrypter.h"
32 #import "CKKSKeychainView.h"
33
34 #import "CKKSOutgoingQueueEntry.h"
35 #import "CKKSKey.h"
36 #import "CKKSItem.h"
37
38 #if OCTAGON
39
40 @implementation CKKSItemEncrypter
41
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
45 if (blockSize == 0) {
46 blockSize = 1;
47 secwarning("CKKS padding function received invalid blocksize 0, using 1 instead");
48 }
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
52 if (extra) {
53 paddingLength += blockSize;
54 }
55 [data appendData:[NSMutableData dataWithLength:paddingLength]];
56 uint8_t *bytes = [data mutableBytes];
57 bytes[data.length - paddingLength] = CKKS_PADDING_MARK_BYTE;
58 return data;
59 }
60
61 + (NSData *)removePaddingFromData:(NSData * _Nonnull)input {
62 size_t len = input.length;
63 uint8_t const *bytes = [input bytes];
64 size_t idx = len;
65 while (idx > 0) {
66 idx -= 1;
67 switch(bytes[idx]) {
68 case 0:
69 continue;
70 case CKKS_PADDING_MARK_BYTE:
71 return [input subdataWithRange:NSMakeRange(0, idx)];
72 default:
73 return nil;
74 }
75 }
76 return nil;
77 }
78
79 +(CKKSItem*)encryptCKKSItem:(CKKSItem*)baseitem
80 dataDictionary:(NSDictionary *)dict
81 updatingCKKSItem:(CKKSItem*)olditem
82 parentkey:(CKKSKey *)parentkey
83 error:(NSError * __autoreleasing *) error
84 {
85 CKKSKey *itemkey = nil;
86
87 // If we're updating a CKKSItem, extract its dictionary and overlay our new one on top
88 if(olditem) {
89 NSMutableDictionary* oldDictionary = [[CKKSItemEncrypter decryptItemToDictionary: olditem error:error] mutableCopy];
90 if(!oldDictionary) {
91 ckkserror("ckme", olditem.zoneID, "Couldn't decrypt old CKMirror entry: %@", (error ? *error : @"null error passed in"));
92 return nil;
93 }
94
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
98 dict = oldDictionary;
99 }
100
101 // generate a new key and wrap it
102 itemkey = [CKKSKey randomKeyWrappedByParent: parentkey error:error];
103 if(!itemkey) {
104 return nil;
105 }
106
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;
111
112 // if we're updating a v2 item, use v2
113 if(olditem && olditem.encver == CKKSItemEncryptionVersion2 && (int)CKKSItemEncryptionVersion2 >= (int)currentCKKSItemEncryptionVersion) {
114 encryptedItem.encver = CKKSItemEncryptionVersion2;
115 } else {
116 encryptedItem.encver = currentCKKSItemEncryptionVersion;
117 }
118
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];
122 }
123
124 NSDictionary<NSString*, NSData*>* authenticatedData = [encryptedItem makeAuthenticatedDataDictionaryUpdatingCKKSItem: olditem encryptionVersion:encryptedItem.encver];
125
126 encryptedItem.encitem = [self encryptDictionary: dict key:itemkey.aessivkey authenticatedData:authenticatedData error:error];
127 if(!encryptedItem.encitem) {
128 return nil;
129 }
130
131 return encryptedItem;
132 }
133
134 + (NSDictionary*) decryptItemToDictionaryVersionNone: (CKKSItem*) item error: (NSError * __autoreleasing *) error {
135 return [NSPropertyListSerialization propertyListWithData:item.encitem
136 options:(NSPropertyListReadOptions)kCFPropertyListSupportedFormatBinary_v1_0
137 format:nil
138 error:error];
139 }
140
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;
144
145 CKKSAESSIVKey* itemkey = nil;
146
147 CKKSKey* key = [CKKSKey loadKeyWithUUID: item.parentKeyUUID zoneID:item.zoneID error:error];
148 if(!key) {
149 return nil;
150 }
151
152 itemkey = [key unwrapAESKey:item.wrappedkey error:error];
153 if(!itemkey) {
154 return nil;
155 }
156
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];
159
160 NSDictionary* result = [self decryptDictionary: item.encitem key:itemkey authenticatedData:authenticatedData error:error];
161 if(!result) {
162 ckkserror("item", item.zoneID, "ckks: couldn't decrypt item %@", *error);
163 }
164 return result;
165 }
166
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 */
174 default:
175 {
176 NSError *localError = [NSError errorWithDomain:@"securityd"
177 code:1
178 userInfo:@{NSLocalizedDescriptionKey:
179 [NSString stringWithFormat:@"Unrecognized encryption version: %lu", (unsigned long)item.encver]}];
180 ckkserror("item", item.zoneID, "decryptItemToDictionary failed: %@", localError);
181 if (error) {
182 *error = localError;
183 }
184 return nil;
185 }
186 }
187 }
188
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
192 options:0
193 error:error];
194 if(!data) {
195 return nil;
196 }
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];
201
202 return [key encryptData: data authenticatedData: ad error: error];
203 }
204
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];
207 if(!plaintext) {
208 return nil;
209 }
210 plaintext = [CKKSItemEncrypter removePaddingFromData:plaintext];
211 if(!plaintext) {
212 if (error) *error = [NSError errorWithDomain:@"securityd"
213 code:errSecInvalidData
214 userInfo:@{NSLocalizedDescriptionKey: @"Could not remove padding from decrypted item: malformed data"}];
215 return nil;
216 }
217 return [NSPropertyListSerialization propertyListWithData:plaintext
218 options:(NSPropertyListReadOptions)kCFPropertyListSupportedFormatBinary_v1_0
219 format:nil
220 error:error];
221 }
222
223 @end
224
225 #endif