-
Notifications
You must be signed in to change notification settings - Fork 443
build: make signing portable across contributor teams #929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,3 +31,4 @@ xcuserdata/**/* | |
| !xcuserdata/**/xcschemes/* | ||
|
|
||
| *.moved-aside | ||
| LocalSigning.xcconfig | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| #import "SCDaemonXPC.h" | ||
| #import"SCDaemonBlockMethods.h" | ||
| #import "SCFileWatcher.h" | ||
| #import "SCBlockClock.h" | ||
|
|
||
| static NSString* serviceName = @"org.eyebeam.selfcontrold"; | ||
| float const INACTIVITY_LIMIT_SECS = 60 * 2; // 2 minutes | ||
|
|
@@ -25,6 +26,7 @@ @interface SCDaemon () <NSXPCListenerDelegate> | |
|
|
||
| @property (nonatomic, strong, readwrite) NSXPCListener* listener; | ||
| @property (strong, readwrite) NSTimer* checkupTimer; | ||
| @property (strong, readwrite) NSTimer* checkpointTimer; | ||
| @property (strong, readwrite) NSTimer* inactivityTimer; | ||
| @property (nonatomic, strong, readwrite) NSDate* lastActivityDate; | ||
|
|
||
|
|
@@ -100,11 +102,26 @@ - (void)stopCheckupTimer { | |
| if (self.checkupTimer == nil) { | ||
| return; | ||
| } | ||
|
|
||
| [self.checkupTimer invalidate]; | ||
| self.checkupTimer = nil; | ||
| } | ||
|
|
||
| - (void)startCheckpointTimer { | ||
| if (self.checkpointTimer != nil) return; | ||
| self.checkpointTimer = [NSTimer scheduledTimerWithTimeInterval: 30.0 | ||
| repeats: YES | ||
| block: ^(NSTimer* _Nonnull t) { | ||
| [SCBlockClock tickCheckpoint]; | ||
| }]; | ||
| } | ||
|
|
||
| - (void)stopCheckpointTimer { | ||
| if (self.checkpointTimer == nil) return; | ||
| [self.checkpointTimer invalidate]; | ||
| self.checkpointTimer = nil; | ||
| } | ||
|
|
||
|
|
||
| - (void)startInactivityTimer { | ||
| self.inactivityTimer = [NSTimer scheduledTimerWithTimeInterval: 15.0 repeats: YES block:^(NSTimer * _Nonnull timer) { | ||
|
|
@@ -133,6 +150,10 @@ - (void)dealloc { | |
| [self.checkupTimer invalidate]; | ||
| self.checkupTimer = nil; | ||
| } | ||
| if (self.checkpointTimer) { | ||
| [self.checkpointTimer invalidate]; | ||
| self.checkpointTimer = nil; | ||
| } | ||
| if (self.inactivityTimer) { | ||
| [self.inactivityTimer invalidate]; | ||
| self.inactivityTimer = nil; | ||
|
|
@@ -155,15 +176,39 @@ - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConne | |
| if (SecCodeCopyGuestWithAttributes(NULL, (__bridge CFDictionaryRef _Nullable)(guestAttributes), kSecCSDefaultFlags, &guest) != errSecSuccess) { | ||
| return NO; | ||
| } | ||
|
|
||
| SecRequirementRef isSelfControlApp; | ||
|
|
||
| // Pin XPC clients to the same Apple Developer Team that signed this daemon. | ||
| // This mirrors the SMAuthorizedClients plist entry and works for both Apple | ||
| // Development local builds and Developer ID release builds (both certs have subject.OU). | ||
| NSString* teamID = nil; | ||
| SecCodeRef selfCode = NULL; | ||
| if (SecCodeCopySelf(kSecCSDefaultFlags, &selfCode) == errSecSuccess) { | ||
| CFDictionaryRef signingInfo = NULL; | ||
| if (SecCodeCopySigningInformation(selfCode, kSecCSSigningInformation, &signingInfo) == errSecSuccess) { | ||
| teamID = (__bridge NSString*)CFDictionaryGetValue(signingInfo, kSecCodeInfoTeamIdentifier); | ||
| teamID = [teamID copy]; | ||
| CFRelease(signingInfo); | ||
| } | ||
| CFRelease(selfCode); | ||
| } | ||
| if (teamID.length == 0) { | ||
| NSLog(@"Rejecting XPC connection: could not determine daemon's own team identifier"); | ||
| CFRelease(guest); | ||
| return NO; | ||
| } | ||
|
|
||
| NSString* requirementString = [NSString stringWithFormat: | ||
| @"anchor apple generic and (identifier \"org.eyebeam.SelfControl\" or identifier \"org.eyebeam.selfcontrol-cli\") and info [CFBundleVersion] >= \"407\" and certificate leaf[subject.OU] = \"%@\"", | ||
| teamID]; | ||
|
|
||
| SecRequirementRef isSelfControlApp = NULL; | ||
| // versions before 4.0 didn't have hardened code signing, so aren't trustworthy to talk to the daemon | ||
| // (plus the daemon didn't exist before 4.0 so there's really no reason they should want to run it!) | ||
| SecRequirementCreateWithString(CFSTR("anchor apple generic and (identifier \"org.eyebeam.SelfControl\" or identifier \"org.eyebeam.selfcontrol-cli\") and info [CFBundleVersion] >= \"407\" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = EG6ZYP3AQH)"), kSecCSDefaultFlags, &isSelfControlApp); | ||
| SecRequirementCreateWithString((__bridge CFStringRef)requirementString, kSecCSDefaultFlags, &isSelfControlApp); | ||
| OSStatus clientValidityStatus = SecCodeCheckValidity(guest, kSecCSDefaultFlags, isSelfControlApp); | ||
|
Comment on lines
+200
to
208
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: When the SecRequirementRef argument is NULL in a call to SecCodeCheckValidity, the function does not impose any additional code requirements beyond the standard signature validation [1][2]. It will still perform dynamic validation of the signed code, including verifying the code's signature and ensuring that the code object's host is valid [1]. Essentially, passing NULL tells the system to verify that the code is properly signed and valid, but not to enforce specific identity or entitlement constraints [1]. Regarding the failure of SecRequirementCreateWithString, callers should absolutely reject the connection (or the operation) if this function fails [3]. SecRequirementCreateWithString is responsible for compiling the text representation of your security requirements into a SecRequirement object [4]. If it fails (returns a non-zero OSStatus), it means the requirement string is invalid or could not be parsed [4][5]. Proceeding without a successfully created requirement—or worse, falling back to a NULL requirement when you intended to enforce specific security constraints—would bypass the intended security checks, leaving the application vulnerable to unauthorized or malicious code [3][6]. Always check the return status of security-critical functions and handle errors by aborting the operation [7][3]. Citations:
🏁 Script executed: find . -name "SCDaemon.m" -type fRepository: SelfControlApp/selfcontrol Length of output: 90 🏁 Script executed: sed -n '195,215p' ./Daemon/SCDaemon.mRepository: SelfControlApp/selfcontrol Length of output: 1307 Fail closed if building the client requirement fails. If 🔒 Possible fix SecRequirementRef isSelfControlApp = NULL;
- // versions before 4.0 didn't have hardened code signing, so aren't trustworthy to talk to the daemon
- // (plus the daemon didn't exist before 4.0 so there's really no reason they should want to run it!)
- SecRequirementCreateWithString((__bridge CFStringRef)requirementString, kSecCSDefaultFlags, &isSelfControlApp);
+ // versions before 4.0 didn't have hardened code signing, so aren't trustworthy to talk to the daemon
+ // (plus the daemon didn't exist before 4.0 so there's really no reason they should want to run it!)
+ OSStatus requirementStatus =
+ SecRequirementCreateWithString((__bridge CFStringRef)requirementString,
+ kSecCSDefaultFlags,
+ &isSelfControlApp);
+ if (requirementStatus != errSecSuccess || isSelfControlApp == NULL) {
+ NSLog(@"Rejecting XPC connection: could not build client requirement (%d)", (int)requirementStatus);
+ CFRelease(guest);
+ return NO;
+ }
OSStatus clientValidityStatus = SecCodeCheckValidity(guest, kSecCSDefaultFlags, isSelfControlApp);🤖 Prompt for AI Agents |
||
|
|
||
| CFRelease(guest); | ||
| CFRelease(isSelfControlApp); | ||
| if (isSelfControlApp) CFRelease(isSelfControlApp); | ||
|
|
||
| if (clientValidityStatus) { | ||
| NSError* error = [NSError errorWithDomain: NSOSStatusErrorDomain code: clientValidityStatus userInfo: nil]; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // Copy this file to LocalSigning.xcconfig and put your Apple Developer | ||
| // Team ID below. Find your Team ID at developer.apple.com → Membership. | ||
| // This file is gitignored so you won't accidentally commit your team ID. | ||
|
|
||
| DEVELOPMENT_TEAM = YOUR_TEAM_ID_HERE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
startCheckpointTimeris never wired into the daemon lifecycle.I can't find a call site for this helper anywhere in
Daemon/SCDaemon.m, so the new checkpoint path never starts andtickCheckpointnever runs.🤖 Prompt for AI Agents