]>
Commit | Line | Data |
---|---|---|
d8f41ccd | 1 | /* |
fa7225c8 A |
2 | * Copyright (c) 2006-2009,2012,2016 Apple Inc. All Rights Reserved. |
3 | * | |
d8f41ccd | 4 | * @APPLE_LICENSE_HEADER_START@ |
fa7225c8 | 5 | * |
d8f41ccd A |
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. | |
fa7225c8 | 12 | * |
d8f41ccd A |
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. | |
fa7225c8 | 20 | * |
d8f41ccd A |
21 | * @APPLE_LICENSE_HEADER_END@ |
22 | */ | |
23 | // | |
24 | // clientid - track and manage identity of securityd clients | |
25 | // | |
26 | #include "clientid.h" | |
27 | #include "server.h" | |
28 | #include <Security/SecCodePriv.h> | |
e3d460c9 A |
29 | #include <Security/oidsattr.h> |
30 | #include <Security/SecCertificatePriv.h> | |
d8f41ccd A |
31 | |
32 | ||
33 | // | |
34 | // Constructing a ClientIdentification doesn't do much. | |
35 | // We're waiting for setup(), which should be called by the child class's | |
36 | // constructor. | |
37 | // | |
38 | ClientIdentification::ClientIdentification() | |
e3d460c9 | 39 | : mGotPartitionId(false) |
d8f41ccd A |
40 | { |
41 | } | |
42 | ||
43 | ||
44 | // | |
45 | // Initialize the ClientIdentification. | |
46 | // This creates a process-level code object for the client. | |
47 | // | |
dbe77505 | 48 | void ClientIdentification::setup(Security::CommonCriteria::AuditToken const &audit) |
d8f41ccd | 49 | { |
dbe77505 A |
50 | StLock<Mutex> _(mLock); |
51 | StLock<Mutex> __(mValidityCheckLock); | |
52 | ||
53 | audit_token_t const token = audit.auditToken(); | |
54 | OSStatus rc = SecCodeCreateWithAuditToken(&token, kSecCSDefaultFlags, &mClientProcess.aref()); | |
55 | ||
56 | if (rc) { | |
57 | secerror("could not get code for process %d: OSStatus=%d", | |
58 | audit.pid(), int32_t(rc)); | |
59 | } | |
60 | mGuests.erase(mGuests.begin(), mGuests.end()); | |
d8f41ccd A |
61 | } |
62 | ||
63 | ||
64 | // | |
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. | |
68 | // | |
69 | SecCodeRef ClientIdentification::processCode() const | |
70 | { | |
71 | return mClientProcess; | |
72 | } | |
73 | ||
74 | ||
75 | // | |
76 | // Return a SecCodeRef for the currently active guest within the client | |
77 | // process. | |
78 | // | |
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. | |
83 | // | |
84 | // Note that this consults Server::connection for the currently serviced | |
85 | // Connection object, so this is not entirely a function of ClientIdentification state. | |
86 | // | |
87 | SecCodeRef ClientIdentification::currentGuest() const | |
88 | { | |
89 | if (GuestState *guest = current()) | |
90 | return guest->code; | |
91 | else | |
92 | return mClientProcess; | |
93 | } | |
94 | ||
95 | ClientIdentification::GuestState *ClientIdentification::current() const | |
96 | { | |
97 | // if we have no client identification, we can't find a current guest either | |
98 | if (!processCode()) | |
99 | return NULL; | |
100 | ||
101 | SecGuestRef guestRef = Server::connection().guestRef(); | |
fa7225c8 | 102 | |
d8f41ccd A |
103 | // try to deliver an already-cached entry |
104 | { | |
105 | StLock<Mutex> _(mLock); | |
106 | GuestMap::iterator it = mGuests.find(guestRef); | |
107 | if (it != mGuests.end()) | |
108 | return &it->second; | |
109 | } | |
110 | ||
111 | // okay, make a new one (this may take a while) | |
112 | CFRef<CFDictionaryRef> attributes = (guestRef == kSecNoGuest) | |
113 | ? NULL | |
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())) { | |
119 | case noErr: | |
120 | break; | |
121 | case errSecCSUnsigned: // not signed; clearly not a host | |
122 | case errSecCSNotAHost: // signed but not marked as a (potential) host | |
123 | code = mClientProcess; | |
124 | break; | |
125 | case errSecCSNoSuchCode: // potential host, but... | |
126 | if (guestRef == kSecNoGuest) { // ... no guests (yet), so return the process | |
127 | code = mClientProcess; | |
128 | break; | |
129 | } | |
130 | // else fall through // ... the guest we expected to be there isn't | |
131 | default: | |
132 | MacOSError::throwMe(rc); | |
133 | } | |
134 | StLock<Mutex> _(mLock); | |
135 | GuestState &slot = mGuests[guestRef]; | |
136 | if (!slot.code) // if another thread didn't get here first... | |
137 | slot.code = code; | |
138 | return &slot; | |
139 | } | |
140 | ||
141 | ||
e3d460c9 A |
142 | // |
143 | // Return the partition id ascribed to this client. | |
144 | // This is assigned to the whole client process - it is not per-guest. | |
145 | // | |
146 | std::string ClientIdentification::partitionId() const | |
147 | { | |
148 | if (!mGotPartitionId) { | |
fa7225c8 | 149 | StLock<Mutex> _(mValidityCheckLock); |
e3d460c9 A |
150 | mClientPartitionId = partitionIdForProcess(processCode()); |
151 | mGotPartitionId = true; | |
152 | } | |
153 | return mClientPartitionId; | |
154 | } | |
155 | ||
156 | ||
157 | static std::string hashString(CFDataRef data) | |
158 | { | |
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]); | |
164 | return s; | |
165 | } | |
166 | ||
167 | ||
168 | std::string ClientIdentification::partitionIdForProcess(SecStaticCodeRef code) | |
169 | { | |
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]"); | |
79b9da22 A |
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 |
173 | " or " | |
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 | |
175 | " or " | |
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 | |
e3d460c9 A |
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)) | |
185 | abort(); | |
186 | }); | |
fa7225c8 | 187 | |
e3d460c9 A |
188 | OSStatus rc; |
189 | switch (rc = SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly, apple)) { | |
190 | case noErr: | |
191 | case errSecCSReqFailed: | |
192 | break; | |
193 | case errSecCSUnsigned: | |
194 | return "unsigned:"; | |
195 | default: | |
196 | MacOSError::throwMe(rc); | |
197 | } | |
198 | CFRef<CFDictionaryRef> info; | |
199 | if (OSStatus irc = SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())) | |
200 | MacOSError::throwMe(irc); | |
fa7225c8 | 201 | |
e3d460c9 | 202 | if (rc == noErr) { |
fa7225c8 A |
203 | // for apple-signed code, make it canonical apple |
204 | if (CFEqual(CFDictionaryGetValue(info, kSecCodeInfoIdentifier), CFSTR("com.apple.security"))) { | |
e3d460c9 | 205 | return "apple-tool:"; // take security(1) into a separate partition so it can't automatically peek into Apple's own |
fa7225c8 | 206 | } else { |
e3d460c9 | 207 | return "apple:"; |
fa7225c8 | 208 | } |
e3d460c9 A |
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); | |
222 | } else { | |
223 | // cannot positively classify this code, but it's signed | |
224 | CFDataRef cdhashData = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); | |
225 | assert(cdhashData); | |
226 | return "cdhash:" + hashString(cdhashData); | |
227 | } | |
228 | } | |
229 | ||
230 | ||
d8f41ccd A |
231 | // |
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. | |
235 | // | |
236 | string ClientIdentification::getPath() const | |
237 | { | |
238 | assert(mClientProcess); | |
fa7225c8 | 239 | StLock<Mutex> _(mValidityCheckLock); |
d8f41ccd A |
240 | return codePath(currentGuest()); |
241 | } | |
242 | ||
243 | const CssmData ClientIdentification::getHash() const | |
244 | { | |
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; | |
250 | } | |
251 | return CssmData::wrap(guest->legacyHash, SHA1::digestLength); | |
252 | } else | |
253 | return CssmData(); | |
254 | } | |
255 | ||
fa7225c8 A |
256 | AclSubject* ClientIdentification::copyAclSubject() const |
257 | { | |
258 | StLock<Mutex> _(mValidityCheckLock); | |
259 | RefPointer<OSXCode> clientXCode = new OSXCodeWrap(currentGuest()); | |
260 | return new CodeSignatureAclSubject(OSXVerifier(clientXCode)); | |
261 | } | |
262 | ||
263 | OSStatus ClientIdentification::copySigningInfo(SecCSFlags flags, | |
264 | CFDictionaryRef *info) const | |
265 | { | |
266 | StLock<Mutex> _(mValidityCheckLock); | |
267 | return SecCodeCopySigningInformation(currentGuest(), flags, info); | |
268 | } | |
269 | ||
270 | OSStatus ClientIdentification::checkValidity(SecCSFlags flags, | |
271 | SecRequirementRef requirement) const | |
272 | { | |
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); | |
276 | } | |
277 | ||
6b200bc3 | 278 | bool ClientIdentification::checkAppleSigned() const |
d8f41ccd | 279 | { |
fa7225c8 A |
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); | |
288 | } else { | |
289 | return true; | |
d8f41ccd | 290 | } |
fa7225c8 A |
291 | } |
292 | return false; | |
d8f41ccd A |
293 | } |
294 | ||
295 | ||
e3d460c9 A |
296 | bool ClientIdentification::hasEntitlement(const char *name) const |
297 | { | |
298 | CFRef<CFDictionaryRef> info; | |
fa7225c8 A |
299 | { |
300 | StLock<Mutex> _(mValidityCheckLock); | |
301 | MacOSError::check(SecCodeCopySigningInformation(processCode(), kSecCSDefaultFlags, &info.aref())); | |
302 | } | |
e3d460c9 A |
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 | |
308 | } | |
309 | return false; | |
310 | } | |
311 | ||
312 | ||
d8f41ccd A |
313 | // |
314 | // Bonus function: get the path out of a SecCodeRef | |
315 | // | |
316 | std::string codePath(SecStaticCodeRef code) | |
317 | { | |
318 | CFRef<CFURLRef> path; | |
319 | MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref())); | |
320 | return cfString(path); | |
321 | } | |
322 | ||
323 | ||
324 | // | |
325 | // Debug dump support | |
326 | // | |
327 | #if defined(DEBUGDUMP) | |
328 | ||
329 | static void dumpCode(SecCodeRef code) | |
330 | { | |
331 | CFRef<CFURLRef> path; | |
332 | if (OSStatus rc = SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref())) | |
333 | Debug::dump("unknown(rc=%d)", int32_t(rc)); | |
334 | else | |
335 | Debug::dump("%s", cfString(path).c_str()); | |
336 | } | |
337 | ||
338 | void ClientIdentification::dump() | |
339 | { | |
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]"); | |
347 | } | |
348 | } | |
349 | ||
350 | #endif //DEBUGDUMP |