notarization.cpp
1 /* 2 * Copyright (c) 2018 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 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. 12 * 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. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 #include "SecAssessment.h" 24 #include "notarization.h" 25 #include <security_utilities/unix++.h> 26 27 typedef struct __attribute__((packed)) _package_trailer { 28 uint8_t magic[4]; 29 uint16_t version; 30 uint16_t type; 31 uint32_t length; 32 uint8_t reserved[4]; 33 } package_trailer_t; 34 35 enum TrailerType { 36 TrailerTypeInvalid = 0, 37 TrailerTypeTerminator, 38 TrailerTypeTicket, 39 }; 40 41 static const char *TrailerMagic = "t8lr"; 42 43 namespace Security { 44 namespace CodeSigning { 45 46 static void 47 registerStapledTicketWithSystem(CFDataRef data) 48 { 49 secinfo("notarization", "Registering stapled ticket with system"); 50 51 #if TARGET_OS_OSX 52 CFRef<CFErrorRef> error; 53 if (!SecAssessmentTicketRegister(data, &error.aref())) { 54 secerror("Error registering stapled ticket: %@", error.get()); 55 } 56 #endif // TARGET_OS_OSX 57 } 58 59 bool 60 checkNotarizationServiceForRevocation(CFDataRef hash, SecCSDigestAlgorithm hashType, double *date) 61 { 62 bool is_revoked = false; 63 64 secinfo("notarization", "checking with online notarization service for hash: %@", hash); 65 66 #if TARGET_OS_OSX 67 CFRef<CFErrorRef> error; 68 if (!SecAssessmentTicketLookup(hash, hashType, kSecAssessmentTicketFlagForceOnlineCheck, date, &error.aref())) { 69 CFIndex err = CFErrorGetCode(error); 70 if (err == EACCES) { 71 secerror("Notarization daemon found revoked hash: %@", hash); 72 is_revoked = true; 73 } else { 74 secerror("Error checking with notarization daemon: %ld", err); 75 } 76 } 77 #endif 78 79 return is_revoked; 80 } 81 82 bool 83 isNotarized(const Requirement::Context *context) 84 { 85 CFRef<CFDataRef> cd; 86 CFRef<CFErrorRef> error; 87 bool is_notarized = false; 88 SecCSDigestAlgorithm hashType = kSecCodeSignatureNoHash; 89 90 if (context == NULL) { 91 is_notarized = false; 92 goto lb_exit; 93 } 94 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; 101 } 102 103 if (cd.get() == NULL) { 104 // No cdhash means we can't check notarization. 105 is_notarized = false; 106 goto lb_exit; 107 } 108 109 secinfo("notarization", "checking notarization on %d, %@", hashType, cd.get()); 110 111 #if TARGET_OS_OSX 112 if (SecAssessmentTicketLookup(cd, hashType, kSecAssessmentTicketFlagDefault, NULL, &error.aref())) { 113 is_notarized = true; 114 } else { 115 is_notarized = false; 116 if (error.get() != NULL) { 117 secerror("Error checking with notarization daemon: %ld", CFErrorGetCode(error)); 118 } 119 } 120 #endif 121 122 lb_exit: 123 secinfo("notarization", "isNotarized = %d", is_notarized); 124 return is_notarized; 125 } 126 127 void 128 registerStapledTicketInPackage(const std::string& path) 129 { 130 int fd = 0; 131 package_trailer_t trailer; 132 off_t readOffset = 0; 133 size_t bytesRead = 0; 134 off_t backSeek = 0; 135 uint8_t *ticketData = NULL; 136 boolean_t ticketTrailerFound = false; 137 CFRef<CFDataRef> data; 138 139 secinfo("notarization", "Extracting ticket from package: %s", path.c_str()); 140 141 fd = open(path.c_str(), O_RDONLY); 142 if (fd <= 0) { 143 secerror("cannot open package for reading"); 144 goto lb_exit; 145 } 146 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); 151 goto lb_exit; 152 } 153 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); 158 goto lb_exit; 159 } 160 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"); 164 goto lb_exit; 165 } 166 167 switch (trailer.type) { 168 case TrailerTypeTicket: 169 ticketTrailerFound = true; 170 break; 171 case TrailerTypeTerminator: 172 // Found a terminator before a trailer, so just exit. 173 secinfo("notarization", "package had a trailer, but no ticket trailers"); 174 goto lb_exit; 175 case TrailerTypeInvalid: 176 secinfo("notarization", "package had an invalid trailer"); 177 goto lb_exit; 178 default: 179 // it's an unsupported trailer type, so skip it. 180 break; 181 } 182 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); 190 } 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); 194 goto lb_exit; 195 } 196 } 197 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"); 202 goto lb_exit; 203 } 204 205 bytesRead = read(fd, ticketData, trailer.length); 206 if (bytesRead != trailer.length) { 207 secerror("unable to read entire ticket (error - %d)", errno); 208 goto lb_exit; 209 } 210 211 data.take(makeCFDataMalloc(ticketData, trailer.length)); 212 if (data.get() == NULL) { 213 secerror("unable to create cfdata for notarization"); 214 goto lb_exit; 215 } 216 217 secinfo("notarization", "successfully found stapled ticket for: %s", path.c_str()); 218 registerStapledTicketWithSystem(data); 219 220 lb_exit: 221 if (fd) { 222 close(fd); 223 } 224 } 225 226 void 227 registerStapledTicketInBundle(const std::string& path) 228 { 229 int fd = 0; 230 struct stat st; 231 uint8_t *ticketData = NULL; 232 size_t ticketLength = 0; 233 size_t bytesRead = 0; 234 CFRef<CFDataRef> data; 235 std::string ticketLocation = path + "/Contents/CodeResources"; 236 237 secinfo("notarization", "Extracting ticket from bundle: %s", path.c_str()); 238 239 fd = open(ticketLocation.c_str(), O_RDONLY); 240 if (fd <= 0) { 241 // Only print an error if the file exists, otherwise its an expected early exit case. 242 if (errno != ENOENT) { 243 secerror("cannot open stapled file for reading: %d", errno); 244 } 245 goto lb_exit; 246 } 247 248 if (fstat(fd, &st)) { 249 secerror("unable to stat stapling file: %d", errno); 250 goto lb_exit; 251 } 252 253 if ((st.st_mode & S_IFREG) != S_IFREG) { 254 secerror("stapling is not a regular file"); 255 goto lb_exit; 256 } 257 258 if (st.st_size <= INT_MAX) { 259 ticketLength = (size_t)st.st_size; 260 } else { 261 secerror("ticket size was too large: %lld", st.st_size); 262 goto lb_exit; 263 } 264 265 ticketData = (uint8_t*)malloc(ticketLength); 266 if (ticketData == NULL) { 267 secerror("unable to allocate data for ticket"); 268 goto lb_exit; 269 } 270 271 bytesRead = read(fd, ticketData, ticketLength); 272 if (bytesRead != ticketLength) { 273 secerror("unable to read entire ticket from bundle"); 274 goto lb_exit; 275 } 276 277 data.take(makeCFDataMalloc(ticketData, ticketLength)); 278 if (data.get() == NULL) { 279 secerror("unable to create cfdata for notarization"); 280 goto lb_exit; 281 } 282 283 secinfo("notarization", "successfully found stapled ticket for: %s", path.c_str()); 284 registerStapledTicketWithSystem(data); 285 286 lb_exit: 287 if (fd) { 288 close(fd); 289 } 290 } 291 292 void 293 registerStapledTicketInDMG(CFDataRef ticketData) 294 { 295 if (ticketData == NULL) { 296 return; 297 } 298 secinfo("notarization", "successfully found stapled ticket in DMG"); 299 registerStapledTicketWithSystem(ticketData); 300 } 301 302 void 303 registerStapledTicketInMachO(CFDataRef ticketData) 304 { 305 if (ticketData == NULL) { 306 return; 307 } 308 secinfo("notarization", "successfully found stapled ticket in MachO"); 309 registerStapledTicketWithSystem(ticketData); 310 } 311 312 } 313 }