]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_codesigning/lib/notarization.cpp
Security-59306.101.1.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / 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 }
303 }