]>
Commit | Line | Data |
---|---|---|
79b9da22 A |
1 | /* |
2 | * Copyright (c) 2018 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 | ||
27 | #include <Security/SecIdentity.h> | |
28 | #include <Security/SecCMS.h> | |
29 | #include <Security/CMSEncoder.h> | |
30 | #include <Security/CMSDecoder.h> | |
b54c578e | 31 | #include <Security/CMSPrivate.h> |
79b9da22 A |
32 | |
33 | #include <utilities/SecCFWrappers.h> | |
34 | ||
35 | #if TARGET_OS_OSX | |
36 | #include <Security/SecKeychain.h> | |
37 | #include <Security/SecImportExport.h> | |
38 | #include <Security/CMSPrivate.h> | |
39 | #endif | |
40 | ||
41 | #include "shared_regressions.h" | |
42 | ||
43 | #include "si-35-cms-expiration-time.h" | |
44 | ||
45 | /* MARK: SecCMS tests */ | |
46 | static void SecCMS_positive_tests(void) { | |
47 | SecPolicyRef policy = NULL; | |
48 | SecTrustRef trust = NULL; | |
49 | CFDictionaryRef tmpAttrs = NULL; | |
50 | NSDictionary* attrs = nil; | |
51 | NSData *expirationDateOid = nil, *unparsedExpirationDate = nil; | |
52 | NSArray *attrValues = nil; | |
53 | NSDate *expirationDate = nil, *expectedDate = [NSDate dateWithTimeIntervalSinceReferenceDate: 599400000.0]; | |
54 | ||
55 | NSData *message = [NSData dataWithBytes:_css_gen_expiration_time length:sizeof(_css_gen_expiration_time)]; | |
56 | NSData *content = [NSData dataWithBytes:_css_content length:sizeof(_css_content)]; | |
57 | policy = SecPolicyCreateBasicX509(); | |
58 | ||
59 | /* verify a valid message and copy out attributes */ | |
60 | ok_status(SecCMSVerifyCopyDataAndAttributes((__bridge CFDataRef)message, (__bridge CFDataRef)content, policy, &trust, NULL, &tmpAttrs), | |
61 | "Failed to verify valid CMS message and get out attributes"); | |
62 | require_action(attrs = CFBridgingRelease(tmpAttrs), exit, fail("Failed to copy attributes")); | |
63 | ||
64 | /* verify we can get the parsed expiration date attribute out */ | |
65 | uint8_t appleExpirationDateOid[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x63, 0x64, 0x9, 0x3 }; | |
66 | expirationDateOid = [NSData dataWithBytes:appleExpirationDateOid length:sizeof(appleExpirationDateOid)]; | |
67 | attrValues = attrs[expirationDateOid]; | |
68 | is([attrValues count], (size_t)1, "Wrong number of attribute values"); | |
69 | require_action(unparsedExpirationDate = attrValues[0], exit, fail("Failed to get expiration date attribute value")); | |
70 | uint8_t expectedUTCData[] = { 0x31, 0x39, 0x31, 0x32, 0x33, 0x30, 0x31, 0x32, 0x30, 0x30, 0x30, 0x30, 0x5a }; | |
71 | is([unparsedExpirationDate isEqualToData:[NSData dataWithBytes:expectedUTCData length:sizeof(expectedUTCData)]], true, "Failed to get correct expiration date"); | |
72 | ||
73 | /* verify we can get the "cooked" expiration data out */ | |
74 | ok(expirationDate = attrs[(__bridge NSString*)kSecCMSExpirationDate], "Failed to get pre-parsed expiration date from attributes"); | |
75 | is([expirationDate isEqualToDate:expectedDate], true, "Failed to get correct expiration date"); | |
76 | ||
77 | exit: | |
78 | CFReleaseNull(policy); | |
79 | CFReleaseNull(trust); | |
80 | } | |
81 | ||
82 | static void SecCMS_negative_date_changed(void) { | |
83 | SecPolicyRef policy = NULL; | |
84 | SecTrustRef trust = NULL; | |
85 | ||
86 | NSMutableData *invalid_message = [NSMutableData dataWithBytes:_css_gen_expiration_time length:sizeof(_css_gen_expiration_time)]; | |
87 | [invalid_message resetBytesInRange:NSMakeRange(3980, 1)]; // reset byte in expiration date attribute of _css_gen_expiration_time | |
88 | NSData *content = [NSData dataWithBytes:_css_content length:sizeof(_css_content)]; | |
89 | policy = SecPolicyCreateBasicX509(); | |
90 | ||
91 | /* Verify message with expiration date changed fails*/ | |
92 | is(SecCMSVerifyCopyDataAndAttributes((__bridge CFDataRef)invalid_message, (__bridge CFDataRef)content, policy, &trust, NULL, NULL), | |
93 | errSecAuthFailed, "Failed to verify valid CMS message and get out attributes"); | |
94 | ||
95 | CFReleaseNull(policy); | |
96 | CFReleaseNull(trust); | |
97 | } | |
98 | ||
99 | static void SecCMS_negative_missing_date(void) { | |
100 | SecPolicyRef policy = NULL; | |
101 | SecTrustRef trust = NULL; | |
102 | CFDictionaryRef tmpAttrs = NULL; | |
103 | NSDictionary *attrs = nil; | |
104 | NSData *expirationDateOid = nil; | |
105 | ||
106 | NSData *message = [NSData dataWithBytes:_no_expiration_attr length:sizeof(_no_expiration_attr)]; | |
107 | policy = SecPolicyCreateBasicX509(); | |
108 | ||
109 | /* verify a message with no expiration date */ | |
110 | ok_status(SecCMSVerifyCopyDataAndAttributes((__bridge CFDataRef)message, NULL, policy, &trust, NULL, &tmpAttrs), | |
111 | "Failed to verify valid CMS message and get out attributes"); | |
112 | require_action(attrs = CFBridgingRelease(tmpAttrs), exit, fail("Failed to copy attributes")); | |
113 | ||
114 | /* verify we can't get the expiration date out */ | |
115 | uint8_t appleExpirationDateOid[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x63, 0x64, 0x9, 0x3 }; | |
116 | expirationDateOid = [NSData dataWithBytes:appleExpirationDateOid length:sizeof(appleExpirationDateOid)]; | |
117 | is(attrs[expirationDateOid], NULL, "Got an expiration date attribute from message with no expiration date"); | |
118 | is(attrs[(__bridge NSString*)kSecCMSExpirationDate], NULL, "Got an expiration date attribute from message with no expiration date"); | |
119 | ||
120 | exit: | |
121 | CFReleaseNull(policy); | |
122 | CFReleaseNull(trust); | |
123 | } | |
124 | ||
125 | static void SecCMS_tests(void) { | |
126 | SecCMS_positive_tests(); | |
127 | SecCMS_negative_date_changed(); | |
128 | SecCMS_negative_missing_date(); | |
129 | } | |
130 | ||
131 | /* MARK: CMSEncoder tests */ | |
132 | static void CMSEncoder_tests(SecIdentityRef identity) { | |
133 | CMSEncoderRef encoder = NULL; | |
134 | CMSDecoderRef decoder = NULL; | |
135 | CFDataRef message = NULL; | |
136 | ||
137 | /* Create encoder */ | |
138 | require_noerr_action(CMSEncoderCreate(&encoder), exit, fail("Failed to create CMS encoder")); | |
139 | require_noerr_action(CMSEncoderSetSignerAlgorithm(encoder, kCMSEncoderDigestAlgorithmSHA256), exit, | |
140 | fail("Failed to set digest algorithm to SHA256")); | |
141 | ||
142 | /* Set identity as signer */ | |
143 | require_noerr_action(CMSEncoderAddSigners(encoder, identity), exit, fail("Failed to add signer identity")); | |
144 | ||
145 | /* Add signing time attribute for 6 June 2018 */ | |
146 | require_noerr_action(CMSEncoderAddSignedAttributes(encoder, kCMSAttrSigningTime), exit, | |
147 | fail("Failed to set signing time flag")); | |
148 | require_noerr_action(CMSEncoderSetSigningTime(encoder, 550000000.0), exit, fail("Failed to set signing time")); | |
149 | ||
150 | /* Add expiration date attribute for 30 September 2018 */ | |
151 | ok_status(CMSEncoderAddSignedAttributes(encoder, kCMSAttrAppleExpirationTime), | |
152 | "Set expiration date flag"); | |
153 | ok_status(CMSEncoderSetAppleExpirationTime(encoder, 560000000.0), "Set Expiration time"); | |
154 | ||
155 | /* Load content */ | |
156 | require_noerr_action(CMSEncoderSetHasDetachedContent(encoder, true), exit, fail("Failed to set detached content")); | |
157 | require_noerr_action(CMSEncoderUpdateContent(encoder, _css_content, sizeof(_css_content)), exit, fail("Failed to set content")); | |
158 | ||
159 | /* output cms message */ | |
160 | ok_status(CMSEncoderCopyEncodedContent(encoder, &message), "Finish encoding and output message"); | |
161 | isnt(message, NULL, "Encoded message exists"); | |
162 | ||
163 | /* decode message */ | |
164 | require_noerr_action(CMSDecoderCreate(&decoder), exit, fail("Create CMS decoder")); | |
165 | require_noerr_action(CMSDecoderUpdateMessage(decoder, CFDataGetBytePtr(message), | |
166 | CFDataGetLength(message)), exit, | |
167 | fail("Update decoder with CMS message")); | |
168 | require_noerr_action(CMSDecoderSetDetachedContent(decoder, (__bridge CFDataRef)[NSData dataWithBytes:_css_content | |
169 | length:sizeof(_css_content)]), | |
170 | exit, fail("Set detached content")); | |
171 | ok_status(CMSDecoderFinalizeMessage(decoder), "Finalize decoder"); | |
172 | ||
173 | exit: | |
174 | CFReleaseNull(encoder); | |
175 | CFReleaseNull(message); | |
176 | CFReleaseNull(decoder); | |
177 | } | |
178 | ||
179 | /* MARK: CMSDecoder tests */ | |
180 | static void CMSDecoder_positive_tests(void) { | |
181 | CMSDecoderRef decoder = NULL; | |
182 | SecPolicyRef policy = NULL; | |
183 | SecTrustRef trust = NULL; | |
184 | CMSSignerStatus signerStatus; | |
185 | NSData *_css_contentData = nil; | |
186 | CFAbsoluteTime expirationTime = 0; | |
187 | NSDate *expirationDate = nil, *expectedDate = [NSDate dateWithTimeIntervalSinceReferenceDate: 599400000.0]; | |
188 | ||
189 | /* Create decoder and decode */ | |
190 | require_noerr_action(CMSDecoderCreate(&decoder), exit, fail("Failed to create CMS decoder")); | |
191 | require_noerr_action(CMSDecoderUpdateMessage(decoder, _css_gen_expiration_time, sizeof(_css_gen_expiration_time)), exit, | |
192 | fail("Failed to update decoder with CMS message")); | |
193 | _css_contentData = [NSData dataWithBytes:_css_content length:sizeof(_css_content)]; | |
194 | require_noerr_action(CMSDecoderSetDetachedContent(decoder, (__bridge CFDataRef)_css_contentData), exit, | |
195 | fail("Failed to set detached _css_content")); | |
196 | ok_status(CMSDecoderFinalizeMessage(decoder), "Finalize decoder"); | |
197 | ||
198 | /* Get signer status */ | |
199 | require_action(policy = SecPolicyCreateBasicX509(), exit, fail("Failed to Create policy")); | |
200 | ok_status(CMSDecoderCopySignerStatus(decoder, 0, policy, false, &signerStatus, &trust, NULL), | |
201 | "Copy Signer status"); | |
202 | is(signerStatus, kCMSSignerValid, "Valid signature"); | |
203 | ||
204 | /* Get Expiration Time Attribute value */ | |
205 | ok_status(CMSDecoderCopySignerAppleExpirationTime(decoder, 0, &expirationTime), | |
206 | "Got expiration time from message with no expiration attribute"); | |
207 | expirationDate = [NSDate dateWithTimeIntervalSinceReferenceDate:expirationTime]; | |
208 | is([expirationDate isEqualToDate:expectedDate], true, "Got wrong expiration time"); // 31 December 2019 12:00:00 Zulu | |
209 | ||
210 | exit: | |
211 | CFReleaseNull(decoder); | |
212 | CFReleaseNull(policy); | |
213 | CFReleaseNull(trust); | |
214 | } | |
215 | ||
216 | static void CMSDecoder_negative_date_changed(void) { | |
217 | CMSDecoderRef decoder = NULL; | |
218 | SecPolicyRef policy = NULL; | |
219 | SecTrustRef trust = NULL; | |
220 | CMSSignerStatus signerStatus; | |
221 | NSData *_css_contentData = nil; | |
222 | NSMutableData *invalid_message = nil; | |
223 | ||
224 | /* Create decoder and decode */ | |
225 | invalid_message = [NSMutableData dataWithBytes:_css_gen_expiration_time length:sizeof(_css_gen_expiration_time)]; | |
226 | [invalid_message resetBytesInRange:NSMakeRange(3980, 1)]; // reset byte in expiration date attribute of _css_gen_expiration_time | |
227 | require_noerr_action(CMSDecoderCreate(&decoder), exit, fail("Failed to create CMS decoder")); | |
228 | require_noerr_action(CMSDecoderUpdateMessage(decoder, [invalid_message bytes], [invalid_message length]), exit, | |
229 | fail("Failed to update decoder with CMS message")); | |
230 | _css_contentData = [NSData dataWithBytes:_css_content length:sizeof(_css_content)]; | |
231 | require_noerr_action(CMSDecoderSetDetachedContent(decoder, (__bridge CFDataRef)_css_contentData), exit, | |
232 | fail("Failed to set detached _css_content")); | |
233 | ok_status(CMSDecoderFinalizeMessage(decoder), "Finalize decoder"); | |
234 | ||
235 | /* Get signer status */ | |
236 | require_action(policy = SecPolicyCreateBasicX509(), exit, fail("Failed to Create policy")); | |
237 | ok_status(CMSDecoderCopySignerStatus(decoder, 0, policy, false, &signerStatus, &trust, NULL), | |
238 | "Copy Signer status"); | |
239 | is(signerStatus, kCMSSignerInvalidSignature, "Valid signature"); | |
240 | ||
241 | exit: | |
242 | CFReleaseNull(decoder); | |
243 | CFReleaseNull(policy); | |
244 | CFReleaseNull(trust); | |
245 | } | |
246 | ||
247 | static void CMSDecoder_negative_missing_date(void) { | |
248 | CMSDecoderRef decoder = NULL; | |
249 | SecPolicyRef policy = NULL; | |
250 | SecTrustRef trust = NULL; | |
251 | CMSSignerStatus signerStatus; | |
252 | CFAbsoluteTime expirationTime = 0.0; | |
253 | ||
254 | /* Create decoder and decode */ | |
255 | require_noerr_action(CMSDecoderCreate(&decoder), exit, fail("Failed to create CMS decoder")); | |
256 | require_noerr_action(CMSDecoderUpdateMessage(decoder, _no_expiration_attr, sizeof(_no_expiration_attr)), exit, | |
257 | fail("Failed to update decoder with CMS message")); | |
258 | ok_status(CMSDecoderFinalizeMessage(decoder), "Finalize decoder"); | |
259 | ||
260 | /* Get signer status */ | |
261 | require_action(policy = SecPolicyCreateBasicX509(), exit, fail("Failed to Create policy")); | |
262 | ok_status(CMSDecoderCopySignerStatus(decoder, 0, policy, false, &signerStatus, &trust, NULL), | |
263 | "Copy Signer status"); | |
264 | is(signerStatus, kCMSSignerValid, "Valid signature"); | |
265 | ||
266 | /* Get Expiration Time Attribute value */ | |
267 | is(CMSDecoderCopySignerAppleExpirationTime(decoder, 0, &expirationTime), -1, | |
268 | "Got expiration time from message with no expiration attribute"); | |
269 | ||
270 | exit: | |
271 | CFReleaseNull(decoder); | |
272 | CFReleaseNull(policy); | |
273 | CFReleaseNull(trust); | |
274 | } | |
275 | ||
276 | static void CMSDecoder_tests(void) { | |
277 | CMSDecoder_positive_tests(); | |
278 | CMSDecoder_negative_date_changed(); | |
279 | CMSDecoder_negative_missing_date(); | |
280 | } | |
281 | ||
282 | static bool setup_keychain(const uint8_t *p12, size_t p12_len, SecIdentityRef *identity) { | |
283 | CFArrayRef tmp_imported_items = NULL; | |
284 | NSArray *imported_items = nil; | |
285 | ||
286 | NSDictionary *options = @{ (__bridge NSString *)kSecImportExportPassphrase : @"password" }; | |
287 | NSData *p12Data = [NSData dataWithBytes:signing_identity_p12 length:sizeof(signing_identity_p12)]; | |
288 | require_noerr_action(SecPKCS12Import((__bridge CFDataRef)p12Data, (__bridge CFDictionaryRef)options, | |
289 | &tmp_imported_items), exit, | |
290 | fail("Failed to import identity")); | |
291 | imported_items = CFBridgingRelease(tmp_imported_items); | |
292 | require_noerr_action([imported_items count] == 0 && | |
293 | [imported_items[0] isKindOfClass:[NSDictionary class]], exit, | |
294 | fail("Wrong imported items output")); | |
295 | *identity = (SecIdentityRef)CFBridgingRetain(imported_items[0][(__bridge NSString*)kSecImportItemIdentity]); | |
296 | require_action(*identity, exit, fail("Failed to get identity")); | |
297 | ||
298 | return true; | |
299 | ||
300 | exit: | |
301 | return false; | |
302 | } | |
303 | ||
304 | static void cleanup_keychain(SecIdentityRef identity) { | |
305 | #if TARGET_OS_OSX | |
306 | // SecPKCS12Import adds the items to the keychain on macOS | |
307 | NSDictionary *query = @{ (__bridge NSString*)kSecValueRef : (__bridge id)identity }; | |
308 | ok_status(SecItemDelete((__bridge CFDictionaryRef)query), "failed to remove identity from keychain"); | |
309 | #else | |
310 | pass("skip test on iOS"); | |
311 | #endif | |
312 | CFReleaseNull(identity); | |
313 | } | |
314 | ||
315 | int si_35_cms_expiration_time(int argc, char *const *argv) { | |
316 | plan_tests(5+1+3+5+5+3+4+1); | |
317 | ||
318 | SecIdentityRef identity = NULL; | |
319 | ||
320 | if (setup_keychain(signing_identity_p12 , sizeof(signing_identity_p12), &identity)) { | |
321 | SecCMS_tests(); | |
322 | CMSEncoder_tests(identity); | |
323 | CMSDecoder_tests(); | |
b54c578e | 324 | cleanup_keychain(identity); |
79b9da22 A |
325 | } |
326 | ||
79b9da22 A |
327 | return 0; |
328 | } |