2 * Copyright (c) 2003-2010,2012,2014 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@
26 #include "identity_find.h"
27 #include "keychain_utilities.h"
28 #include "trusted_cert_utils.h"
35 #include <Security/cssmtype.h>
36 #include <Security/oidsalg.h>
37 #include <Security/SecCertificate.h>
38 #include <Security/SecIdentity.h>
39 #include <Security/SecIdentitySearch.h>
40 #include <Security/SecPolicySearch.h>
41 #include <Security/SecTrust.h>
44 #include <Security/SecBasePriv.h>
45 // SecCertificateInferLabel, SecDigestGetData
46 #include <Security/SecCertificatePriv.h>
47 // SecIdentitySearchCreateWithPolicy
48 #include <Security/SecIdentitySearchPriv.h>
52 find_identity(CFTypeRef keychainOrArray
,
57 SecIdentityRef identityRef
= NULL
;
58 SecIdentitySearchRef searchRef
= NULL
;
59 OSStatus status
= SecIdentitySearchCreate(keychainOrArray
, keyUsage
, &searchRef
);
64 // check input hash string and convert to data
65 CSSM_DATA hashData
= { 0, NULL
};
67 CSSM_SIZE len
= strlen(hash
)/2;
68 hashData
.Length
= len
;
69 hashData
.Data
= (uint8
*)malloc(hashData
.Length
);
70 fromHex(hash
, &hashData
);
73 // filter candidates against the hash (or the name, if no hash provided)
74 CFStringRef matchRef
= (identity
) ? CFStringCreateWithCString(NULL
, identity
, kCFStringEncodingUTF8
) : NULL
;
75 Boolean exactMatch
= FALSE
;
77 CSSM_DATA certData
= { 0, NULL
};
78 SecIdentityRef candidate
= NULL
;
80 while (SecIdentitySearchCopyNext(searchRef
, &candidate
) == noErr
) {
81 SecCertificateRef cert
= NULL
;
82 if (SecIdentityCopyCertificate(candidate
, &cert
) != noErr
) {
83 safe_CFRelease(&candidate
);
86 if (SecCertificateGetData(cert
, &certData
) != noErr
) {
87 safe_CFRelease(&cert
);
88 safe_CFRelease(&candidate
);
92 uint8 candidate_sha1_hash
[20];
94 digest
.Length
= sizeof(candidate_sha1_hash
);
95 digest
.Data
= candidate_sha1_hash
;
96 if ((SecDigestGetData(CSSM_ALGID_SHA1
, &digest
, &certData
) == CSSM_OK
) &&
97 (hashData
.Length
== digest
.Length
) &&
98 (!memcmp(hashData
.Data
, digest
.Data
, digest
.Length
))) {
99 identityRef
= candidate
; // currently retained
100 safe_CFRelease(&cert
);
101 break; // we're done - can't get more exact than this
104 // copy certificate name
105 CFStringRef nameRef
= NULL
;
106 if ((SecCertificateCopyCommonName(cert
, &nameRef
) != noErr
) || nameRef
== NULL
) {
107 safe_CFRelease(&cert
);
108 safe_CFRelease(&candidate
);
109 continue; // no name, so no match is possible
111 CFIndex nameLen
= CFStringGetLength(nameRef
);
112 CFIndex bufLen
= 1 + CFStringGetMaximumSizeForEncoding(nameLen
, kCFStringEncodingUTF8
);
113 char *nameBuf
= (char *)malloc(bufLen
);
114 if (!CFStringGetCString(nameRef
, nameBuf
, bufLen
-1, kCFStringEncodingUTF8
))
117 if (!strcmp(identity
, "*")) { // special case: means "just take the first one"
118 sec_error("Using identity \"%s\"", nameBuf
);
119 identityRef
= candidate
; // currently retained
121 safe_CFRelease(&nameRef
);
122 safe_CFRelease(&cert
);
125 CFRange find
= { kCFNotFound
, 0 };
126 if (nameRef
&& matchRef
)
127 find
= CFStringFind(nameRef
, matchRef
, kCFCompareCaseInsensitive
| kCFCompareNonliteral
);
128 Boolean isExact
= (find
.location
== 0 && find
.length
== nameLen
);
129 if (find
.location
== kCFNotFound
) {
131 safe_CFRelease(&nameRef
);
132 safe_CFRelease(&cert
);
133 safe_CFRelease(&candidate
);
134 continue; // no match
136 if (identityRef
) { // got two matches
137 if (exactMatch
&& !isExact
) { // prior is better; ignore this one
139 safe_CFRelease(&nameRef
);
140 safe_CFRelease(&cert
);
141 safe_CFRelease(&candidate
);
144 if (exactMatch
== isExact
) { // same class of match
145 if (CFEqual(identityRef
, candidate
)) { // identities have same cert
147 safe_CFRelease(&nameRef
);
148 safe_CFRelease(&cert
);
149 safe_CFRelease(&candidate
);
152 // ambiguity - must fail
153 sec_error("\"%s\" is ambiguous, matches more than one certificate", identity
);
155 safe_CFRelease(&nameRef
);
156 safe_CFRelease(&cert
);
157 safe_CFRelease(&candidate
);
158 safe_CFRelease(&identityRef
);
161 safe_CFRelease(&identityRef
); // about to replace with this one
163 identityRef
= candidate
; // currently retained
164 exactMatch
= isExact
;
166 safe_CFRelease(&nameRef
);
168 safe_CFRelease(&cert
);
171 safe_CFRelease(&searchRef
);
172 safe_CFRelease(&matchRef
);
180 static void printIdentity(SecIdentityRef identity
, SecPolicyRef policy
, int ordinalValue
)
183 Boolean printHash
= TRUE
, printName
= TRUE
;
184 SecCertificateRef cert
= NULL
;
186 status
= SecIdentityCopyCertificate(identity
, &cert
);
189 CSSM_DATA certData
= { 0, nil
};
190 (void) SecCertificateGetData(cert
, &certData
);
191 fprintf(stdout
, "%3d) ", ordinalValue
);
195 digest
.Length
= sizeof(sha1_hash
);
196 digest
.Data
= sha1_hash
;
197 if (SecDigestGetData(CSSM_ALGID_SHA1
, &digest
, &certData
) == CSSM_OK
) {
199 uint32 len
= digest
.Length
;
200 uint8
*cp
= digest
.Data
;
201 for(i
=0; i
<len
; i
++) {
202 fprintf(stdout
, "%02X", ((unsigned char *)cp
)[i
]);
205 fprintf(stdout
, "!----- unable to get SHA-1 digest -----!");
209 char *nameBuf
= NULL
;
210 CFStringRef nameRef
= NULL
;
211 status
= SecCertificateInferLabel(cert
, &nameRef
);
212 CFIndex nameLen
= (nameRef
) ? CFStringGetLength(nameRef
) : 0;
214 CFIndex bufLen
= 1 + CFStringGetMaximumSizeForEncoding(nameLen
, kCFStringEncodingUTF8
);
215 nameBuf
= (char *)malloc(bufLen
);
216 if (!CFStringGetCString(nameRef
, nameBuf
, bufLen
-1, kCFStringEncodingUTF8
))
219 fprintf(stdout
, " \"%s\"", (nameBuf
&& nameBuf
[0] != 0) ? nameBuf
: "<unknown>");
222 safe_CFRelease(&nameRef
);
225 // Default to X.509 Basic if no policy was specified
227 SecPolicySearchRef policySearch
= NULL
;
228 if (SecPolicySearchCreate(CSSM_CERT_X_509v3
, &CSSMOID_APPLE_X509_BASIC
, NULL
, &policySearch
)==noErr
) {
229 SecPolicySearchCopyNext(policySearch
, &policy
);
231 safe_CFRelease(&policySearch
);
236 // Create the trust reference, given policy and certificates
237 SecTrustRef trust
= nil
;
238 SecTrustResultType trustResult
= kSecTrustResultInvalid
;
239 OSStatus trustResultCode
= noErr
;
240 CFMutableArrayRef certificates
= CFArrayCreateMutable(NULL
, 0, &kCFTypeArrayCallBacks
);
242 CFArrayAppendValue(certificates
, cert
);
244 status
= SecTrustCreateWithCertificates((CFArrayRef
)certificates
, policy
, &trust
);
246 status
= SecTrustEvaluate(trust
, &trustResult
);
248 if (trustResult
!= kSecTrustResultInvalid
) {
249 status
= SecTrustGetCssmResultCode(trust
, &trustResultCode
);
251 if (trustResultCode
!= noErr
) {
252 fprintf(stdout
, " (%s)\n", cssmErrorString(trustResultCode
));
254 fprintf(stdout
, "\n");
256 safe_CFRelease(&trust
);
257 safe_CFRelease(&policy
);
258 safe_CFRelease(&certificates
);
260 safe_CFRelease(&cert
);
265 do_identity_search_with_policy(CFTypeRef keychainOrArray
,
267 const CSSM_OID
* oidPtr
,
268 CSSM_KEYUSE keyUsage
,
272 // set up SMIME options with provided data
273 CE_KeyUsage ceKeyUsage
= 0;
274 if (keyUsage
& CSSM_KEYUSE_SIGN
) ceKeyUsage
|= CE_KU_DigitalSignature
;
275 if (keyUsage
& CSSM_KEYUSE_ENCRYPT
) ceKeyUsage
|= CE_KU_KeyEncipherment
;
276 CSSM_APPLE_TP_SMIME_OPTIONS smimeOpts
= {
277 CSSM_APPLE_TP_SMIME_OPTS_VERSION
, // Version
278 ceKeyUsage
, // IntendedUsage
279 name
? strlen(name
) : 0, // SenderEmailLen
282 CSSM_DATA smimeValue
= { sizeof(smimeOpts
), (uint8
*)&smimeOpts
};
284 // set up SSL options with provided data
285 CSSM_APPLE_TP_SSL_OPTIONS sslOpts
= {
286 CSSM_APPLE_TP_SSL_OPTS_VERSION
, // Version
287 (name
&& !client
) ? strlen(name
) : 0, // ServerNameLen
288 (client
) ? NULL
: name
, // ServerName
289 (client
) ? CSSM_APPLE_TP_SSL_CLIENT
: 0 // Flags
291 CSSM_DATA sslValue
= { sizeof(sslOpts
), (uint8
*)&sslOpts
};
293 // get a policy ref for the specified policy OID
294 OSStatus status
= noErr
;
295 SecPolicyRef policy
= NULL
;
296 SecPolicySearchRef policySearch
= NULL
;
297 status
= SecPolicySearchCreate(CSSM_CERT_X_509v3
, oidPtr
, NULL
, &policySearch
);
299 status
= SecPolicySearchCopyNext(policySearch
, &policy
);
301 CSSM_DATA
*policyValue
= NULL
;
302 const char *policyName
= "<unknown>";
304 if (compareOids(oidPtr
, &CSSMOID_APPLE_TP_SMIME
)) {
305 policyName
= "S/MIME";
306 policyValue
= &smimeValue
;
308 else if (compareOids(oidPtr
, &CSSMOID_APPLE_TP_SSL
)) {
310 policyName
= "SSL (client)";
312 policyName
= "SSL (server)";
313 policyValue
= &sslValue
;
315 else if (compareOids(oidPtr
, &CSSMOID_APPLE_TP_EAP
)) {
318 else if (compareOids(oidPtr
, &CSSMOID_APPLE_TP_IP_SEC
)) {
319 policyName
= "IPsec";
321 else if (compareOids(oidPtr
, &CSSMOID_APPLE_TP_ICHAT
)) {
322 policyName
= "iChat";
324 else if (compareOids(oidPtr
, &CSSMOID_APPLE_TP_CODE_SIGNING
)) {
325 policyName
= "Code Signing";
327 else if (compareOids(oidPtr
, &CSSMOID_APPLE_X509_BASIC
)) {
328 policyName
= "X.509 Basic";
330 else if (compareOids(oidPtr
, &CSSMOID_APPLE_TP_MACAPPSTORE_RECEIPT
)) {
331 policyName
= "Mac App Store Receipt";
333 else if (compareOids(oidPtr
, &CSSMOID_APPLE_TP_APPLEID_SHARING
)) {
334 policyName
= "AppleID Sharing";
337 // set the policy's value, if there is one (this is specific to certain policies)
338 if (policy
&& policyValue
)
339 status
= SecPolicySetValue(policy
, policyValue
);
341 CFStringRef idStr
= (name
) ? CFStringCreateWithCStringNoCopy(NULL
, name
, kCFStringEncodingUTF8
, kCFAllocatorNull
) : NULL
;
342 SecIdentitySearchRef searchRef
= NULL
;
343 int identityCount
= 0;
346 // create an identity search, specifying all identities (i.e. returnOnlyValidIdentities=FALSE)
347 // this should return all identities which match the policy and key usage, regardless of validity
348 fprintf(stdout
, "\nPolicy: %s\n", policyName
);
349 fprintf(stdout
, " Matching identities\n");
350 status
= SecIdentitySearchCreateWithPolicy(policy
, idStr
, keyUsage
, keychainOrArray
, FALSE
, &searchRef
);
353 SecIdentityRef identityRef
= NULL
;
354 while (SecIdentitySearchCopyNext(searchRef
, &identityRef
) == noErr
)
357 printIdentity(identityRef
, policy
, identityCount
);
358 safe_CFRelease(&identityRef
);
360 safe_CFRelease(&searchRef
);
362 fprintf(stdout
, " %d identities found\n\n", identityCount
);
365 // create a second identity search, specifying only valid identities (i.e. returnOnlyValidIdentities=TRUE)
366 // this should return only valid identities for the policy.
369 fprintf(stdout
, " Valid identities only\n");
371 status
= SecIdentitySearchCreateWithPolicy(policy
, idStr
, keyUsage
, keychainOrArray
, TRUE
, &searchRef
);
374 SecIdentityRef identityRef
= NULL
;
375 while (SecIdentitySearchCopyNext(searchRef
, &identityRef
) == noErr
)
378 printIdentity(identityRef
, policy
, identityCount
);
379 safe_CFRelease(&identityRef
);
381 safe_CFRelease(&searchRef
);
383 fprintf(stdout
, " %d valid identities found\n", identityCount
);
385 safe_CFRelease(&idStr
);
386 safe_CFRelease(&policy
);
387 safe_CFRelease(policySearch
);
391 do_system_identity_search(CFStringRef domain
)
393 SecIdentityRef identity
= NULL
;
394 OSStatus status
= SecIdentityCopySystemIdentity(domain
, &identity
, NULL
);
395 if (CFEqual(domain
, kSecIdentityDomainDefault
)) {
396 fprintf(stdout
, "\n System default identity\n");
397 } else if (CFEqual(domain
, kSecIdentityDomainKerberosKDC
)) {
398 fprintf(stdout
, "\n System Kerberos KDC identity\n");
400 if (!status
&& identity
) {
401 SecPolicyRef policy
= NULL
;
402 SecPolicySearchRef policySearch
= NULL
;
403 if (SecPolicySearchCreate(CSSM_CERT_X_509v3
, &CSSMOID_APPLE_X509_BASIC
, NULL
, &policySearch
) == noErr
) {
404 if (SecPolicySearchCopyNext(policySearch
, &policy
) == noErr
) {
405 printIdentity(identity
, policy
, 1);
408 safe_CFRelease(&policySearch
);
410 safe_CFRelease(&identity
);
415 do_find_identities(CFTypeRef keychainOrArray
, const char *name
, unsigned int policyFlags
, Boolean validOnly
)
420 fprintf(stdout
, "Looking for identities matching \"%s\"\n", name
);
422 if (policyFlags
& (1 << 0))
423 do_identity_search_with_policy(keychainOrArray
, name
, &CSSMOID_APPLE_TP_SSL
, CSSM_KEYUSE_SIGN
, TRUE
, validOnly
);
424 if (policyFlags
& (1 << 1))
425 do_identity_search_with_policy(keychainOrArray
, name
, &CSSMOID_APPLE_TP_SSL
, CSSM_KEYUSE_SIGN
, FALSE
, validOnly
);
426 if (policyFlags
& (1 << 2))
427 do_identity_search_with_policy(keychainOrArray
, name
, &CSSMOID_APPLE_TP_SMIME
, CSSM_KEYUSE_SIGN
, TRUE
, validOnly
);
428 if (policyFlags
& (1 << 3))
429 do_identity_search_with_policy(keychainOrArray
, name
, &CSSMOID_APPLE_TP_EAP
, CSSM_KEYUSE_SIGN
, TRUE
, validOnly
);
430 if (policyFlags
& (1 << 4))
431 do_identity_search_with_policy(keychainOrArray
, name
, &CSSMOID_APPLE_TP_IP_SEC
, CSSM_KEYUSE_SIGN
, TRUE
, validOnly
);
432 if (policyFlags
& (1 << 5))
433 do_identity_search_with_policy(keychainOrArray
, name
, &CSSMOID_APPLE_TP_ICHAT
, CSSM_KEYUSE_SIGN
, TRUE
, validOnly
);
434 if (policyFlags
& (1 << 6))
435 do_identity_search_with_policy(keychainOrArray
, name
, &CSSMOID_APPLE_TP_CODE_SIGNING
, CSSM_KEYUSE_SIGN
, TRUE
, validOnly
);
436 if (policyFlags
& (1 << 7))
437 do_identity_search_with_policy(keychainOrArray
, name
, &CSSMOID_APPLE_X509_BASIC
, CSSM_KEYUSE_SIGN
, TRUE
, validOnly
);
439 if (policyFlags
& (1 << 8))
440 do_system_identity_search(kSecIdentityDomainDefault
);
441 if (policyFlags
& (1 << 9))
442 do_system_identity_search(kSecIdentityDomainKerberosKDC
);
443 if (policyFlags
& (1 << 10))
444 do_identity_search_with_policy(keychainOrArray
, name
, &CSSMOID_APPLE_TP_APPLEID_SHARING
, CSSM_KEYUSE_SIGN
, FALSE
, validOnly
);
445 if (policyFlags
& (1 << 11))
446 do_identity_search_with_policy(keychainOrArray
, name
, &CSSMOID_APPLE_TP_MACAPPSTORE_RECEIPT
, CSSM_KEYUSE_SIGN
, TRUE
, validOnly
);
452 keychain_find_identity(int argc
, char * const *argv
)
455 unsigned int policyFlags
= 0;
456 const char *name
= NULL
;
457 Boolean validOnly
= FALSE
;
458 CFTypeRef keychainOrArray
= NULL
;
461 * " -p Specify policy to evaluate (multiple -p options are allowed)\n"
462 * " -s Specify optional policy-specific string (e.g. a DNS hostname for SSL,\n"
463 * " or RFC822 email address for S/MIME)\n"
464 * " -v Show valid identities only (default is to show all identities)\n"
467 while ((ch
= getopt(argc
, argv
, "hp:s:v")) != -1)
472 if (optarg
!= NULL
) {
473 if (!strcmp(optarg
, "ssl-client"))
474 policyFlags
|= 1 << 0;
475 else if (!strcmp(optarg
, "ssl-server"))
476 policyFlags
|= 1 << 1;
477 else if (!strcmp(optarg
, "smime"))
478 policyFlags
|= 1 << 2;
479 else if (!strcmp(optarg
, "eap"))
480 policyFlags
|= 1 << 3;
481 else if (!strcmp(optarg
, "ipsec"))
482 policyFlags
|= 1 << 4;
483 else if (!strcmp(optarg
, "ichat"))
484 policyFlags
|= 1 << 5;
485 else if (!strcmp(optarg
, "codesigning"))
486 policyFlags
|= 1 << 6;
487 else if (!strcmp(optarg
, "basic"))
488 policyFlags
|= 1 << 7;
489 else if (!strcmp(optarg
, "sys-default"))
490 policyFlags
|= 1 << 8;
491 else if (!strcmp(optarg
, "sys-kerberos-kdc"))
492 policyFlags
|= 1 << 9;
493 else if (!strcmp(optarg
, "appleID"))
494 policyFlags
|= 1 << 10;
495 else if (!strcmp(optarg
, "macappstore"))
496 policyFlags
|= 1 << 11;
498 result
= 2; /* @@@ Return 2 triggers usage message. */
511 result
= 2; /* @@@ Return 2 triggers usage message. */
517 policyFlags
|= 1 << 7; /* default to basic policy if none specified */
522 keychainOrArray
= keychain_create_array(argc
, argv
);
524 result
= do_find_identities(keychainOrArray
, name
, policyFlags
, validOnly
);
527 safe_CFRelease(&keychainOrArray
);