X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/90dc47c27df1983f6ebc252b0c4b94c8718fe52d..79b9da22a1f4b26279940d285c1bc28ce4e99252:/OSX/libsecurity_codesigning/lib/notarization.cpp diff --git a/OSX/libsecurity_codesigning/lib/notarization.cpp b/OSX/libsecurity_codesigning/lib/notarization.cpp new file mode 100644 index 00000000..91a2c15f --- /dev/null +++ b/OSX/libsecurity_codesigning/lib/notarization.cpp @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2018 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#include "SecAssessment.h" +#include "notarization.h" +#include "unix++.h" + +typedef struct __attribute__((packed)) _package_trailer { + uint8_t magic[4]; + uint16_t version; + uint16_t type; + uint32_t length; + uint8_t reserved[4]; +} package_trailer_t; + +enum TrailerType { + TrailerTypeInvalid = 0, + TrailerTypeTerminator, + TrailerTypeTicket, +}; + +static const char *TrailerMagic = "t8lr"; + +namespace Security { +namespace CodeSigning { + +static void +registerStapledTicketWithSystem(CFDataRef data) +{ + secinfo("notarization", "Registering stapled ticket with system"); + +#if TARGET_OS_OSX + CFErrorRef error = NULL; + if (!SecAssessmentTicketRegister(data, &error)) { + secerror("Error registering stapled ticket: %@", data); + } +#endif // TARGET_OS_OSX +} + +bool +checkNotarizationServiceForRevocation(CFDataRef hash, SecCSDigestAlgorithm hashType, double *date) +{ + bool is_revoked = false; + + secinfo("notarization", "checking with online notarization service for hash: %@", hash); + +#if TARGET_OS_OSX + CFRef error; + if (!SecAssessmentTicketLookup(hash, hashType, kSecAssessmentTicketFlagForceOnlineCheck, date, &error.aref())) { + CFIndex err = CFErrorGetCode(error); + if (err == EACCES) { + secerror("Notarization daemon found revoked hash: %@", hash); + is_revoked = true; + } else { + secerror("Error checking with notarization daemon: %ld", err); + } + } +#endif + + return is_revoked; +} + +bool +isNotarized(const Requirement::Context *context) +{ + CFRef cd; + CFRef error; + bool is_notarized = false; + SecCSDigestAlgorithm hashType = kSecCodeSignatureNoHash; + + if (context == NULL) { + is_notarized = false; + goto lb_exit; + } + + if (context->directory) { + cd.take(context->directory->cdhash()); + hashType = (SecCSDigestAlgorithm)context->directory->hashType; + } else if (context->packageChecksum) { + cd = context->packageChecksum; + hashType = context->packageAlgorithm; + } + + if (cd.get() == NULL) { + // No cdhash means we can't check notarization. + is_notarized = false; + goto lb_exit; + } + + secinfo("notarization", "checking notarization on %d, %@", hashType, cd.get()); + +#if TARGET_OS_OSX + if (SecAssessmentTicketLookup(cd, hashType, kSecAssessmentTicketFlagDefault, NULL, &error.aref())) { + is_notarized = true; + } else { + is_notarized = false; + if (error.get() != NULL) { + secerror("Error checking with notarization daemon: %ld", CFErrorGetCode(error)); + } + } +#endif + +lb_exit: + secinfo("notarization", "isNotarized = %d", is_notarized); + return is_notarized; +} + +void +registerStapledTicketInPackage(const std::string& path) +{ + int fd = 0; + package_trailer_t trailer; + off_t readOffset = 0; + size_t bytesRead = 0; + off_t backSeek = 0; + uint8_t *ticketData = NULL; + boolean_t ticketTrailerFound = false; + CFRef data; + + secinfo("notarization", "Extracting ticket from package: %s", path.c_str()); + + fd = open(path.c_str(), O_RDONLY); + if (fd <= 0) { + secerror("cannot open package for reading"); + goto lb_exit; + } + + bzero(&trailer, sizeof(trailer)); + readOffset = lseek(fd, -sizeof(trailer), SEEK_END); + if (readOffset <= 0) { + secerror("could not scan for first trailer on package (error - %d)", errno); + goto lb_exit; + } + + while (!ticketTrailerFound) { + bytesRead = read(fd, &trailer, sizeof(trailer)); + if (bytesRead != sizeof(trailer)) { + secerror("could not read next trailer from package (error - %d)", errno); + goto lb_exit; + } + + if (memcmp(trailer.magic, TrailerMagic, strlen(TrailerMagic)) != 0) { + // Most packages will not be stapled, so this isn't really an error. + secdebug("notarization", "package did not end in a trailer"); + goto lb_exit; + } + + switch (trailer.type) { + case TrailerTypeTicket: + ticketTrailerFound = true; + break; + case TrailerTypeTerminator: + // Found a terminator before a trailer, so just exit. + secinfo("notarization", "package had a trailer, but no ticket trailers"); + goto lb_exit; + case TrailerTypeInvalid: + secinfo("notarization", "package had an invalid trailer"); + goto lb_exit; + default: + // it's an unsupported trailer type, so skip it. + break; + } + + // If we're here, it's either a ticket or an unknown trailer. In both cases we can definitely seek back to the + // beginning of the data pointed to by this trailer, which is the length of its data and the size of the trailer itself. + backSeek = -1 * (sizeof(trailer) + trailer.length); + if (!ticketTrailerFound) { + // 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 + // trailer blob to prepare for reading it. + backSeek -= sizeof(trailer); + } + readOffset = lseek(fd, backSeek, SEEK_CUR); + if (readOffset <= 0) { + secerror("could not scan backwards (%lld) for next trailer on package (error - %d)", backSeek, errno); + goto lb_exit; + } + } + + // If we got here, we have a valid ticket trailer and already seeked back to the beginning of its data. + ticketData = (uint8_t*)malloc(trailer.length); + if (ticketData == NULL) { + secerror("could not allocate memory for ticket"); + goto lb_exit; + } + + bytesRead = read(fd, ticketData, trailer.length); + if (bytesRead != trailer.length) { + secerror("unable to read entire ticket (error - %d)", errno); + goto lb_exit; + } + + data = CFDataCreateWithBytesNoCopy(NULL, ticketData, trailer.length, NULL); + if (data.get() == NULL) { + secerror("unable to create cfdata for notarization"); + goto lb_exit; + } + + secinfo("notarization", "successfully found stapled ticket for: %s", path.c_str()); + registerStapledTicketWithSystem(data); + +lb_exit: + if (fd) { + close(fd); + } + if (ticketData) { + free(ticketData); + } +} + +void +registerStapledTicketInBundle(const std::string& path) +{ + int fd = 0; + struct stat st; + uint8_t *ticketData = NULL; + size_t ticketLength = 0; + size_t bytesRead = 0; + CFRef data; + std::string ticketLocation = path + "/Contents/CodeResources"; + + secinfo("notarization", "Extracting ticket from bundle: %s", path.c_str()); + + fd = open(ticketLocation.c_str(), O_RDONLY); + if (fd <= 0) { + // Only print an error if the file exists, otherwise its an expected early exit case. + if (errno != ENOENT) { + secerror("cannot open stapled file for reading: %d", errno); + } + goto lb_exit; + } + + if (fstat(fd, &st)) { + secerror("unable to stat stapling file: %d", errno); + goto lb_exit; + } + + if ((st.st_mode & S_IFREG) != S_IFREG) { + secerror("stapling is not a regular file"); + goto lb_exit; + } + + if (st.st_size <= INT_MAX) { + ticketLength = (size_t)st.st_size; + } else { + secerror("ticket size was too large: %lld", st.st_size); + goto lb_exit; + } + + ticketData = (uint8_t*)malloc(ticketLength); + if (ticketData == NULL) { + secerror("unable to allocate data for ticket"); + goto lb_exit; + } + + bytesRead = read(fd, ticketData, ticketLength); + if (bytesRead != ticketLength) { + secerror("unable to read entire ticket from bundle"); + goto lb_exit; + } + + data = CFDataCreateWithBytesNoCopy(NULL, ticketData, ticketLength, NULL); + if (data.get() == NULL) { + secerror("unable to create cfdata for notarization"); + goto lb_exit; + } + + secinfo("notarization", "successfully found stapled ticket for: %s", path.c_str()); + registerStapledTicketWithSystem(data); + +lb_exit: + if (fd) { + close(fd); + } + if (ticketData) { + free(ticketData); + } +} + +void +registerStapledTicketInDMG(CFDataRef ticketData) +{ + if (ticketData == NULL) { + return; + } + secinfo("notarization", "successfully found stapled ticket in DMG"); + registerStapledTicketWithSystem(ticketData); +} + +} +}