5 * Created by Conrad Sauerwald on 7/9/08.
6 * Copyright 2008 __MyCompanyName__. All rights reserved.
21 #include <sys/socket.h>
26 #define DEBUG_ASSERT_PRODUCTION_CODE 0
27 #include <AssertMacros.h>
29 #include <CoreFoundation/CoreFoundation.h>
31 #define log(format, args...) \
32 fprintf(stderr, "codesign_wrapper " format "\n", ##args);
33 #define cflog(format, args...) do { \
34 CFStringRef logstr = CFStringCreateWithFormat(NULL, NULL, CFSTR(format), ##args); \
35 if (logstr) { CFShow(logstr); CFRelease(logstr); } \
38 enum { CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171 };
46 extract_entitlements_blob(const uint8_t *data
, size_t length
)
48 CFDataRef entitlements
= NULL
;
49 cs_blob_index
*csbi
= (cs_blob_index
*)data
;
51 require(data
&& length
, out
);
52 require(csbi
->type
== ntohl(CSMAGIC_EMBEDDED_ENTITLEMENTS
), out
);
53 require(length
== ntohl(csbi
->offset
), out
);
54 entitlements
= CFDataCreate(kCFAllocatorDefault
,
55 (uint8_t*)(data
+ sizeof(cs_blob_index
)),
56 (CFIndex
)(length
- sizeof(cs_blob_index
)));
62 bool valid_application_identifier
;
63 bool valid_keychain_access_group
;
64 bool illegal_entitlement
;
65 } filter_whitelist_ctx
;
68 filter_entitlement(const void *key
, const void *value
,
69 filter_whitelist_ctx
*ctx
)
71 if (CFEqual(key
, CFSTR("application-identifier"))) {
73 if (CFGetTypeID(value
) != CFStringGetTypeID()) {
74 cflog("ERR: Illegal entitlement value %@ for key %@", value
, key
);
78 // log it for posterity
79 cflog("NOTICE: application-identifier := '%@'", value
);
81 // - put in an application-identifier that is messed up: <char-string>.<char-string-repeat>.
82 // split ident by periods and make sure the first two are not the same
83 // - put in an application-identifier they're not allowed to have: but we have no way to tell, although "apple" is illegal
84 // is apple, superseded by doesn't have at least 2 components split by a period
86 CFArrayRef identifier_pieces
= CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault
, value
, CFSTR(".")); /* No separators in the string returns array with that string; string == sep returns two empty strings */
87 if (!identifier_pieces
|| (CFArrayGetCount(identifier_pieces
) < 2)) {
88 cflog("ERR: Malformed identifier %@ := %@", key
, value
);
92 // doubled-up identifier
93 if (CFEqual(CFArrayGetValueAtIndex(identifier_pieces
, 0),
94 CFArrayGetValueAtIndex(identifier_pieces
, 1))) {
95 cflog("ERR: Malformed identifier %@ := %@", key
, value
);
99 // incomplete identifier: "blabla."
100 if (CFEqual(CFArrayGetValueAtIndex(identifier_pieces
, 1), CFSTR(""))) {
101 cflog("ERR: Malformed identifier %@ := %@", key
, value
);
105 ctx
->valid_application_identifier
= true;
109 if (CFEqual(key
, CFSTR("keychain-access-groups"))) {
110 // if there is one, false until proven correct
111 ctx
->valid_keychain_access_group
= false;
113 // log it for posterity - we're not expecting people to use it yet
114 cflog("NOTICE: keychain-access-groups := %@", value
);
116 // - put in keychain-access-groups containing "apple"
117 if (CFGetTypeID(value
) == CFStringGetTypeID()) {
118 if (CFEqual(CFSTR("apple"), value
) ||
119 (CFStringFind(value
, CFSTR("*"), 0).location
!= kCFNotFound
)) {
120 cflog("ERR: Illegal keychain access group value %@ for key %@", value
, key
);
123 } else if (CFGetTypeID(value
) == CFArrayGetTypeID()) {
124 CFIndex i
, count
= CFArrayGetCount(value
);
125 for (i
=0; i
<count
; i
++) {
126 CFStringRef val
= (CFStringRef
)CFArrayGetValueAtIndex(value
, i
);
127 if (CFGetTypeID(val
) != CFStringGetTypeID()) {
128 cflog("ERR: Illegal value in keychain access groups array %@", val
);
131 if (CFEqual(CFSTR("apple"), val
) ||
132 (CFStringFind(val
, CFSTR("*"), 0).location
!= kCFNotFound
)) {
133 cflog("ERR: Illegal keychain access group value %@ for key %@", value
, key
);
138 cflog("ERR: Illegal entitlement value %@ for key %@", value
, key
);
142 ctx
->valid_keychain_access_group
= true;
146 // - double check there's no "get-task-allow"
147 // - nothing else should be allowed
148 cflog("ERR: Illegal entitlement key '%@' := '%@'", key
, value
);
149 ctx
->illegal_entitlement
= true;
153 filter_entitlements(CFDictionaryRef entitlements
)
158 // did not put in an application-identifier: that keeps us from identifying the app securely
159 filter_whitelist_ctx ctx
= { /* valid_application_identifier */ false,
160 /* valid_keychain_access_group */ true,
161 /* illegal_entitlement */ false };
162 CFDictionaryApplyFunction(entitlements
,
163 (CFDictionaryApplierFunction
)filter_entitlement
, &ctx
);
164 return (ctx
.valid_application_identifier
&& ctx
.valid_keychain_access_group
&&
165 !ctx
.illegal_entitlement
);
168 static pid_t kill_child
= -1;
169 void child_timeout(int sig
)
171 if (kill_child
!= -1) {
172 kill(kill_child
, sig
);
177 pid_t
fork_child(void (*pre_exec
)(void *arg
), void *pre_exec_arg
,
178 const char * const argv
[])
180 unsigned delay
= 1, maxDelay
= 60;
183 switch (pid
= fork()) {
184 case -1: /* fork failed */
187 continue; /* no problem */
189 if (delay
< maxDelay
) {
199 assert(-1); /* unreached */
203 pre_exec(pre_exec_arg
);
204 execv(argv
[0], (char * const *)argv
);
208 default: /* parent */
218 int fork_child_timeout(void (*pre_exec
)(), char *pre_exec_arg
,
219 const char * const argv
[], int timeout
)
221 int exit_status
= -1;
222 pid_t child_pid
= fork_child(pre_exec
, pre_exec_arg
, argv
);
224 kill_child
= child_pid
;
228 int err
= wait4(child_pid
, &exit_status
, 0, NULL
);
234 if (err
== child_pid
) {
235 if (WIFSIGNALED(exit_status
)) {
236 log("child %d received signal %d", child_pid
, WTERMSIG(exit_status
));
237 kill(child_pid
, SIGHUP
);
240 if (WIFEXITED(exit_status
))
241 return WEXITSTATUS(exit_status
);
248 void dup_io(int arg
[])
250 dup2(arg
[0], arg
[1]);
255 int fork_child_timeout_output(int child_fd
, int *parent_fd
, const char * const argv
[], int timeout
)
258 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, output
))
260 fcntl(output
[1], F_SETFD
, 1); /* close in child */
261 int redirect_child
[] = { output
[0], child_fd
};
262 int err
= fork_child_timeout(dup_io
, (void*)redirect_child
, argv
, timeout
);
264 close(output
[0]); /* close the child side in the parent */
265 *parent_fd
= output
[1];
270 ssize_t
read_fd(int fd
, void **buffer
)
273 size_t capacity
= 1024;
274 char * data
= malloc(capacity
);
277 int bytes_left
= capacity
- size
;
278 int bytes_read
= read(fd
, data
+ size
, bytes_left
);
279 if (bytes_read
>= 0) {
281 if (capacity
== size
) {
283 data
= realloc(data
, capacity
);
305 static char * codesign_binary
= "/usr/bin/codesign";
308 main(int argc
, char *argv
[])
312 CFArrayRef empty_array
= CFArrayCreate(NULL
, NULL
, 0, NULL
);
313 CFMutableDictionaryRef dict
= CFDictionaryCreateMutable(kCFAllocatorDefault
,
314 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
317 require(!filter_entitlements(dict
), fail_test
);
319 CFDictionarySetValue(dict
, CFSTR("get-task-allow"), kCFBooleanTrue
);
321 // no get-task-allow allowed
322 require(!filter_entitlements(dict
), fail_test
);
323 CFDictionaryRemoveValue(dict
, CFSTR("get-task-allow"));
325 CFDictionarySetValue(dict
, CFSTR("application-identifier"), empty_array
);
326 require(!filter_entitlements(dict
), fail_test
);
328 CFDictionarySetValue(dict
, CFSTR("application-identifier"), CFSTR("apple"));
329 require(!filter_entitlements(dict
), fail_test
);
330 CFDictionarySetValue(dict
, CFSTR("application-identifier"), CFSTR("AJ$K#GK$.AJ$K#GK$.hoi"));
331 require(!filter_entitlements(dict
), fail_test
);
332 CFDictionarySetValue(dict
, CFSTR("application-identifier"), CFSTR("AJ$K#GK$."));
333 require(!filter_entitlements(dict
), fail_test
);
334 CFDictionarySetValue(dict
, CFSTR("application-identifier"), CFSTR("AJ$K#GK$.hoi"));
335 require(filter_entitlements(dict
), fail_test
);
337 CFDictionarySetValue(dict
, CFSTR("keychain-access-groups"), CFSTR("apple"));
338 require(!filter_entitlements(dict
), fail_test
);
339 const void *ary
[] = { CFSTR("test"), CFSTR("apple") };
340 CFArrayRef ka_array
= CFArrayCreate(NULL
, ary
, sizeof(ary
)/sizeof(*ary
), NULL
);
341 CFDictionarySetValue(dict
, CFSTR("keychain-access-groups"), ka_array
);
342 require(!filter_entitlements(dict
), fail_test
);
343 CFDictionarySetValue(dict
, CFSTR("keychain-access-groups"), CFSTR("AJ$K#GK$.joh"));
344 require(filter_entitlements(dict
), fail_test
);
345 CFDictionarySetValue(dict
, CFSTR("this-should-not"), CFSTR("be-there"));
346 require(!filter_entitlements(dict
), fail_test
);
350 fprintf(stderr
, "failed internal test\n");
356 fprintf(stderr
, "usage: %s <file>\n", argv
[0]);
362 fprintf(stderr
, "NOTICE: check_entitlements on %s", argv
[1]);
365 const char * const extract_entitlements
[] =
366 { codesign_binary
, "--display", "--entitlements", "-", argv
[1], NULL
};
368 if ((exit_status
= fork_child_timeout_output(STDOUT_FILENO
, &entitlements_fd
,
369 extract_entitlements
, 0))) {
370 fprintf(stderr
, "ERR: failed to extract entitlements: %d\n", exit_status
);
374 void *entitlements
= NULL
;
375 size_t entitlements_size
= read_fd(entitlements_fd
, &entitlements
);
376 if (entitlements_size
== -1)
378 close(entitlements_fd
);
380 if (entitlements
&& entitlements_size
) {
381 CFDataRef ent
= extract_entitlements_blob(entitlements
, entitlements_size
);
384 CFPropertyListRef entitlements_dict
=
385 CFPropertyListCreateFromXMLData(kCFAllocatorDefault
,
386 ent
, kCFPropertyListImmutable
, NULL
);
388 require(entitlements_dict
, out
);
389 if (!filter_entitlements(entitlements_dict
)) {
390 fprintf(stderr
, "ERR: bad entitlements\n");
395 fprintf(stderr
, "ERR: no entitlements!\n");