2  * Copyright (c) 2006-2010 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 // csproxy - Code Signing Hosting Proxy 
  32 #include <Security/SecStaticCode.h> 
  33 #include <securityd_client/cshosting.h> 
  34 #include <security_utilities/cfmunge.h> 
  35 #include <security_utilities/casts.h> 
  36 #include <utilities/SecCFRelease.h> 
  39 // Construct a CodeSigningHost 
  41 CodeSigningHost::CodeSigningHost() 
  42         : mLock(Mutex::recursive
), mHostingState(noHosting
) 
  50 CodeSigningHost::~CodeSigningHost() 
  57 // Reset Code Signing Hosting state. 
  58 // This turns hosting off and clears all children. 
  60 void CodeSigningHost::reset() 
  62         StLock
<Mutex
> _(mLock
); 
  63         switch (mHostingState
) { 
  65                 break;  // nothing to do 
  67                 mHostingPort
.destroy(); 
  68                 mHostingPort 
= MACH_PORT_NULL
; 
  69         secnotice("SS", "%d host unregister", mHostingPort
.port()); 
  72                 Server::active().remove(*this); // unhook service handler 
  73                 mHostingPort
.destroy(); // destroy receive right 
  74                 mHostingState 
= noHosting
; 
  75                 mHostingPort 
= MACH_PORT_NULL
; 
  76                 mGuests
.erase(mGuests
.begin(), mGuests
.end()); 
  77         secnotice("SS", "%d host unregister", mHostingPort
.port()); 
  84 // Given a host reference (possibly NULL for the process itself), locate 
  85 // its most dedicated guest. This descends a contiguous chain of dedicated 
  86 // guests until it find a host that either has no guests, or whose guests 
  89 CodeSigningHost::Guest 
*CodeSigningHost::findHost(SecGuestRef hostRef
) 
  91         Guest 
*host 
= findGuest(hostRef
, true); 
  93                 if (Guest 
*guest 
= findGuest(host
)) 
  94                         if (guest
->dedicated
) { 
 104 // Look up guest by guestRef. 
 105 // Throws if we don't have a guest by that ref. 
 107 CodeSigningHost::Guest 
*CodeSigningHost::findGuest(SecGuestRef guestRef
, bool hostOk 
/* = false */) 
 109         GuestMap::iterator it 
= mGuests
.find(guestRef
); 
 110     if (it 
== mGuests
.end()) { 
 114                         MacOSError::throwMe(errSecCSNoSuchCode
); 
 117         assert(it
->first 
== it
->second
->guestRef()); 
 123 // Look up guest by attribute set. 
 124 // Returns the host if the attributes can't be found (*loose* interpretation). 
 125 // Throws if multiple guests are found (ambiguity). 
 126 // Implicitly matches dedicated guests no matter what attributes are requested. 
 128 CodeSigningHost::Guest 
*CodeSigningHost::findGuest(Guest 
*host
, const CssmData 
&attrData
) 
 130         CFRef
<CFDictionaryRef
> attrDict 
= attrData
 
 131                 ? makeCFDictionaryFrom(attrData
.data(), attrData
.length()) 
 132                 : makeCFDictionary(0); 
 133         CFDictionary 
attrs(attrDict
, errSecCSInvalidAttributeValues
); 
 135         // if a guest handle was provided, start with that - it must be valid or we fail 
 136         if (CFNumberRef canonical 
= attrs
.get
<CFNumberRef
>(kSecGuestAttributeCanonical
)) { 
 137                 // direct lookup by SecGuestRef (canonical guest handle) 
 138                 SecGuestRef guestRef 
= cfNumber
<SecGuestRef
>(canonical
); 
 139                 if (Guest 
*guest 
= findGuest(guestRef
, true))   // found guest handle 
 140                         if (guest
->isGuestOf(host
, loose
)) 
 141                                 host 
= guest
;           // new starting point 
 143                                 MacOSError::throwMe(errSecCSNoSuchCode
); // not a guest of given host 
 145                         MacOSError::throwMe(errSecCSNoSuchCode
); // not there at all 
 148         // now take the rest of the attrs 
 149         CFIndex count 
= CFDictionaryGetCount(attrs
); 
 150         CFTypeRef keys
[count
], values
[count
]; 
 151         CFDictionaryGetKeysAndValues(attrs
, keys
, values
); 
 153                 Guest 
*match 
= NULL
;    // previous match found 
 154         for (GuestMap::const_iterator it 
= mGuests
.begin(); it 
!= mGuests
.end(); ++it
) { 
 155             if (it
->second
->isGuestOf(host
, strict
)) { 
 156                 if (it
->second
->matches(count
, keys
, values
)) { 
 158                                                 MacOSError::throwMe(errSecCSMultipleGuests
);    // ambiguous 
 165                 if (!match
)             // nothing found 
 168                         host 
= match
;   // and repeat 
 174 // Find any guest of a given host. 
 175 // This will return a randomly chosen guest of this host if it has any, 
 176 // or NULL if it has none (i.e. it is not a host). 
 178 CodeSigningHost::Guest 
*CodeSigningHost::findGuest(Guest 
*host
) 
 180         for (GuestMap::const_iterator it 
= mGuests
.begin(); it 
!= mGuests
.end(); ++it
) 
 181                 if (it
->second
->isGuestOf(host
, strict
)) 
 188 // Register a hosting API service port where the host will dynamically 
 189 // answer hosting queries from interested parties. This switches the process 
 190 // to dynamic hosting mode, and is incompatible with proxy hosting. 
 192 void CodeSigningHost::registerCodeSigning(mach_port_t hostingPort
, SecCSFlags flags
) 
 194         StLock
<Mutex
> _(mLock
); 
 195         switch (mHostingState
) { 
 197                 mHostingPort 
= hostingPort
; 
 198                 mHostingState 
= dynamicHosting
; 
 199         secnotice("SS", "%d host register: %d", mHostingPort
.port(), mHostingPort
.port()); 
 202                 MacOSError::throwMe(errSecCSHostProtocolContradiction
); 
 208 // Create a guest entry for the given host and prepare to answer for it 
 209 // when dynamic hosting queries are received for it. 
 210 // This engages proxy hosting mode, and is incompatible with dynamic hosting mode. 
 212 SecGuestRef 
CodeSigningHost::createGuest(SecGuestRef hostRef
, 
 213                 uint32_t status
, const char *path
, 
 214                 const CssmData 
&cdhash
, const CssmData 
&attributes
, SecCSFlags flags
) 
 216         StLock
<Mutex
> _(mLock
); 
 217         if (path
[0] != '/')             // relative path (relative to what? :-) 
 218                 MacOSError::throwMe(errSecCSHostProtocolRelativePath
); 
 219         if (cdhash
.length() > maxUcspHashLength
) 
 220                 MacOSError::throwMe(errSecCSHostProtocolInvalidHash
); 
 222         // set up for hosting proxy services if nothing's there yet 
 223         switch (mHostingState
) { 
 224         case noHosting
:                                                                         // first hosting call, this host 
 225                 // set up proxy hosting 
 226                 mHostingPort
.allocate();                                                // allocate service port 
 227                 MachServer::Handler::port(mHostingPort
);                // put into Handler 
 228                 MachServer::active().add(*this);                                // start listening 
 229                 mHostingState 
= proxyHosting
;                                   // now proxying for this host 
 230         secnotice("SS", "%d host proxy: %d", mHostingPort
.port(), mHostingPort
.port()); 
 232         case proxyHosting
:                                                                      // already proxying 
 234         case dynamicHosting
:                                                            // in dynamic mode, can't switch 
 235                 MacOSError::throwMe(errSecCSHostProtocolContradiction
); 
 238         RefPointer
<Guest
> host 
= findHost(hostRef
); 
 239     if (RefPointer
<Guest
> knownGuest 
= findGuest(host
)) {       // got a guest already 
 240         if (flags 
& kSecCSDedicatedHost
) { 
 241                         MacOSError::throwMe(errSecCSHostProtocolDedicationError
);       // can't dedicate with other guests 
 242         } else if (knownGuest
->dedicated
) { 
 243                         MacOSError::throwMe(errSecCSHostProtocolDedicationError
);       // other guest is already dedicated 
 247         // create the new guest 
 248         RefPointer
<Guest
> guest 
= new Guest
; 
 250                 guest
->guestPath 
= host
->guestPath
; 
 251         guest
->guestPath
.push_back(int_cast
<CSSM_HANDLE
,SecGuestRef
>(guest
->handle())); 
 252         guest
->status 
= status
; 
 254         guest
->setAttributes(attributes
); 
 255         guest
->setHash(cdhash
, flags 
& kSecCSGenerateGuestHash
); 
 256         guest
->dedicated 
= (flags 
& kSecCSDedicatedHost
); 
 257         mGuests
[guest
->guestRef()] = guest
; 
 258     secnotice("SS", "%d guest create %d %d status:%d %d %s", mHostingPort
.port(), hostRef
, guest
->guestRef(), guest
->status
, flags
, guest
->path
.c_str()); 
 259         return guest
->guestRef(); 
 263 void CodeSigningHost::setGuestStatus(SecGuestRef guestRef
, uint32_t status
, const CssmData 
&attributes
) 
 265         StLock
<Mutex
> _(mLock
); 
 266         if (mHostingState 
!= proxyHosting
) 
 267                 MacOSError::throwMe(errSecCSHostProtocolNotProxy
); 
 268         Guest 
*guest 
= findGuest(guestRef
); 
 270         // state modification machine 
 271         if ((status 
& ~guest
->status
) & kSecCodeStatusValid
) 
 272                 MacOSError::throwMe(errSecCSHostProtocolStateError
); // can't set 
 273         if ((~status 
& guest
->status
) & (kSecCodeStatusHard 
| kSecCodeStatusKill
)) 
 274                 MacOSError::throwMe(errSecCSHostProtocolStateError
); // can't clear 
 275         guest
->status 
= status
; 
 276     secnotice("SS", "%d guest change %d %d", mHostingPort
.port(), guestRef
, status
); 
 278         // replace attributes if requested 
 280                 guest
->setAttributes(attributes
); 
 285 // Remove a guest previously introduced via createGuest(). 
 287 void CodeSigningHost::removeGuest(SecGuestRef hostRef
, SecGuestRef guestRef
) 
 289         StLock
<Mutex
> _(mLock
); 
 290         if (mHostingState 
!= proxyHosting
)  
 291                 MacOSError::throwMe(errSecCSHostProtocolNotProxy
); 
 292         RefPointer
<Guest
> host 
= findHost(hostRef
); 
 293         RefPointer
<Guest
> guest 
= findGuest(guestRef
); 
 294         if (guest
->dedicated
)   // can't remove a dedicated guest 
 295                 MacOSError::throwMe(errSecCSHostProtocolDedicationError
); 
 296         if (!guest
->isGuestOf(host
, strict
)) 
 297                 MacOSError::throwMe(errSecCSHostProtocolUnrelated
); 
 299     set
<SecGuestRef
> matchingGuests
; 
 301     for (auto &it 
: mGuests
) { 
 302                 if (it
.second
->isGuestOf(guest
, loose
)) { 
 303             matchingGuests
.insert(it
.first
); 
 307     for (auto &it 
: matchingGuests
) { 
 308         secnotice("SS", "%d guest destroy %d", mHostingPort
.port(), it
); 
 315 // The internal Guest object 
 317 CodeSigningHost::Guest::~Guest() 
 320 void CodeSigningHost::Guest::setAttributes(const CssmData 
&attrData
) 
 322         CFRef
<CFNumberRef
> guest 
= makeCFNumber(guestRef()); 
 324         CFDictionaryRef attrs 
= makeCFDictionaryFrom(attrData
.data(), attrData
.length()); 
 325                 attributes
.take(cfmake
<CFDictionaryRef
>("{+%O,%O=%O}", 
 326                         attrs
, kSecGuestAttributeCanonical
, guest
.get())); 
 327         CFReleaseNull(attrs
); 
 329                 attributes
.take(makeCFDictionary(1, kSecGuestAttributeCanonical
, guest
.get())); 
 333 CFDataRef 
CodeSigningHost::Guest::attrData() const 
 336                 mAttrData 
= makeCFData(this->attributes
.get()); 
 341 void CodeSigningHost::Guest::setHash(const CssmData 
&given
, bool generate
) 
 343         if (given
.length())             // explicitly given 
 344                 this->cdhash
.take(makeCFData(given
)); 
 345         else if (CFTypeRef hash 
= CFDictionaryGetValue(this->attributes
, kSecGuestAttributeHash
)) 
 346                 if (CFGetTypeID(hash
) == CFDataGetTypeID()) 
 347                         this->cdhash 
= CFDataRef(hash
); 
 349                         MacOSError::throwMe(errSecCSHostProtocolInvalidHash
); 
 350         else if (generate
) {            // generate from path (well, try) 
 351                 CFRef
<SecStaticCodeRef
> code
; 
 352                 MacOSError::check(SecStaticCodeCreateWithPath(CFTempURL(this->path
), kSecCSDefaultFlags
, &code
.aref())); 
 353                 CFRef
<CFDictionaryRef
> info
; 
 354                 MacOSError::check(SecCodeCopySigningInformation(code
, kSecCSDefaultFlags
, &info
.aref())); 
 355                 this->cdhash 
= CFDataRef(CFDictionaryGetValue(info
, kSecCodeInfoUnique
)); 
 360 bool CodeSigningHost::Guest::isGuestOf(Guest 
*host
, GuestCheck check
) const 
 362         vector
<SecGuestRef
> hostPath
; 
 364                 hostPath 
= host
->guestPath
; 
 365         if (hostPath
.size() <= guestPath
.size() 
 366                         && !memcmp(&hostPath
[0], &guestPath
[0], sizeof(SecGuestRef
) * hostPath
.size())) 
 367                 // hostPath is a prefix of guestPath 
 372                         return guestPath
.size() == hostPath
.size() + 1; // immediate guest 
 379 // Check to see if a given guest matches the (possibly empty) attribute set provided 
 380 // (in broken-open form, for efficiency). A dedicated guest will match all attribute 
 381 // specifications, even empty ones. A non-dedicated guest matches if at least one 
 382 // attribute value requested matches exactly (in the sense of CFEqual) that given 
 383 // by the host for this guest. 
 385 bool CodeSigningHost::Guest::matches(CFIndex count
, CFTypeRef keys
[], CFTypeRef values
[]) const 
 389         for (CFIndex n 
= 0; n 
< count
; n
++) { 
 390                 CFStringRef key 
= CFStringRef(keys
[n
]); 
 391                 if (CFEqual(key
, kSecGuestAttributeCanonical
))  // ignore canonical attribute (handled earlier) 
 393                 if (CFTypeRef value 
= CFDictionaryGetValue(attributes
, key
)) 
 394                         if (CFEqual(value
, values
[n
])) 
 402 // The MachServer dispatch handler for proxy hosting. 
 405 // give MIG handlers access to the object lock 
 406 class CodeSigningHost::Lock 
: private StLock
<Mutex
> { 
 408         Lock(CodeSigningHost 
*host
) : StLock
<Mutex
>(host
->mLock
) { } 
 412 boolean_t 
cshosting_server(mach_msg_header_t 
*, mach_msg_header_t 
*); 
 414 static ThreadNexus
<CodeSigningHost 
*> context
; 
 416 boolean_t 
CodeSigningHost::handle(mach_msg_header_t 
*in
, mach_msg_header_t 
*out
) 
 418         CodeSigningHost::Lock 
_(this); 
 420         return cshosting_server(in
, out
); 
 425 // Proxy implementation of Code Signing Hosting protocol 
 427 #define CSH_ARGS        mach_port_t servicePort, mach_port_t replyPort, OSStatus *rcode 
 429 #define BEGIN_IPC       try { 
 430 #define END_IPC         *rcode = noErr; } \ 
 431         catch (const CommonError &err) { *rcode = err.osStatus(); } \ 
 432         catch (...) { *rcode = errSecCSInternalError; } \ 
 435 #define DATA_IN(base)   void *base, mach_msg_type_number_t base##Length 
 436 #define DATA_OUT(base)  void **base, mach_msg_type_number_t *base##Length 
 437 #define DATA(base)              CssmData(base, base##Length) 
 441 // Find a guest by arbitrary attribute set. 
 443 // This returns an array of canonical guest references describing the path 
 444 // from the host given to the guest found. If the host itself is returned 
 445 // as a guest, this will be an empty array (zero length). 
 447 // The subhost return argument may in the future return the hosting port for 
 448 // a guest who dynamically manages its hosting (thus breaking out of proxy mode), 
 449 // but this is not yet implemented. 
 451 kern_return_t 
cshosting_server_findGuest(CSH_ARGS
, SecGuestRef hostRef
, 
 453         GuestChain 
*foundGuest
, mach_msg_type_number_t 
*depth
, mach_port_t 
*subhost
) 
 457         *subhost 
= MACH_PORT_NULL
;      // preset no sub-hosting port returned 
 459         Process::Guest 
*host 
= context()->findGuest(hostRef
, true); 
 460         if (Process::Guest 
*guest 
= context()->findGuest(host
, DATA(attributes
))) { 
 461                 *foundGuest 
= &guest
->guestPath
[0]; 
 462                 *depth 
= int_cast
<size_t, mach_msg_type_number_t
>(guest
->guestPath
.size()); 
 472 // Retrieve the path to a guest specified by canonical reference. 
 474 kern_return_t 
cshosting_server_identifyGuest(CSH_ARGS
, SecGuestRef guestRef
, 
 475         char *path
, char *hash
, uint32_t *hashLength
, DATA_OUT(attributes
)) 
 478         CodeSigningHost::Guest 
*guest 
= context()->findGuest(guestRef
); 
 479         strncpy(path
, guest
->path
.c_str(), MAXPATHLEN
); 
 483                 *hashLength 
= int_cast
<size_t, uint32_t>(CFDataGetLength(guest
->cdhash
)); 
 484                 assert(*hashLength 
<= maxUcspHashLength
); 
 485                 memcpy(hash
, CFDataGetBytePtr(guest
->cdhash
), *hashLength
); 
 487                 *hashLength 
= 0;        // unavailable 
 489         // visible attributes. This proxy returns all attributes set by the host 
 490         CFDataRef attrData 
= guest
->attrData(); // (the guest will cache this until it dies) 
 491         *attributes 
= (void *)CFDataGetBytePtr(attrData
);       // MIG botch (it doesn't need a writable pointer) 
 492         *attributesLength 
= int_cast
<CFIndex
, mach_msg_type_number_t
>(CFDataGetLength(attrData
)); 
 499 // Retrieve the status word for a guest specified by canonical reference. 
 501 kern_return_t 
cshosting_server_guestStatus(CSH_ARGS
, SecGuestRef guestRef
, uint32_t *status
) 
 504         *status 
= context()->findGuest(guestRef
)->status
; 
 512 #if defined(DEBUGDUMP) 
 514 void CodeSigningHost::dump() const 
 516         StLock
<Mutex
> _(mLock
); 
 517         switch (mHostingState
) { 
 521                 Debug::dump(" dynamic host port=%d", mHostingPort
.port()); 
 524                 Debug::dump(" proxy-host port=%d", mHostingPort
.port()); 
 525                 if (!mGuests
.empty()) { 
 526                         Debug::dump(" %d guests={", int(mGuests
.size())); 
 527                         for (GuestMap::const_iterator it 
= mGuests
.begin(); it 
!= mGuests
.end(); ++it
) { 
 528                                 if (it 
!= mGuests
.begin()) 
 538 void CodeSigningHost::Guest::dump() const 
 540         Debug::dump("%s[", path
.c_str()); 
 541         for (vector
<SecGuestRef
>::const_iterator it 
= guestPath
.begin(); it 
!= guestPath
.end(); ++it
) { 
 542                 if (it 
!= guestPath
.begin()) 
 544                 Debug::dump("0x%x", *it
); 
 546         Debug::dump("; status=0x%x attrs=%s]", 
 547                 status
, cfStringRelease(CFCopyDescription(attributes
)).c_str());