]> git.saurik.com Git - apple/security.git/blobdiff - OSX/libsecurity_codesigning/lib/notarization.cpp
Security-58286.200.222.tar.gz
[apple/security.git] / 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 (file)
index 0000000..91a2c15
--- /dev/null
@@ -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<CFErrorRef> 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<CFDataRef> cd;
+       CFRef<CFErrorRef> 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<CFDataRef> 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<CFDataRef> 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);
+}
+
+}
+}