2  * Copyright (c) 2018 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@ 
  23 #include "SecAssessment.h" 
  24 #include "notarization.h" 
  25 #include <security_utilities/unix++.h> 
  27 typedef struct __attribute__((packed
)) _package_trailer 
{ 
  36         TrailerTypeInvalid 
= 0, 
  37         TrailerTypeTerminator
, 
  41 static const char *TrailerMagic 
= "t8lr"; 
  44 namespace CodeSigning 
{ 
  47 registerStapledTicketWithSystem(CFDataRef data
) 
  49         secinfo("notarization", "Registering stapled ticket with system"); 
  52         CFErrorRef error 
= NULL
; 
  53         if (!SecAssessmentTicketRegister(data
, &error
)) { 
  54                 secerror("Error registering stapled ticket: %@", data
); 
  56 #endif // TARGET_OS_OSX 
  60 checkNotarizationServiceForRevocation(CFDataRef hash
, SecCSDigestAlgorithm hashType
, double *date
) 
  62         bool is_revoked 
= false; 
  64         secinfo("notarization", "checking with online notarization service for hash: %@", hash
); 
  67         CFRef
<CFErrorRef
> error
; 
  68         if (!SecAssessmentTicketLookup(hash
, hashType
, kSecAssessmentTicketFlagForceOnlineCheck
, date
, &error
.aref())) { 
  69                 CFIndex err 
= CFErrorGetCode(error
); 
  71                         secerror("Notarization daemon found revoked hash: %@", hash
); 
  74                         secerror("Error checking with notarization daemon: %ld", err
); 
  83 isNotarized(const Requirement::Context 
*context
) 
  86         CFRef
<CFErrorRef
> error
; 
  87         bool is_notarized 
= false; 
  88         SecCSDigestAlgorithm hashType 
= kSecCodeSignatureNoHash
; 
  90         if (context 
== NULL
) { 
  95         if (context
->directory
) { 
  96                 cd
.take(context
->directory
->cdhash()); 
  97                 hashType 
= (SecCSDigestAlgorithm
)context
->directory
->hashType
; 
  98         } else if (context
->packageChecksum
) { 
  99                 cd 
= context
->packageChecksum
; 
 100                 hashType 
= context
->packageAlgorithm
; 
 103         if (cd
.get() == NULL
) { 
 104                 // No cdhash means we can't check notarization. 
 105                 is_notarized 
= false; 
 109         secinfo("notarization", "checking notarization on %d, %@", hashType
, cd
.get()); 
 112         if (SecAssessmentTicketLookup(cd
, hashType
, kSecAssessmentTicketFlagDefault
, NULL
, &error
.aref())) { 
 115                 is_notarized 
= false; 
 116                 if (error
.get() != NULL
) { 
 117                         secerror("Error checking with notarization daemon: %ld", CFErrorGetCode(error
)); 
 123         secinfo("notarization", "isNotarized = %d", is_notarized
); 
 128 registerStapledTicketInPackage(const std::string
& path
) 
 131         package_trailer_t trailer
; 
 132         off_t readOffset 
= 0; 
 133         size_t bytesRead 
= 0; 
 135         uint8_t *ticketData 
= NULL
; 
 136         boolean_t ticketTrailerFound 
= false; 
 137         CFRef
<CFDataRef
> data
; 
 139         secinfo("notarization", "Extracting ticket from package: %s", path
.c_str()); 
 141         fd 
= open(path
.c_str(), O_RDONLY
); 
 143                 secerror("cannot open package for reading"); 
 147         bzero(&trailer
, sizeof(trailer
)); 
 148         readOffset 
= lseek(fd
, -sizeof(trailer
), SEEK_END
); 
 149         if (readOffset 
<= 0) { 
 150                 secerror("could not scan for first trailer on package (error - %d)", errno
); 
 154         while (!ticketTrailerFound
) { 
 155                 bytesRead 
= read(fd
, &trailer
, sizeof(trailer
)); 
 156                 if (bytesRead 
!= sizeof(trailer
)) { 
 157                         secerror("could not read next trailer from package (error - %d)", errno
); 
 161                 if (memcmp(trailer
.magic
, TrailerMagic
, strlen(TrailerMagic
)) != 0) { 
 162                         // Most packages will not be stapled, so this isn't really an error. 
 163                         secdebug("notarization", "package did not end in a trailer"); 
 167                 switch (trailer
.type
) { 
 168                         case TrailerTypeTicket
: 
 169                                 ticketTrailerFound 
= true; 
 171                         case TrailerTypeTerminator
: 
 172                                 // Found a terminator before a trailer, so just exit. 
 173                                 secinfo("notarization", "package had a trailer, but no ticket trailers"); 
 175                         case TrailerTypeInvalid
: 
 176                                 secinfo("notarization", "package had an invalid trailer"); 
 179                                 // it's an unsupported trailer type, so skip it. 
 183                 // If we're here, it's either a ticket or an unknown trailer.  In both cases we can definitely seek back to the 
 184                 // beginning of the data pointed to by this trailer, which is the length of its data and the size of the trailer itself. 
 185                 backSeek 
= -1 * (sizeof(trailer
) + trailer
.length
); 
 186                 if (!ticketTrailerFound
) { 
 187                         // If we didn't find a ticket, we're about to iterate again and want to read the next trailer so seek back an additional 
 188                         // trailer blob to prepare for reading it. 
 189                         backSeek 
-= sizeof(trailer
); 
 191                 readOffset 
= lseek(fd
, backSeek
, SEEK_CUR
); 
 192                 if (readOffset 
<= 0) { 
 193                         secerror("could not scan backwards (%lld) for next trailer on package (error - %d)", backSeek
, errno
); 
 198         // If we got here, we have a valid ticket trailer and already seeked back to the beginning of its data. 
 199         ticketData 
= (uint8_t*)malloc(trailer
.length
); 
 200         if (ticketData 
== NULL
) { 
 201                 secerror("could not allocate memory for ticket"); 
 205         bytesRead 
= read(fd
, ticketData
, trailer
.length
); 
 206         if (bytesRead 
!= trailer
.length
) { 
 207                 secerror("unable to read entire ticket (error - %d)", errno
); 
 211         data 
= CFDataCreateWithBytesNoCopy(NULL
, ticketData
, trailer
.length
, NULL
); 
 212         if (data
.get() == NULL
) { 
 213                 secerror("unable to create cfdata for notarization"); 
 217         secinfo("notarization", "successfully found stapled ticket for: %s", path
.c_str()); 
 218         registerStapledTicketWithSystem(data
); 
 230 registerStapledTicketInBundle(const std::string
& path
) 
 234         uint8_t *ticketData 
= NULL
; 
 235         size_t ticketLength 
= 0; 
 236         size_t bytesRead 
= 0; 
 237         CFRef
<CFDataRef
> data
; 
 238         std::string ticketLocation 
= path 
+ "/Contents/CodeResources"; 
 240         secinfo("notarization", "Extracting ticket from bundle: %s", path
.c_str()); 
 242         fd 
=  open(ticketLocation
.c_str(), O_RDONLY
); 
 244                 // Only print an error if the file exists, otherwise its an expected early exit case. 
 245                 if (errno 
!= ENOENT
) { 
 246                         secerror("cannot open stapled file for reading: %d", errno
); 
 251         if (fstat(fd
, &st
)) { 
 252                 secerror("unable to stat stapling file: %d", errno
); 
 256         if ((st
.st_mode 
& S_IFREG
) != S_IFREG
) { 
 257                 secerror("stapling is not a regular file"); 
 261         if (st
.st_size 
<= INT_MAX
) { 
 262                 ticketLength 
= (size_t)st
.st_size
; 
 264                 secerror("ticket size was too large: %lld", st
.st_size
); 
 268         ticketData 
= (uint8_t*)malloc(ticketLength
); 
 269         if (ticketData 
== NULL
) { 
 270                 secerror("unable to allocate data for ticket"); 
 274         bytesRead 
= read(fd
, ticketData
, ticketLength
); 
 275         if (bytesRead 
!= ticketLength
) { 
 276                 secerror("unable to read entire ticket from bundle"); 
 280         data 
= CFDataCreateWithBytesNoCopy(NULL
, ticketData
, ticketLength
, NULL
); 
 281         if (data
.get() == NULL
) { 
 282                 secerror("unable to create cfdata for notarization"); 
 286         secinfo("notarization", "successfully found stapled ticket for: %s", path
.c_str()); 
 287         registerStapledTicketWithSystem(data
); 
 299 registerStapledTicketInDMG(CFDataRef ticketData
) 
 301         if (ticketData 
== NULL
) { 
 304         secinfo("notarization", "successfully found stapled ticket in DMG"); 
 305         registerStapledTicketWithSystem(ticketData
);