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
);