2 * Copyright (c) 2019 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>
27 #include <Security/SecCertificatePriv.h>
28 #include <Security/SecIdentity.h>
29 #include <Security/SecCMS.h>
30 #include <Security/CMSEncoder.h>
31 #include <Security/CMSDecoder.h>
32 #include <Security/SecImportExport.h>
33 #include <Security/SecCmsBase.h>
35 #include <utilities/SecCFWrappers.h>
38 #include <Security/SecKeychain.h>
39 #define kSystemLoginKeychainPath "/Library/Keychains/System.keychain"
42 #include "shared_regressions.h"
44 #include "si-29-cms-chain-mode.h"
46 static NSData* CMSEncoder_encode_for_chain_mode(SecIdentityRef identity, CMSCertificateChainMode chainMode) {
47 CMSEncoderRef encoder = NULL;
48 CFDataRef message = NULL;
51 require_noerr_action(CMSEncoderCreate(&encoder), exit, fail("Failed to create CMS encoder"));
52 require_noerr_action(CMSEncoderSetSignerAlgorithm(encoder, kCMSEncoderDigestAlgorithmSHA256), exit,
53 fail("Failed to set digest algorithm to SHA256"));
55 /* Set identity as signer */
56 require_noerr_action(CMSEncoderAddSigners(encoder, identity), exit, fail("Failed to add signer identity"));
59 require_noerr_action(CMSEncoderSetCertificateChainMode(encoder, chainMode), exit, fail("Failed to set chain mode"));
62 CMSEncoderUpdateContent(encoder, _chain_mode_content, sizeof(_chain_mode_content));
64 /* output cms message */
65 CMSEncoderCopyEncodedContent(encoder, &message);
68 CFReleaseNull(encoder);
69 return CFBridgingRelease(message);
72 static NSData* SecCMS_encode_for_chain_mode(SecIdentityRef identity, SecCmsCertChainMode chainMode) {
73 NSMutableData *data = [NSMutableData data];
74 NSData *content = [NSData dataWithBytes:_chain_mode_content length:sizeof(_chain_mode_content)];
75 NSDictionary *parameters = @{
76 (__bridge NSString*)kSecCMSSignHashAlgorithm : (__bridge NSString*)kSecCMSHashingAlgorithmSHA256,
77 (__bridge NSString*)kSecCMSCertChainMode : [NSString stringWithFormat:@"%d", chainMode],
79 SecCMSCreateSignedData(identity, (__bridge CFDataRef)content, (__bridge CFDictionaryRef)parameters, nil, (__bridge CFMutableDataRef)data);
81 if (data.length > 0) {
87 static NSArray* CMSDecoder_copy_certs(NSData *cms_message) {
88 CMSDecoderRef decoder = NULL;
89 CFArrayRef certs = NULL;
95 require_noerr_action(CMSDecoderCreate(&decoder), exit, fail("Failed to create CMS decoder"));
96 require_noerr_action(CMSDecoderUpdateMessage(decoder, cms_message.bytes, cms_message.length), exit,
97 fail("Failed to update decoder with CMS message"));
98 require_noerr_action(CMSDecoderFinalizeMessage(decoder), exit, fail("Failed to finalize decoder"));
99 ok_status(CMSDecoderCopyAllCerts(decoder, &certs), "Failed to get certs from cms message");
102 CFReleaseNull(decoder);
103 return CFBridgingRelease(certs);
106 static void SecCMS_root_unavailable_tests(SecIdentityRef identity) {
107 /* Chain Mode None */
108 NSData *cms_message = SecCMS_encode_for_chain_mode(identity, SecCmsCMNone);
109 NSArray *certs = CMSDecoder_copy_certs(cms_message);
110 is(0, certs.count, "Expected 0 certs, got %lu", (unsigned long)certs.count);
112 /* Chain Mode: Signer */
113 cms_message = SecCMS_encode_for_chain_mode(identity, SecCmsCMCertOnly);
114 certs = CMSDecoder_copy_certs(cms_message);
115 is(certs.count, 1, "Expected 1 certs, got %lu", (unsigned long)certs.count);
117 /* Chain Mode: Chain */
118 cms_message = SecCMS_encode_for_chain_mode(identity, SecCmsCMCertChain);
119 certs = CMSDecoder_copy_certs(cms_message);
120 is(certs.count, 2, "Expected 2 certs, got %lu", (unsigned long)certs.count);
122 /* Chain Mode: Chain With Root */
123 cms_message = SecCMS_encode_for_chain_mode(identity, SecCmsCMCertChainWithRoot);
124 certs = CMSDecoder_copy_certs(cms_message);
125 is(certs.count, 2, "Expected 2 certs, got %lu", (unsigned long)certs.count);
127 /* Chain Mode: Chain With Root or Fail */
128 /* We shouldn't be able to find the root, so we shouldn't be able to make the CMS message */
129 cms_message = SecCMS_encode_for_chain_mode(identity, SecCmsCMCertChainWithRootOrFail);
130 ok(cms_message == nil, "Expected to fail to encode CMS message without root in keychain");
133 static void CMSEncoder_root_unavailable_tests(SecIdentityRef identity) {
134 /* Chain Mode None */
135 NSData *cms_message = CMSEncoder_encode_for_chain_mode(identity, kCMSCertificateNone);
136 NSArray *certs = CMSDecoder_copy_certs(cms_message);
137 is(certs.count, 0, "Expected 0 certs, got %lu", (unsigned long)certs.count);
139 /* Chain Mode: Signer */
140 cms_message = CMSEncoder_encode_for_chain_mode(identity, kCMSCertificateSignerOnly);
141 certs = CMSDecoder_copy_certs(cms_message);
142 is(certs.count, 1, "Expected 1 certs, got %lu", (unsigned long)certs.count);
144 /* Chain Mode: Chain */
145 cms_message = CMSEncoder_encode_for_chain_mode(identity, kCMSCertificateChain);
146 certs = CMSDecoder_copy_certs(cms_message);
147 is(certs.count, 2, "Expected 2 certs, got %lu", (unsigned long)certs.count);
149 /* Chain Mode: Chain With Root */
150 cms_message = CMSEncoder_encode_for_chain_mode(identity, kCMSCertificateChainWithRoot);
151 certs = CMSDecoder_copy_certs(cms_message);
152 is(certs.count, 2, "Expected 2 certs, got %lu", (unsigned long)certs.count);
154 /* Chain Mode: Chain With Root or Fail */
155 /* We shouldn't be able to find the root, so we shouldn't be able to make the CMS message */
156 cms_message = CMSEncoder_encode_for_chain_mode(identity, kCMSCertificateChainWithRootOrFail);
157 ok(cms_message == nil, "Expected to fail to encode CMS message without root in keychain");
160 static void SecCMS_root_available_tests(SecIdentityRef identity) {
161 /* Chain Mode None */
162 NSData *cms_message = SecCMS_encode_for_chain_mode(identity, SecCmsCMNone);
163 NSArray *certs = CMSDecoder_copy_certs(cms_message);
164 is(certs.count, 0, "Expected 0 certs, got %lu", (unsigned long)certs.count);
166 /* Chain Mode: Signer */
167 cms_message = SecCMS_encode_for_chain_mode(identity, SecCmsCMCertOnly);
168 certs = CMSDecoder_copy_certs(cms_message);
169 is(certs.count, 1, "Expected 1 certs, got %lu", (unsigned long)certs.count);
171 /* Chain Mode: Chain */
172 cms_message = SecCMS_encode_for_chain_mode(identity, SecCmsCMCertChain);
173 certs = CMSDecoder_copy_certs(cms_message);
174 is(certs.count, 2, "Expected 2 certs, got %lu", (unsigned long)certs.count);
176 /* Chain Mode: Chain With Root */
177 cms_message = SecCMS_encode_for_chain_mode(identity, SecCmsCMCertChainWithRoot);
178 certs = CMSDecoder_copy_certs(cms_message);
179 is(certs.count, 3, "Expected 3 certs, got %lu", (unsigned long)certs.count);
181 /* Chain Mode: Chain With Root or Fail */
182 cms_message = SecCMS_encode_for_chain_mode(identity, SecCmsCMCertChainWithRootOrFail);
183 certs = CMSDecoder_copy_certs(cms_message);
184 is(certs.count, 3, "Expected 3 certs, got %lu", (unsigned long)certs.count);
187 static void CMSEncoder_root_available_tests(SecIdentityRef identity) {
188 /* Chain Mode None */
189 NSData *cms_message = CMSEncoder_encode_for_chain_mode(identity, kCMSCertificateNone);
190 NSArray *certs = CMSDecoder_copy_certs(cms_message);
191 is(certs.count, 0, "Expected 0 certs, got %lu", (unsigned long)certs.count);
193 /* Chain Mode: Signer */
194 cms_message = CMSEncoder_encode_for_chain_mode(identity, kCMSCertificateSignerOnly);
195 certs = CMSDecoder_copy_certs(cms_message);
196 is(certs.count, 1, "Expected 1 certs, got %lu", (unsigned long)certs.count);
198 /* Chain Mode: Chain */
199 cms_message = CMSEncoder_encode_for_chain_mode(identity, kCMSCertificateChain);
200 certs = CMSDecoder_copy_certs(cms_message);
201 is(certs.count, 2, "Expected 2 certs, got %lu", (unsigned long)certs.count);
203 /* Chain Mode: Chain With Root */
204 cms_message = CMSEncoder_encode_for_chain_mode(identity, kCMSCertificateChainWithRoot);
205 certs = CMSDecoder_copy_certs(cms_message);
206 is(certs.count, 3, "Expected 3 certs, got %lu", (unsigned long)certs.count);
208 /* Chain Mode: Chain With Root or Fail */
209 cms_message = CMSEncoder_encode_for_chain_mode(identity, kCMSCertificateChainWithRootOrFail);
210 certs = CMSDecoder_copy_certs(cms_message);
211 is(certs.count, 3, "Expected 3 certs, got %lu", (unsigned long)certs.count);
214 static bool setup_keychain(const uint8_t *p12, size_t p12_len, SecIdentityRef *identity) {
215 CFArrayRef tmp_imported_items = NULL;
216 NSArray *imported_items = nil;
218 NSDictionary *options = @{ (__bridge NSString *)kSecImportExportPassphrase : @"password" };
219 NSData *p12Data = [NSData dataWithBytes:p12 length:p12_len];
220 require_noerr_action(SecPKCS12Import((__bridge CFDataRef)p12Data, (__bridge CFDictionaryRef)options,
221 &tmp_imported_items), exit,
222 fail("Failed to import identity"));
223 imported_items = CFBridgingRelease(tmp_imported_items);
224 require_noerr_action([imported_items count] == 0 &&
225 [imported_items[0] isKindOfClass:[NSDictionary class]], exit,
226 fail("Wrong imported items output"));
227 *identity = (SecIdentityRef)CFBridgingRetain(imported_items[0][(__bridge NSString*)kSecImportItemIdentity]);
228 require_action(*identity, exit, fail("Failed to get identity"));
236 static void cleanup_keychain(SecIdentityRef identity, NSArray *certs) {
238 // SecPKCS12Import adds the items to the keychain on macOS
239 NSDictionary *query = @{ (__bridge NSString*)kSecValueRef : (__bridge id)identity };
240 ok_status(SecItemDelete((__bridge CFDictionaryRef)query), "failed to remove identity from keychain");
242 pass("skip test on iOS");
244 for(id cert in certs) {
245 NSDictionary *cert_query = @{ (__bridge NSString*)kSecValueRef : cert };
246 ok_status(SecItemDelete((__bridge CFDictionaryRef)cert_query), "failed to remove cert from keychain");
250 static void add_cert_to_keychain(SecCertificateRef cert) {
252 SecKeychainRef kcRef = NULL;
253 SecKeychainOpen(kSystemLoginKeychainPath, &kcRef);
255 fail("failed to open system keychain");
258 NSDictionary *query = @{
259 (__bridge NSString*)kSecValueRef : (__bridge id)cert,
260 (__bridge NSString*)kSecUseKeychain: (__bridge id)kcRef,
263 NSDictionary *query = @{ (__bridge NSString*)kSecValueRef : (__bridge id)cert};
265 ok_status(SecItemAdd((__bridge CFDictionaryRef)query, NULL), "failed to add cert to keychain. following tests may fail.");
268 int si_29_cms_chain_mode(int argc, char *const *argv) {
271 SecIdentityRef identity = NULL;
272 SecCertificateRef root = SecCertificateCreateWithBytes(NULL, _chain_mode_root, sizeof(_chain_mode_root));
273 SecCertificateRef intermediate = SecCertificateCreateWithBytes(NULL, _chain_mode_subca, sizeof(_chain_mode_subca));
275 if (setup_keychain(_chain_mode_leaf_p12 , sizeof(_chain_mode_leaf_p12), &identity)) {
276 add_cert_to_keychain(intermediate);
278 SecCMS_root_unavailable_tests(identity);
279 CMSEncoder_root_unavailable_tests(identity);
281 add_cert_to_keychain(root);
283 SecCMS_root_available_tests(identity);
284 CMSEncoder_root_available_tests(identity);
286 cleanup_keychain(identity, @[(__bridge id) intermediate, (__bridge id)root]);
289 CFReleaseNull(identity);
291 CFReleaseNull(intermediate);