2 * Copyright (c) 2006-2009,2012,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 // clientid - track and manage identity of securityd clients
28 #include <Security/SecCodePriv.h>
29 #include <Security/oidsattr.h>
30 #include <Security/SecCertificatePriv.h>
34 // Constructing a ClientIdentification doesn't do much.
35 // We're waiting for setup(), which should be called by the child class's
38 ClientIdentification::ClientIdentification()
39 : mGotPartitionId(false)
45 // Initialize the ClientIdentification.
46 // This creates a process-level code object for the client.
48 void ClientIdentification::setup(Security::CommonCriteria::AuditToken
const &audit
)
50 StLock
<Mutex
> _(mLock
);
51 StLock
<Mutex
> __(mValidityCheckLock
);
53 audit_token_t
const token
= audit
.auditToken();
54 OSStatus rc
= SecCodeCreateWithAuditToken(&token
, kSecCSDefaultFlags
, &mClientProcess
.aref());
57 secerror("could not get code for process %d: OSStatus=%d",
58 audit
.pid(), int32_t(rc
));
60 mGuests
.erase(mGuests
.begin(), mGuests
.end());
65 // Return a SecCodeRef for the client process itself, regardless of
66 // which guest within it is currently active.
67 // Think twice before using this.
69 SecCodeRef
ClientIdentification::processCode() const
71 return mClientProcess
;
76 // Return a SecCodeRef for the currently active guest within the client
79 // We make a fair effort to cache client guest identities without over-growing
80 // the cache. Note that there's currently no protocol for being notified of
81 // a guest's death or disappearance (independent from the host process's death),
82 // so we couldn't track guests live even if we tried.
84 // Note that this consults Server::connection for the currently serviced
85 // Connection object, so this is not entirely a function of ClientIdentification state.
87 SecCodeRef
ClientIdentification::currentGuest() const
89 if (GuestState
*guest
= current())
92 return mClientProcess
;
95 ClientIdentification::GuestState
*ClientIdentification::current() const
97 // if we have no client identification, we can't find a current guest either
101 SecGuestRef guestRef
= Server::connection().guestRef();
103 // try to deliver an already-cached entry
105 StLock
<Mutex
> _(mLock
);
106 GuestMap::iterator it
= mGuests
.find(guestRef
);
107 if (it
!= mGuests
.end())
111 // okay, make a new one (this may take a while)
112 CFRef
<CFDictionaryRef
> attributes
= (guestRef
== kSecNoGuest
)
114 : makeCFDictionary(1, kSecGuestAttributeCanonical
, CFTempNumber(guestRef
).get());
115 Server::active().longTermActivity();
116 CFRef
<SecCodeRef
> code
;
117 switch (OSStatus rc
= SecCodeCopyGuestWithAttributes(processCode(),
118 attributes
, kSecCSDefaultFlags
, &code
.aref())) {
121 case errSecCSUnsigned
: // not signed; clearly not a host
122 case errSecCSNotAHost
: // signed but not marked as a (potential) host
123 code
= mClientProcess
;
125 case errSecCSNoSuchCode
: // potential host, but...
126 if (guestRef
== kSecNoGuest
) { // ... no guests (yet), so return the process
127 code
= mClientProcess
;
130 // else fall through // ... the guest we expected to be there isn't
132 MacOSError::throwMe(rc
);
134 StLock
<Mutex
> _(mLock
);
135 GuestState
&slot
= mGuests
[guestRef
];
136 if (!slot
.code
) // if another thread didn't get here first...
143 // Return the partition id ascribed to this client.
144 // This is assigned to the whole client process - it is not per-guest.
146 std::string
ClientIdentification::partitionId() const
148 if (!mGotPartitionId
) {
149 StLock
<Mutex
> _(mValidityCheckLock
);
150 mClientPartitionId
= partitionIdForProcess(processCode());
151 mGotPartitionId
= true;
153 return mClientPartitionId
;
157 static std::string
hashString(CFDataRef data
)
159 CFIndex length
= CFDataGetLength(data
);
160 const unsigned char *hash
= CFDataGetBytePtr(data
);
161 char s
[2 * length
+ 1];
162 for (CFIndex n
= 0; n
< length
; n
++)
163 sprintf(&s
[2*n
], "%2.2x", hash
[n
]);
168 std::string
ClientIdentification::partitionIdForProcess(SecStaticCodeRef code
)
170 static CFStringRef
const appleReq
= CFSTR("anchor apple");
171 static CFStringRef
const masReq
= CFSTR("anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9]");
172 static CFStringRef
const developmentOrDevIDReq
= CFSTR("anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] and certificate leaf[field.1.2.840.113635.100.6.1.13]" // Developer ID CA and Leaf
174 "anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.1] and certificate leaf[field.1.2.840.113635.100.6.1.12]" // WWDR CA and Mac Development Leaf
176 "anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.1] and certificate leaf[field.1.2.840.113635.100.6.1.7]"); // WWDR CA and Mac Distribution Leaf
177 static SecRequirementRef apple
;
178 static SecRequirementRef mas
;
179 static SecRequirementRef developmentOrDevID
;
180 static dispatch_once_t onceToken
;
181 dispatch_once(&onceToken
, ^{
182 if (noErr
!= SecRequirementCreateWithString(appleReq
, kSecCSDefaultFlags
, &apple
)
183 || noErr
!= SecRequirementCreateWithString(masReq
, kSecCSDefaultFlags
, &mas
)
184 || noErr
!= SecRequirementCreateWithString(developmentOrDevIDReq
, kSecCSDefaultFlags
, &developmentOrDevID
))
189 switch (rc
= SecStaticCodeCheckValidity(code
, kSecCSBasicValidateOnly
, apple
)) {
191 case errSecCSReqFailed
:
193 case errSecCSUnsigned
:
196 MacOSError::throwMe(rc
);
198 CFRef
<CFDictionaryRef
> info
;
199 if (OSStatus irc
= SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref()))
200 MacOSError::throwMe(irc
);
203 // for apple-signed code, make it canonical apple
204 if (CFEqual(CFDictionaryGetValue(info
, kSecCodeInfoIdentifier
), CFSTR("com.apple.security"))) {
205 return "apple-tool:"; // take security(1) into a separate partition so it can't automatically peek into Apple's own
209 } else if (noErr
== SecStaticCodeCheckValidity(code
, kSecCSBasicValidateOnly
, mas
)) {
210 // for MAS-signed code, we take the embedded team identifier (verified by Apple)
211 return "teamid:" + cfString(CFStringRef(CFDictionaryGetValue(info
, kSecCodeInfoTeamIdentifier
)));
212 } else if (noErr
== SecStaticCodeCheckValidity(code
, kSecCSBasicValidateOnly
, developmentOrDevID
)) {
213 // for developer-signed code, we take the team identifier from the signing certificate's OU field
214 CFRef
<CFDictionaryRef
> info
;
215 if (noErr
!= (rc
= SecCodeCopySigningInformation(code
, kSecCSSigningInformation
, &info
.aref())))
216 MacOSError::throwMe(rc
);
217 CFArrayRef certChain
= CFArrayRef(CFDictionaryGetValue(info
, kSecCodeInfoCertificates
));
218 SecCertificateRef signingCert
= SecCertificateRef(CFArrayGetValueAtIndex(certChain
, 0));
219 CFRef
<CFStringRef
> ou
;
220 SecCertificateCopySubjectComponent(signingCert
, &CSSMOID_OrganizationalUnitName
, &ou
.aref());
221 return "teamid:" + cfString(ou
);
223 // cannot positively classify this code, but it's signed
224 CFDataRef cdhashData
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
));
226 return "cdhash:" + hashString(cdhashData
);
232 // Support for the legacy hash identification mechanism.
233 // The legacy machinery deals exclusively in terms of processes.
234 // It knows nothing about guests and their identities.
236 string
ClientIdentification::getPath() const
238 assert(mClientProcess
);
239 StLock
<Mutex
> _(mValidityCheckLock
);
240 return codePath(currentGuest());
243 const CssmData
ClientIdentification::getHash() const
245 if (GuestState
*guest
= current()) {
246 if (!guest
->gotHash
) {
247 RefPointer
<OSXCode
> clientCode
= new OSXCodeWrap(guest
->code
);
248 OSXVerifier::makeLegacyHash(clientCode
, guest
->legacyHash
);
249 guest
->gotHash
= true;
251 return CssmData::wrap(guest
->legacyHash
, SHA1::digestLength
);
256 AclSubject
* ClientIdentification::copyAclSubject() const
258 StLock
<Mutex
> _(mValidityCheckLock
);
259 RefPointer
<OSXCode
> clientXCode
= new OSXCodeWrap(currentGuest());
260 return new CodeSignatureAclSubject(OSXVerifier(clientXCode
));
263 OSStatus
ClientIdentification::copySigningInfo(SecCSFlags flags
,
264 CFDictionaryRef
*info
) const
266 StLock
<Mutex
> _(mValidityCheckLock
);
267 return SecCodeCopySigningInformation(currentGuest(), flags
, info
);
270 OSStatus
ClientIdentification::checkValidity(SecCSFlags flags
,
271 SecRequirementRef requirement
) const
273 // Make sure more than one thread cannot be evaluating this code signature concurrently
274 StLock
<Mutex
> _(mValidityCheckLock
);
275 return SecCodeCheckValidityWithErrors(currentGuest(), flags
, requirement
, NULL
);
278 bool ClientIdentification::checkAppleSigned() const
280 // This is the clownfish supported way to check for a Mac App Store or B&I signed build
281 static CFStringRef
const requirementString
= CFSTR("(anchor apple) or (anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9])");
282 CFRef
<SecRequirementRef
> secRequirementRef
= NULL
;
283 OSStatus status
= SecRequirementCreateWithString(requirementString
, kSecCSDefaultFlags
, &secRequirementRef
.aref());
284 if (status
== errSecSuccess
) {
285 status
= checkValidity(kSecCSDefaultFlags
, secRequirementRef
);
286 if (status
!= errSecSuccess
) {
287 secnotice("clientid", "code requirement check failed (%d), client is not Apple-signed", (int32_t)status
);
296 bool ClientIdentification::hasEntitlement(const char *name
) const
298 CFRef
<CFDictionaryRef
> info
;
300 StLock
<Mutex
> _(mValidityCheckLock
);
301 MacOSError::check(SecCodeCopySigningInformation(processCode(), kSecCSDefaultFlags
, &info
.aref()));
303 CFCopyRef
<CFDictionaryRef
> entitlements
= (CFDictionaryRef
)CFDictionaryGetValue(info
, kSecCodeInfoEntitlementsDict
);
304 if (entitlements
&& entitlements
.is
<CFDictionaryRef
>()) {
305 CFTypeRef value
= CFDictionaryGetValue(entitlements
, CFTempString(name
));
306 if (value
&& value
!= kCFBooleanFalse
)
307 return true; // have entitlement, it's not <false/> - bypass partition construction
314 // Bonus function: get the path out of a SecCodeRef
316 std::string
codePath(SecStaticCodeRef code
)
318 CFRef
<CFURLRef
> path
;
319 MacOSError::check(SecCodeCopyPath(code
, kSecCSDefaultFlags
, &path
.aref()));
320 return cfString(path
);
325 // Debug dump support
327 #if defined(DEBUGDUMP)
329 static void dumpCode(SecCodeRef code
)
331 CFRef
<CFURLRef
> path
;
332 if (OSStatus rc
= SecCodeCopyPath(code
, kSecCSDefaultFlags
, &path
.aref()))
333 Debug::dump("unknown(rc=%d)", int32_t(rc
));
335 Debug::dump("%s", cfString(path
).c_str());
338 void ClientIdentification::dump()
340 Debug::dump(" client=");
341 dumpCode(mClientProcess
);
342 for (GuestMap::const_iterator it
= mGuests
.begin(); it
!= mGuests
.end(); ++it
) {
343 Debug::dump(" guest(0x%x)=", it
->first
);
344 dumpCode(it
->second
.code
);
345 if (it
->second
.gotHash
)
346 Debug::dump(" [got hash]");