]>
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 | // | |
48 | void ClientIdentification::setup(pid_t pid) | |
49 | { | |
50 | StLock<Mutex> _(mLock); | |
fa7225c8 A |
51 | StLock<Mutex> __(mValidityCheckLock); |
52 | OSStatus rc = SecCodeCreateWithPID(pid, kSecCSDefaultFlags, &mClientProcess.aref()); | |
53 | if (rc) | |
54 | secinfo("clientid", "could not get code for process %d: OSStatus=%d", | |
d8f41ccd A |
55 | pid, int32_t(rc)); |
56 | mGuests.erase(mGuests.begin(), mGuests.end()); | |
57 | } | |
58 | ||
59 | ||
60 | // | |
61 | // Return a SecCodeRef for the client process itself, regardless of | |
62 | // which guest within it is currently active. | |
63 | // Think twice before using this. | |
64 | // | |
65 | SecCodeRef ClientIdentification::processCode() const | |
66 | { | |
67 | return mClientProcess; | |
68 | } | |
69 | ||
70 | ||
71 | // | |
72 | // Return a SecCodeRef for the currently active guest within the client | |
73 | // process. | |
74 | // | |
75 | // We make a fair effort to cache client guest identities without over-growing | |
76 | // the cache. Note that there's currently no protocol for being notified of | |
77 | // a guest's death or disappearance (independent from the host process's death), | |
78 | // so we couldn't track guests live even if we tried. | |
79 | // | |
80 | // Note that this consults Server::connection for the currently serviced | |
81 | // Connection object, so this is not entirely a function of ClientIdentification state. | |
82 | // | |
83 | SecCodeRef ClientIdentification::currentGuest() const | |
84 | { | |
85 | if (GuestState *guest = current()) | |
86 | return guest->code; | |
87 | else | |
88 | return mClientProcess; | |
89 | } | |
90 | ||
91 | ClientIdentification::GuestState *ClientIdentification::current() const | |
92 | { | |
93 | // if we have no client identification, we can't find a current guest either | |
94 | if (!processCode()) | |
95 | return NULL; | |
96 | ||
97 | SecGuestRef guestRef = Server::connection().guestRef(); | |
fa7225c8 | 98 | |
d8f41ccd A |
99 | // try to deliver an already-cached entry |
100 | { | |
101 | StLock<Mutex> _(mLock); | |
102 | GuestMap::iterator it = mGuests.find(guestRef); | |
103 | if (it != mGuests.end()) | |
104 | return &it->second; | |
105 | } | |
106 | ||
107 | // okay, make a new one (this may take a while) | |
108 | CFRef<CFDictionaryRef> attributes = (guestRef == kSecNoGuest) | |
109 | ? NULL | |
110 | : makeCFDictionary(1, kSecGuestAttributeCanonical, CFTempNumber(guestRef).get()); | |
111 | Server::active().longTermActivity(); | |
112 | CFRef<SecCodeRef> code; | |
113 | switch (OSStatus rc = SecCodeCopyGuestWithAttributes(processCode(), | |
114 | attributes, kSecCSDefaultFlags, &code.aref())) { | |
115 | case noErr: | |
116 | break; | |
117 | case errSecCSUnsigned: // not signed; clearly not a host | |
118 | case errSecCSNotAHost: // signed but not marked as a (potential) host | |
119 | code = mClientProcess; | |
120 | break; | |
121 | case errSecCSNoSuchCode: // potential host, but... | |
122 | if (guestRef == kSecNoGuest) { // ... no guests (yet), so return the process | |
123 | code = mClientProcess; | |
124 | break; | |
125 | } | |
126 | // else fall through // ... the guest we expected to be there isn't | |
127 | default: | |
128 | MacOSError::throwMe(rc); | |
129 | } | |
130 | StLock<Mutex> _(mLock); | |
131 | GuestState &slot = mGuests[guestRef]; | |
132 | if (!slot.code) // if another thread didn't get here first... | |
133 | slot.code = code; | |
134 | return &slot; | |
135 | } | |
136 | ||
137 | ||
e3d460c9 A |
138 | // |
139 | // Return the partition id ascribed to this client. | |
140 | // This is assigned to the whole client process - it is not per-guest. | |
141 | // | |
142 | std::string ClientIdentification::partitionId() const | |
143 | { | |
144 | if (!mGotPartitionId) { | |
fa7225c8 | 145 | StLock<Mutex> _(mValidityCheckLock); |
e3d460c9 A |
146 | mClientPartitionId = partitionIdForProcess(processCode()); |
147 | mGotPartitionId = true; | |
148 | } | |
149 | return mClientPartitionId; | |
150 | } | |
151 | ||
152 | ||
153 | static std::string hashString(CFDataRef data) | |
154 | { | |
155 | CFIndex length = CFDataGetLength(data); | |
156 | const unsigned char *hash = CFDataGetBytePtr(data); | |
157 | char s[2 * length + 1]; | |
158 | for (CFIndex n = 0; n < length; n++) | |
159 | sprintf(&s[2*n], "%2.2x", hash[n]); | |
160 | return s; | |
161 | } | |
162 | ||
163 | ||
164 | std::string ClientIdentification::partitionIdForProcess(SecStaticCodeRef code) | |
165 | { | |
166 | static CFStringRef const appleReq = CFSTR("anchor apple"); | |
167 | static CFStringRef const masReq = CFSTR("anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9]"); | |
168 | 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]" | |
169 | " or " | |
170 | "anchor apple generic and certificate leaf[subject.CN] = \"Mac Developer:\"* and certificate 1[field.1.2.840.113635.100.6.2.1]"); | |
171 | static SecRequirementRef apple; | |
172 | static SecRequirementRef mas; | |
173 | static SecRequirementRef developmentOrDevID; | |
174 | static dispatch_once_t onceToken; | |
175 | dispatch_once(&onceToken, ^{ | |
176 | if (noErr != SecRequirementCreateWithString(appleReq, kSecCSDefaultFlags, &apple) | |
177 | || noErr != SecRequirementCreateWithString(masReq, kSecCSDefaultFlags, &mas) | |
178 | || noErr != SecRequirementCreateWithString(developmentOrDevIDReq, kSecCSDefaultFlags, &developmentOrDevID)) | |
179 | abort(); | |
180 | }); | |
fa7225c8 | 181 | |
e3d460c9 A |
182 | OSStatus rc; |
183 | switch (rc = SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly, apple)) { | |
184 | case noErr: | |
185 | case errSecCSReqFailed: | |
186 | break; | |
187 | case errSecCSUnsigned: | |
188 | return "unsigned:"; | |
189 | default: | |
190 | MacOSError::throwMe(rc); | |
191 | } | |
192 | CFRef<CFDictionaryRef> info; | |
193 | if (OSStatus irc = SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())) | |
194 | MacOSError::throwMe(irc); | |
fa7225c8 | 195 | |
e3d460c9 | 196 | if (rc == noErr) { |
fa7225c8 A |
197 | // for apple-signed code, make it canonical apple |
198 | if (CFEqual(CFDictionaryGetValue(info, kSecCodeInfoIdentifier), CFSTR("com.apple.security"))) { | |
e3d460c9 | 199 | return "apple-tool:"; // take security(1) into a separate partition so it can't automatically peek into Apple's own |
fa7225c8 | 200 | } else { |
e3d460c9 | 201 | return "apple:"; |
fa7225c8 | 202 | } |
e3d460c9 A |
203 | } else if (noErr == SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly, mas)) { |
204 | // for MAS-signed code, we take the embedded team identifier (verified by Apple) | |
205 | return "teamid:" + cfString(CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoTeamIdentifier))); | |
206 | } else if (noErr == SecStaticCodeCheckValidity(code, kSecCSBasicValidateOnly, developmentOrDevID)) { | |
207 | // for developer-signed code, we take the team identifier from the signing certificate's OU field | |
208 | CFRef<CFDictionaryRef> info; | |
209 | if (noErr != (rc = SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref()))) | |
210 | MacOSError::throwMe(rc); | |
211 | CFArrayRef certChain = CFArrayRef(CFDictionaryGetValue(info, kSecCodeInfoCertificates)); | |
212 | SecCertificateRef signingCert = SecCertificateRef(CFArrayGetValueAtIndex(certChain, 0)); | |
213 | CFRef<CFStringRef> ou; | |
214 | SecCertificateCopySubjectComponent(signingCert, &CSSMOID_OrganizationalUnitName, &ou.aref()); | |
215 | return "teamid:" + cfString(ou); | |
216 | } else { | |
217 | // cannot positively classify this code, but it's signed | |
218 | CFDataRef cdhashData = CFDataRef(CFDictionaryGetValue(info, kSecCodeInfoUnique)); | |
219 | assert(cdhashData); | |
220 | return "cdhash:" + hashString(cdhashData); | |
221 | } | |
222 | } | |
223 | ||
224 | ||
d8f41ccd A |
225 | // |
226 | // Support for the legacy hash identification mechanism. | |
227 | // The legacy machinery deals exclusively in terms of processes. | |
228 | // It knows nothing about guests and their identities. | |
229 | // | |
230 | string ClientIdentification::getPath() const | |
231 | { | |
232 | assert(mClientProcess); | |
fa7225c8 | 233 | StLock<Mutex> _(mValidityCheckLock); |
d8f41ccd A |
234 | return codePath(currentGuest()); |
235 | } | |
236 | ||
237 | const CssmData ClientIdentification::getHash() const | |
238 | { | |
239 | if (GuestState *guest = current()) { | |
240 | if (!guest->gotHash) { | |
241 | RefPointer<OSXCode> clientCode = new OSXCodeWrap(guest->code); | |
242 | OSXVerifier::makeLegacyHash(clientCode, guest->legacyHash); | |
243 | guest->gotHash = true; | |
244 | } | |
245 | return CssmData::wrap(guest->legacyHash, SHA1::digestLength); | |
246 | } else | |
247 | return CssmData(); | |
248 | } | |
249 | ||
fa7225c8 A |
250 | AclSubject* ClientIdentification::copyAclSubject() const |
251 | { | |
252 | StLock<Mutex> _(mValidityCheckLock); | |
253 | RefPointer<OSXCode> clientXCode = new OSXCodeWrap(currentGuest()); | |
254 | return new CodeSignatureAclSubject(OSXVerifier(clientXCode)); | |
255 | } | |
256 | ||
257 | OSStatus ClientIdentification::copySigningInfo(SecCSFlags flags, | |
258 | CFDictionaryRef *info) const | |
259 | { | |
260 | StLock<Mutex> _(mValidityCheckLock); | |
261 | return SecCodeCopySigningInformation(currentGuest(), flags, info); | |
262 | } | |
263 | ||
264 | OSStatus ClientIdentification::checkValidity(SecCSFlags flags, | |
265 | SecRequirementRef requirement) const | |
266 | { | |
267 | // Make sure more than one thread cannot be evaluating this code signature concurrently | |
268 | StLock<Mutex> _(mValidityCheckLock); | |
269 | return SecCodeCheckValidityWithErrors(currentGuest(), flags, requirement, NULL); | |
270 | } | |
271 | ||
6b200bc3 | 272 | bool ClientIdentification::checkAppleSigned() const |
d8f41ccd | 273 | { |
fa7225c8 A |
274 | // This is the clownfish supported way to check for a Mac App Store or B&I signed build |
275 | static CFStringRef const requirementString = CFSTR("(anchor apple) or (anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9])"); | |
276 | CFRef<SecRequirementRef> secRequirementRef = NULL; | |
277 | OSStatus status = SecRequirementCreateWithString(requirementString, kSecCSDefaultFlags, &secRequirementRef.aref()); | |
278 | if (status == errSecSuccess) { | |
279 | status = checkValidity(kSecCSDefaultFlags, secRequirementRef); | |
280 | if (status != errSecSuccess) { | |
281 | secnotice("clientid", "code requirement check failed (%d), client is not Apple-signed", (int32_t)status); | |
282 | } else { | |
283 | return true; | |
d8f41ccd | 284 | } |
fa7225c8 A |
285 | } |
286 | return false; | |
d8f41ccd A |
287 | } |
288 | ||
289 | ||
e3d460c9 A |
290 | bool ClientIdentification::hasEntitlement(const char *name) const |
291 | { | |
292 | CFRef<CFDictionaryRef> info; | |
fa7225c8 A |
293 | { |
294 | StLock<Mutex> _(mValidityCheckLock); | |
295 | MacOSError::check(SecCodeCopySigningInformation(processCode(), kSecCSDefaultFlags, &info.aref())); | |
296 | } | |
e3d460c9 A |
297 | CFCopyRef<CFDictionaryRef> entitlements = (CFDictionaryRef)CFDictionaryGetValue(info, kSecCodeInfoEntitlementsDict); |
298 | if (entitlements && entitlements.is<CFDictionaryRef>()) { | |
299 | CFTypeRef value = CFDictionaryGetValue(entitlements, CFTempString(name)); | |
300 | if (value && value != kCFBooleanFalse) | |
301 | return true; // have entitlement, it's not <false/> - bypass partition construction | |
302 | } | |
303 | return false; | |
304 | } | |
305 | ||
306 | ||
d8f41ccd A |
307 | // |
308 | // Bonus function: get the path out of a SecCodeRef | |
309 | // | |
310 | std::string codePath(SecStaticCodeRef code) | |
311 | { | |
312 | CFRef<CFURLRef> path; | |
313 | MacOSError::check(SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref())); | |
314 | return cfString(path); | |
315 | } | |
316 | ||
317 | ||
318 | // | |
319 | // Debug dump support | |
320 | // | |
321 | #if defined(DEBUGDUMP) | |
322 | ||
323 | static void dumpCode(SecCodeRef code) | |
324 | { | |
325 | CFRef<CFURLRef> path; | |
326 | if (OSStatus rc = SecCodeCopyPath(code, kSecCSDefaultFlags, &path.aref())) | |
327 | Debug::dump("unknown(rc=%d)", int32_t(rc)); | |
328 | else | |
329 | Debug::dump("%s", cfString(path).c_str()); | |
330 | } | |
331 | ||
332 | void ClientIdentification::dump() | |
333 | { | |
334 | Debug::dump(" client="); | |
335 | dumpCode(mClientProcess); | |
336 | for (GuestMap::const_iterator it = mGuests.begin(); it != mGuests.end(); ++it) { | |
337 | Debug::dump(" guest(0x%x)=", it->first); | |
338 | dumpCode(it->second.code); | |
339 | if (it->second.gotHash) | |
340 | Debug::dump(" [got hash]"); | |
341 | } | |
342 | } | |
343 | ||
344 | #endif //DEBUGDUMP |