Compare commits

...

39 Commits
0.9.8 ... 0.9.9

Author SHA1 Message Date
Russell Hancox
488b28bfd5 SantaGUI: Log to syslog 2016-04-11 15:15:03 -04:00
Russell Hancox
0fceb7b2e1 SantaGUI: Post notifications to main thread using dispatch_async 2016-04-11 15:14:52 -04:00
Russell Hancox
a79d1a98e7 santactl/fileinfo: Only print "Signing Chain" header if certificates array isn't empty 2016-04-08 16:21:15 -04:00
Russell Hancox
43434fd445 santactl/fileinfo: Don't crash on <512b files 2016-04-08 16:20:49 -04:00
Russell Hancox
492e523884 Project: Move enums in SNTCommonEnums to NS_ENUM, part 2 2016-04-08 15:41:26 -04:00
Russell Hancox
3d1fdb7a2b Project: Move enums in SNTCommonEnums to NS_ENUM, part 1 2016-04-08 15:17:32 -04:00
Russell Hancox
95a4bf0ec7 santad: Ensure launchd/santad rules are whitelisted on every startup
If they weren't already and the database is not new, log an error.
2016-04-08 15:16:12 -04:00
Russell Hancox
0d4f261e14 santad: Have SNTRuleTable return NSError when failing to add rules so user can see why 2016-04-08 15:07:43 -04:00
Russell Hancox
e96288b41b santad: Exclusive-lock rules.db when opening 2016-04-08 12:11:08 -04:00
Russell Hancox
deda1abcf7 SantaGUI: Detect value type for silenced notifications to prevent crashes from bad plist 2016-04-08 10:32:48 -04:00
Russell Hancox
ee79d75483 santad: Set ThrottleInterval to 1s. 2016-04-07 17:03:49 -04:00
Russell Hancox
0e9e445ddf SantaGUI: Reconnect when listener loses a client.
Also move WEAKIFY/STRONGIFY macros into their own header.
2016-04-07 17:03:09 -04:00
Russell Hancox
e64720bcd9 Project: Tell Travis to use xcode7 2016-04-07 15:33:10 -04:00
Russell Hancox
6e27590b57 SantaGUI: Add 'dismiss for a day' checkbox.
Fixes #39.
2016-04-07 14:40:33 -04:00
Russell Hancox
916c3c7a2a SNTXPCConnection: Re-add forced establishment of clients, better tests.
Previously SNTXPCConnection had two-way validation which, due to the method of
implementation, forced a client to connect to a server straight away. Once that
was removed, it meant invalidationHandlers aren't called if either end dies
before the connection is established.

This also puts back the acceptedHandler, which can be used to know when the
connection has finished being established (particularly useful on the server
side), updates the __weak stuff to use WEAKIFY/STRONGIFY macros (and now
actually switch them back to strong within each block) and make the
tests a lot better by using in-process anonymousListener's rather than
lots of mocking.
2016-04-06 23:25:55 -04:00
Russell Hancox
8a5fde8ceb LogicTests: Stop instrumenting program flow, it causes 100s of log lines during tests 2016-04-06 17:30:08 -04:00
Russell Hancox
f5bd9bde7f SantaGUI: Use ultralight system font for title of message window 2016-04-06 15:23:18 -04:00
Russell Hancox
b987f61924 SantaGUI: Fix centering constraint for publisher label 2016-04-06 15:22:21 -04:00
Russell Hancox
482b51a2f9 santactl/sync: Fix userAgent not being set 2016-04-05 14:52:48 -04:00
Russell Hancox
93f2078eda SantaGUI: Fix some constraint bugs in MessageWindow 2016-04-04 18:15:20 -04:00
Russell Hancox
158ae11e61 Tests: Remove old stuff from XPCConnectionTest 2016-04-01 17:53:37 -04:00
Russell Hancox
d282388266 santactl/sync: Release certificate after use 2016-04-01 17:31:24 -04:00
Russell Hancox
6ecdfcba38 santactl/sync: If unable to find client identity, let default handling occur. 2016-04-01 09:54:15 -04:00
Russell Hancox
88dc8a547e README: Add video of block event, as an example 2016-03-31 13:51:35 -04:00
Russell Hancox
58e24b3c11 santagui: Remove old comment, don't activateIgnoringOtherApps twice. 2016-03-31 09:27:38 -04:00
Russell Hancox
5f1b3a2284 santad: Initialize ppath to (null) before calling proc_pidpath 2016-03-30 16:12:52 -04:00
Russell Hancox
31be2584f2 Conf: Use facility instead of sender for santad/santactl logs 2016-03-29 18:00:30 -04:00
Russell Hancox
a2311e5128 santad: Attempt to load santa-driver before connecting to it 2016-03-29 14:08:29 -04:00
Russell Hancox
e94d42187b santactl/sync: Don't log successful stages
Currently a standard succesful sync will print 4 success lines, one for
each stage that was run, followed by a line that the entire sync was
successful. As each stage will also log if it did anything, these
success messages aren't useful. Instead, just log if they failed.
2016-03-25 16:44:06 -04:00
Russell Hancox
2b99cc3f62 Logging: strncmp for binaryName 2016-03-25 14:20:46 -04:00
Russell Hancox
cb7f782893 santad: Fix typo in cert protection error 2016-03-23 17:45:08 -04:00
Russell Hancox
d5a0f8a74b Logging: Remove extraneous ; 2016-03-23 16:46:38 -04:00
Russell Hancox
2ebd71df24 santactl/sync: Fix single-event upload with extra arguments 2016-03-23 16:46:21 -04:00
Russell Hancox
479203f47c santa-driver: Style and type cleanups, inlining some small functions 2016-03-22 15:38:48 -04:00
Russell Hancox
022b9209d9 LogicTests: Delete resources that aren't used anymore. 2016-03-22 15:36:56 -04:00
Russell Hancox
771c2c868f SantaGUI: Increase contrast of user-defined block window messages. 2016-03-21 18:00:02 -04:00
Russell Hancox
5285a728b1 santa-driver: Don't record fileop events from santad 2016-03-21 16:15:20 -04:00
Russell Hancox
41e6583920 SantaGUI: Improve accessiblity of message dialog
+ VoiceOver: add more useful label descriptions
+ VoiceOver: skip some fields
+ Color: increase contrast
2016-03-21 13:49:50 -04:00
Russell Hancox
cbb60b3a05 SantaGUI: Have daemon reply when setting notification listener so GUI can ensure it connected 2016-03-17 17:55:31 -04:00
57 changed files with 1046 additions and 786 deletions

View File

@@ -2,6 +2,7 @@
language: objective-c
cache: cocoapods
sudo: false
osx_image: xcode7
before_install:
- gem install activesupport

View File

@@ -2,7 +2,5 @@
> /var/log/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
? [= Sender kernel] [S= Message santa-driver:] claim
? [= Sender kernel] [S= Message santa-driver:] file /var/log/santa.log
? [= Sender santad] claim
? [= Sender santad] file /var/log/santa.log
? [= Sender santactl] claim
? [= Sender santactl] file /var/log/santa.log
? [= Facility com.google.santa] claim
? [= Facility com.google.santa] file /var/log/santa.log

View File

@@ -7,6 +7,7 @@
<key>ProgramArguments</key>
<array>
<string>/Library/Extensions/santa-driver.kext/Contents/MacOS/santad</string>
<string>--syslog</string>
</array>
<key>MachServices</key>
<dict>
@@ -19,5 +20,7 @@
<true />
<key>ProcessType</key>
<string>Interactive</string>
<key>ThrottleInterval</key>
<integer>1</integer>
</dict>
</plist>

View File

@@ -7,6 +7,7 @@
<key>ProgramArguments</key>
<array>
<string>/Applications/Santa.app/Contents/MacOS/Santa</string>
<string>--syslog</string>
</array>
<key>RunAtLoad</key>
<true/>

View File

@@ -68,7 +68,7 @@ hosts in whatever other ways you see fit.
Get Help
========
If you have questions or need help getting started, the
If you have questions or need help getting started, the
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is the
best place to start.
@@ -108,6 +108,15 @@ option) if it would be useful to others.
* Tests: There aren't enough of them.
Screenshots
===========
A tool like Santa doesn't really lend itself to screenshots, so here's a video instead.
<p align="center">
<img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif" alt="Santa Block Video" />
</p>
Building
========
```sh

View File

@@ -82,10 +82,6 @@
0D63DD5E1906FCB400D346C4 /* SNTDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */; };
0D668E8118D1121700E29A8B /* SNTMessageWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D668E8018D1121700E29A8B /* SNTMessageWindow.m */; };
0D6F12D819EC8822006B218E /* SecurityInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DCD5F771909C659006B445C /* SecurityInterface.framework */; };
0D6F12DA19EDE51E006B218E /* tubitak.crt in Resources */ = {isa = PBXBuildFile; fileRef = 0D6F12D919EDE411006B218E /* tubitak.crt */; };
0D6FDC8318C68D7E0044685C /* GIAG2.crt in Resources */ = {isa = PBXBuildFile; fileRef = 0D6FDC8218C68D7E0044685C /* GIAG2.crt */; };
0D6FDC8518C68E500044685C /* GIAG2.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0D6FDC8418C68E500044685C /* GIAG2.pem */; };
0D6FDC8718C6913D0044685C /* apple.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0D6FDC8618C6913D0044685C /* apple.pem */; };
0D6FDC9618C93A020044685C /* SNTXPCConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */; };
0D6FDC9718C93A020044685C /* SNTXPCConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */; };
0D7A7AF3174FCF4C00B77646 /* SantaCachedDecision.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0D7A7AF1174FCF4C00B77646 /* SantaCachedDecision.cc */; };
@@ -141,6 +137,7 @@
0DD0D48F194F78F8005F27EB /* SNTFileInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD0D48E194F78F8005F27EB /* SNTFileInfoTest.m */; };
0DD0D491194F9947005F27EB /* SNTExecutionControllerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD0D490194F9947005F27EB /* SNTExecutionControllerTest.m */; };
0DD0D492194F9BEF005F27EB /* SNTLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA73C9E1934F8100056D7C4 /* SNTLogging.m */; };
0DE2CE561CA05561002B649A /* SNTAccessibleTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE2CE551CA05561002B649A /* SNTAccessibleTextField.m */; };
0DE4C8A118FEF28200466D04 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D8C200B180F359A00CE2BF8 /* Security.framework */; };
0DE4C8A618FF3B1700466D04 /* SNTCommandFlushCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE4C8A518FF3B1700466D04 /* SNTCommandFlushCache.m */; };
0DE50F681912716A007B2B0C /* SNTRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE50F671912716A007B2B0C /* SNTRule.m */; };
@@ -297,6 +294,7 @@
0D4644C3182AF81700098690 /* SantaDecisionManager.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SantaDecisionManager.cc; sourceTree = "<group>"; };
0D4644C4182AF81700098690 /* SantaDecisionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SantaDecisionManager.h; sourceTree = "<group>"; };
0D4A5006176A4602004F63BF /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = "<absolute>"; };
0D5058CF1CB70123008784BA /* SNTStrengthify.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTStrengthify.h; sourceTree = "<group>"; };
0D536ED51B8E7A2E0039A26D /* bad_pagezero */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = bad_pagezero; sourceTree = "<group>"; };
0D536ED61B8E7A2E0039A26D /* missing_pagezero */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = missing_pagezero; sourceTree = "<group>"; };
0D536ED91B94E9230039A26D /* SNTEventLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTEventLog.h; sourceTree = "<group>"; };
@@ -305,10 +303,6 @@
0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTDatabaseController.m; sourceTree = "<group>"; };
0D668E7F18D1121700E29A8B /* SNTMessageWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTMessageWindow.h; sourceTree = "<group>"; };
0D668E8018D1121700E29A8B /* SNTMessageWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTMessageWindow.m; sourceTree = "<group>"; };
0D6F12D919EDE411006B218E /* tubitak.crt */ = {isa = PBXFileReference; lastKnownFileType = file; path = tubitak.crt; sourceTree = "<group>"; };
0D6FDC8218C68D7E0044685C /* GIAG2.crt */ = {isa = PBXFileReference; lastKnownFileType = file; path = GIAG2.crt; sourceTree = "<group>"; };
0D6FDC8418C68E500044685C /* GIAG2.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GIAG2.pem; sourceTree = "<group>"; };
0D6FDC8618C6913D0044685C /* apple.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = apple.pem; sourceTree = "<group>"; };
0D6FDC9418C93A020044685C /* SNTXPCConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTXPCConnection.h; sourceTree = "<group>"; };
0D6FDC9518C93A020044685C /* SNTXPCConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTXPCConnection.m; sourceTree = "<group>"; };
0D7A7AF1174FCF4C00B77646 /* SantaCachedDecision.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SantaCachedDecision.cc; sourceTree = "<group>"; };
@@ -360,6 +354,8 @@
0DCD6061191188B1006B445C /* SNTAuthenticatingURLSession.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTAuthenticatingURLSession.m; sourceTree = "<group>"; };
0DD0D48E194F78F8005F27EB /* SNTFileInfoTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTFileInfoTest.m; sourceTree = "<group>"; };
0DD0D490194F9947005F27EB /* SNTExecutionControllerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTExecutionControllerTest.m; sourceTree = "<group>"; };
0DE2CE541CA05561002B649A /* SNTAccessibleTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTAccessibleTextField.h; sourceTree = "<group>"; };
0DE2CE551CA05561002B649A /* SNTAccessibleTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTAccessibleTextField.m; sourceTree = "<group>"; };
0DE4C8A518FF3B1700466D04 /* SNTCommandFlushCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandFlushCache.m; sourceTree = "<group>"; };
0DE50F6619127169007B2B0C /* SNTRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTRule.h; sourceTree = "<group>"; };
0DE50F671912716A007B2B0C /* SNTRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTRule.m; sourceTree = "<group>"; };
@@ -478,12 +474,8 @@
0D536ED51B8E7A2E0039A26D /* bad_pagezero */,
0D536ED61B8E7A2E0039A26D /* missing_pagezero */,
0D2CD4601A81C7B100C9C910 /* dn.plist */,
0D6FDC8618C6913D0044685C /* apple.pem */,
0D6FDC8218C68D7E0044685C /* GIAG2.crt */,
0D6FDC8418C68E500044685C /* GIAG2.pem */,
0D260DB118B68E12002A0B55 /* Tests-Info.plist */,
0D260DB718B68E12002A0B55 /* Tests-Prefix.pch */,
0D6F12D919EDE411006B218E /* tubitak.crt */,
);
path = Resources;
sourceTree = "<group>";
@@ -544,6 +536,8 @@
0D385DC3180DE4A900418BC6 /* main.m */,
0D1B476D19A53419008CADD3 /* SNTAboutWindowController.h */,
0D1B476E19A53419008CADD3 /* SNTAboutWindowController.m */,
0DE2CE541CA05561002B649A /* SNTAccessibleTextField.h */,
0DE2CE551CA05561002B649A /* SNTAccessibleTextField.m */,
0D385DEA180DE51600418BC6 /* SNTAppDelegate.h */,
0D385DEB180DE51600418BC6 /* SNTAppDelegate.m */,
0D668E7F18D1121700E29A8B /* SNTMessageWindow.h */,
@@ -681,6 +675,7 @@
0DE50F671912716A007B2B0C /* SNTRule.m */,
0DCD604919105433006B445C /* SNTStoredEvent.h */,
0DCD604A19105433006B445C /* SNTStoredEvent.m */,
0D5058CF1CB70123008784BA /* SNTStrengthify.h */,
0D42D2B319D1D98A00955F08 /* SNTSystemInfo.h */,
0D42D2B419D1D98A00955F08 /* SNTSystemInfo.m */,
0D6FDC9418C93A020044685C /* SNTXPCConnection.h */,
@@ -979,12 +974,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0D6FDC8518C68E500044685C /* GIAG2.pem in Resources */,
0D6FDC8318C68D7E0044685C /* GIAG2.crt in Resources */,
0D6F12DA19EDE51E006B218E /* tubitak.crt in Resources */,
0D536ED71B8E7A2E0039A26D /* bad_pagezero in Resources */,
0D2CD4611A81C7B100C9C910 /* dn.plist in Resources */,
0D6FDC8718C6913D0044685C /* apple.pem in Resources */,
0D536ED81B8E7A2E0039A26D /* missing_pagezero in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1346,6 +1337,7 @@
0DA73CA11934F8100056D7C4 /* SNTLogging.m in Sources */,
0DEFB7C51ACDD80100B92AAE /* SNTFileWatcher.m in Sources */,
0D20710E1A7C4A86008B0A9A /* SNTStoredEvent.m in Sources */,
0DE2CE561CA05561002B649A /* SNTAccessibleTextField.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1527,7 +1519,6 @@
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_GENERATE_TEST_COVERAGE_FILES = YES;
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Tests/LogicTests/Resources/Tests-Prefix.pch";
@@ -1578,7 +1569,6 @@
);
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_GENERATE_TEST_COVERAGE_FILES = YES;
GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Tests/LogicTests/Resources/Tests-Prefix.pch";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

View File

@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="8191" systemVersion="15C50" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8191"/>
<development version="6300" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
@@ -14,64 +15,95 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="497" height="381"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<rect key="contentRect" x="167" y="107" width="497" height="439"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="497" height="381"/>
<rect key="frame" x="0.0" y="0.0" width="497" height="439"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="-6" y="411" width="37" height="32"/>
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</buttonCell>
<connections>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="vl5-A8-O0H"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="207" y="311" width="83" height="40"/>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" size="34" name="HelveticaNeue-UltraLight"/>
<color key="textColor" red="0.18696189413265307" green="0.18696189413265307" blue="0.18696189413265307" alpha="1" colorSpace="calibratedRGB"/>
<rect key="frame" x="206" y="368" width="85" height="41"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" metaFont="systemUltraLight" size="34"/>
<color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="z5y-RR-IEH"/>
</connections>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR">
<rect key="frame" x="22" y="264" width="454" height="17"/>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="22" y="329" width="454" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
<animations/>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
<textFieldCell key="cell" selectable="YES" refusesFirstResponder="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
<font key="font" metaFont="system"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.attributedCustomMessage" id="376-sj-4Q1"/>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="VC7-bE-uHc"/>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ">
<rect key="frame" x="165" y="192" width="294" height="17"/>
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="146" y="132" width="1" height="167"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<animations/>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="E7T-9h-ofr">
<font key="font" metaFont="system"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
<rect key="frame" x="8" y="282" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="8mA-zi-Ev7"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.filePath" id="qfp-sR-Nmu"/>
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr">
<rect key="frame" x="165" y="217" width="294" height="17"/>
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="282" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="Pav-ZA-iAu"/>
</constraints>
<animations/>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
<font key="font" metaFont="systemBold"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.fileBundleName" id="enC-Cl-UWt">
@@ -81,31 +113,68 @@
</binding>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="165" y="142" width="219" height="17"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="257" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
</constraints>
<animations/>
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="SHA-256" id="X4W-9e-eIu">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename" id="KgY-X1-ESG">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
<rect key="frame" x="8" y="232" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="3wU-P0-gAC"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="adC-be-Beh">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="257" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Name" id="E7T-9h-ofr">
<font key="font" metaFont="system"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="9KB-0b-qLV"/>
<binding destination="-2" name="value" keyPath="self.event.filePath.lastPathComponent" id="bOu-gv-1Vh"/>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w">
<rect key="frame" x="165" y="167" width="294" height="17"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
<rect key="frame" x="8" y="207" width="120" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Publisher" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w" userLabel="Publisher" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="207" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="Dem-wH-KHm"/>
</constraints>
<animations/>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Code signing information" placeholderString="" id="ztA-La-XgT">
<font key="font" metaFont="system"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.publisherInfo" id="CEI-Cu-7pC">
@@ -115,93 +184,18 @@
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL">
<rect key="frame" x="8" y="92" width="120" height="17"/>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J">
<rect key="frame" x="8" y="117" width="120" height="17"/>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5">
<rect key="frame" x="8" y="167" width="120" height="17"/>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H">
<rect key="frame" x="8" y="192" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
</constraints>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="KgY-X1-ESG">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y">
<rect key="frame" x="8" y="142" width="120" height="17"/>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0">
<rect key="frame" x="165" y="92" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
<font key="font" metaFont="system"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="146" y="92" width="1" height="142"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<animations/>
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs">
<rect key="frame" x="40" y="168" width="15" height="15"/>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
<rect key="frame" x="40" y="208" width="15" height="15"/>
<constraints>
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
</constraints>
<animations/>
<buttonCell key="cell" type="bevel" bezelStyle="regularSquare" image="NSInfo" imagePosition="overlaps" alignment="center" refusesFirstResponder="YES" imageScaling="proportionallyDown" inset="2" id="R72-Qy-Xbb">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</buttonCell>
<connections>
<action selector="showCertInfo:" target="-2" id="dB0-a3-X31"/>
@@ -212,55 +206,54 @@
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL">
<rect key="frame" x="256" y="33" width="110" height="25"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y" userLabel="Label: Identifier">
<rect key="frame" x="8" y="182" width="120" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="165" y="182" width="219" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
</constraints>
<animations/>
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
</buttonCell>
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="SHA-256" id="X4W-9e-eIu">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="4KL-Z2-1op"/>
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="9KB-0b-qLV"/>
</connections>
</button>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="132" y="33" width="112" height="25"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
<animations/>
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
<outlet property="nextKeyView" destination="BbV-3h-mmL" id="Xkz-va-iGc"/>
</connections>
</button>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o">
<rect key="frame" x="165" y="117" width="294" height="17"/>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J" userLabel="Label: Parent">
<rect key="frame" x="8" y="157" width="120" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Parent Process" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o" userLabel="Parent" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="157" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
</constraints>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Parent Name" id="ieo-WK-aDD">
<font key="font" metaFont="system"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="displayPatternValue1" keyPath="self.event.parentName" id="Lce-TO-q9V">
@@ -275,96 +268,163 @@ DQ
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC">
<rect key="frame" x="8" y="217" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="8mA-zi-Ev7"/>
</constraints>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
<rect key="frame" x="8" y="132" width="120" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="132" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
<font key="font" metaFont="system"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="-6" y="353" width="37" height="32"/>
<animations/>
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="132" y="33" width="112" height="25"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="vl5-A8-O0H"/>
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
<outlet property="nextKeyView" destination="BbV-3h-mmL" id="Xkz-va-iGc"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="256" y="33" width="110" height="25"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
</buttonCell>
<accessibility description="Dismiss Dialog"/>
<connections>
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="4KL-Z2-1op"/>
</connections>
</button>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="232" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="p1W-f9-KBX"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="H1b-Ui-CYo">
<font key="font" metaFont="system"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.filePath" id="Sry-KY-HDb"/>
</connections>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
<rect key="frame" x="91" y="80" width="315" height="29"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
</constraints>
<buttonCell key="cell" type="check" title="Prevent future notifications for this application for a day" bezelStyle="regularSquare" imagePosition="left" alignment="center" inset="2" id="R5Y-Uc-rEP">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.silenceFutureNotifications" id="tEb-2A-sht"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="0AD-PS-5V1"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="centerY" secondItem="eQb-0a-76J" secondAttribute="centerY" id="2Aq-1E-Ybz"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" priority="500" constant="193" id="2uo-Cm-Tfp"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="top" secondItem="f1p-GL-O3o" secondAttribute="bottom" constant="8" id="496-VQ-Fx5"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="-116" id="6Q5-Oo-1cI"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="7K6-bY-Rn6"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="centerY" secondItem="qgf-Jf-cJr" secondAttribute="centerY" id="AKX-pe-hEX"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="ALv-0v-szi"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="8" id="E6D-7P-17g"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="FPe-Rd-G4n"/>
<constraint firstItem="cJf-k6-OxS" firstAttribute="centerY" secondItem="C3G-wL-u7w" secondAttribute="centerY" id="FdL-ZZ-Vbe"/>
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="bottom" secondItem="4Li-ul-zIi" secondAttribute="bottom" id="G0I-O2-S91"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-45" id="GD2-Ka-deo"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="GT2-tO-2td"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="centerY" secondItem="oFj-ol-xpL" secondAttribute="centerY" id="GXI-pT-FM1"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="top" secondItem="pDa-fA-vnC" secondAttribute="top" id="Gd4-Nr-n5G"/>
<constraint firstItem="qgf-Jf-cJr" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="Ht4-Lg-U5N"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="IwX-ja-ZIs"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="top" secondItem="4Li-ul-zIi" secondAttribute="top" priority="500" id="JY4-N1-j8e"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="leading" priority="999" id="MVr-jY-GDj"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="750" constant="30" id="Nsl-zf-poH"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="KmX-kX-VCN"/>
<constraint firstItem="5D8-GP-a4l" firstAttribute="centerX" secondItem="Iwq-Lx-rLv" secondAttribute="centerX" id="LkH-F4-Ncm"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="30" id="Nsl-zf-poH"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="centerY" secondItem="bDE-Tl-UHg" secondAttribute="centerY" id="ObQ-RA-S5V"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="SCl-Ky-VmT"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="Scq-zQ-Sao"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="KEB-eH-x2Y" secondAttribute="trailing" constant="20" id="Seb-c0-MUL"/>
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="leading" priority="999" id="Z6G-l9-G4a"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="top" secondItem="eQb-0a-76J" secondAttribute="bottom" constant="8" id="abm-cM-PN0"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" id="asc-Ga-WHD"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="top" secondItem="bDE-Tl-UHg" secondAttribute="bottom" constant="8" id="ZoS-xV-2WA"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="aMJ-Wb-vRS"/>
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="aOk-S0-0n2"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="eQb-0a-76J" secondAttribute="trailing" constant="20" id="b0B-3w-grH"/>
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" priority="999" id="b5A-M7-ZsD"/>
<constraint firstItem="KEB-eH-x2Y" firstAttribute="centerY" secondItem="PXc-xv-A28" secondAttribute="centerY" id="cHe-pZ-0Oq"/>
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="30" id="dYg-zP-wh2"/>
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="22" id="dYg-zP-wh2"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="eSz-lz-Fdh"/>
<constraint firstItem="qgf-Jf-cJr" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" constant="30" id="esg-lX-BAT"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="fGd-YS-phP"/>
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="h3d-Kc-q88"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="7ua-5a-uSd" secondAttribute="trailing" constant="12" id="ioO-NJ-Jqo"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="jdk-ak-soQ"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="centerY" secondItem="lvJ-Rk-UT5" secondAttribute="centerY" id="jfs-YI-7Ae"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="jlD-Lo-abc"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="trailing" constant="20" id="kOG-Cj-hFG"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="qgf-Jf-cJr" secondAttribute="bottom" constant="8" id="lWU-tC-vWg"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="trailing" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" id="lse-kg-lA2"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="top" secondItem="KEB-eH-x2Y" secondAttribute="bottom" constant="8" id="m2z-1O-ifB"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="pdq-a6-Y73"/>
<constraint firstItem="5D8-GP-a4l" firstAttribute="top" secondItem="h6f-PY-cc0" secondAttribute="bottom" constant="25" id="lYd-VZ-lBs"/>
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="pCX-eX-erN"/>
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="61" id="phL-j9-rPq"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="8" id="pis-of-f93"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="20" id="qKi-KT-jzJ"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="bottom" secondItem="PXc-xv-A28" secondAttribute="top" constant="-8" id="snd-8T-LjC"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="trailing" constant="20" id="stz-Vm-Kxo"/>
<constraint firstItem="PXc-xv-A28" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="tAa-1s-xVZ"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="u1y-6V-moc"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="width" secondItem="eQb-0a-76J" secondAttribute="width" id="u4p-1B-x5B"/>
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="bottom" secondItem="C3G-wL-u7w" secondAttribute="top" constant="-8" id="zst-nc-VqA"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="YNz-ka-cBi" secondAttribute="trailing" constant="20" id="vfq-83-tKI"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="z6s-ga-iAk"/>
</constraints>
<animations/>
</view>
<connections>
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
</connections>
<point key="canvasLocation" x="112.5" y="307.5"/>
<point key="canvasLocation" x="112.5" y="302.5"/>
</window>
</objects>
<resources>

View File

@@ -0,0 +1,21 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
/**
An NSTextField subclass that provides an accessiblity label equal to:
(self.toolTip + self.stringValue) where available. It also sets the
accessibilityRoleDescription to "label".
*/
@interface SNTAccessibleTextField : NSTextField
@end

View File

@@ -0,0 +1,39 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTAccessibleTextField.h"
@implementation SNTAccessibleTextField
- (BOOL)accessibilityIsIgnored {
return NO;
}
- (NSString *)accessibilityLabel {
if (self.toolTip && self.stringValue) {
return [NSString stringWithFormat:@"%@: %@", self.toolTip, self.stringValue];
} else if (self.stringValue) {
return self.stringValue;
} else if (self.toolTip) {
return self.toolTip;
} else {
return nil;
}
}
- (NSString *)accessibilityRoleDescription {
return @"label";
}
@end

View File

@@ -18,6 +18,7 @@
#import "SNTConfigurator.h"
#import "SNTFileWatcher.h"
#import "SNTNotificationManager.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@@ -71,28 +72,37 @@
#pragma mark Connection handling
- (void)createConnection {
dispatch_async(dispatch_get_main_queue(), ^{
__weak __typeof(self) weakSelf = self;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.listener.exportedObject = self.notificationManager;
self.listener.invalidationHandler = ^{
[weakSelf attemptReconnection];
};
[self.listener resume];
WEAKIFY(self);
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] setNotificationListener:listener.endpoint];
});
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.listener.exportedObject = self.notificationManager;
self.listener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
self.listener.invalidationHandler = ^{
STRONGIFY(self);
[self attemptReconnection];
};
[self.listener resume];
// Tell daemon to connect back to the above listener.
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] setNotificationListener:listener.endpoint];
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self attemptReconnection];
}
}
- (void)attemptReconnection {
// TODO(rah): Make this smarter.
sleep(5);
[self createConnection];
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
}
#pragma mark Menu Management

View File

@@ -15,7 +15,7 @@
@class SNTStoredEvent;
@protocol SNTMessageWindowControllerDelegate
- (void)windowDidClose;
- (void)windowDidCloseSilenceHash:(NSString *)hash;
@end
///
@@ -29,41 +29,9 @@
- (IBAction)closeWindow:(id)sender;
- (IBAction)showCertInfo:(id)sender;
///
/// The execution event that this window is for
///
@property SNTStoredEvent *event;
///
/// The custom message to display for this event
///
@property(copy) NSString *customMessage;
///
/// The delegate to inform when the notification is dismissed
///
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
///
/// A 'friendly' string representing the certificate information
///
@property(readonly, nonatomic) NSString *publisherInfo;
///
/// An optional message to display with this block.
///
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
///
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
///
@property IBOutlet NSButton *openEventButton;
///
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
///
@property IBOutlet NSTextField *applicationNameLabel;
@end

View File

@@ -22,13 +22,38 @@
#import "SNTMessageWindow.h"
#import "SNTStoredEvent.h"
@interface SNTMessageWindowController ()
/// The execution event that this window is for
@property SNTStoredEvent *event;
/// The custom message to display for this event
@property(copy) NSString *customMessage;
/// A 'friendly' string representing the certificate information
@property(readonly, nonatomic) NSString *publisherInfo;
/// An optional message to display with this block.
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
@property IBOutlet NSButton *openEventButton;
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
@property IBOutlet NSTextField *applicationNameLabel;
/// Linked to checkbox in UI to prevent future notifications for this binary.
@property BOOL silenceFutureNotifications;
@end
@implementation SNTMessageWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
self = [super initWithWindowNibName:@"MessageWindow"];
if (self) {
_event = event;
_customMessage = (message != (NSString *)[NSNull null] ? message : nil);
_customMessage = message;
}
return self;
}
@@ -61,7 +86,13 @@
}
- (void)windowWillClose:(NSNotification *)notification {
if (self.delegate) [self.delegate windowDidClose];
if (!self.delegate) return;
if (self.silenceFutureNotifications) {
[self.delegate windowDidCloseSilenceHash:self.event.fileSHA256];
} else {
[self.delegate windowDidCloseSilenceHash:nil];
}
}
- (IBAction)showCertInfo:(id)sender {
@@ -123,7 +154,7 @@
@"body {"
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
@" font-size: 13px;"
@" color: #AAA;"
@" color: #666;"
@" text-align: center;"
@"}"
@"</style></head><body>";
@@ -132,7 +163,7 @@
NSString *message;
if (self.customMessage.length) {
message = self.customMessage;
} else if (self.event.decision == EVENTSTATE_BLOCK_UNKNOWN) {
} else if (self.event.decision == SNTEventStateBlockUnknown) {
message = [[SNTConfigurator configurator] unknownBlockMessage];
if (!message) {
message = @"The following application has been blocked from executing<br />"

View File

@@ -14,22 +14,21 @@
#import "SNTNotificationManager.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
@interface SNTNotificationManager ()
///
/// The currently displayed notification
///
@property SNTMessageWindowController *currentWindowController;
///
/// The queue of pending notifications
///
@property(readonly) NSMutableArray *pendingNotifications;
@end
@implementation SNTNotificationManager
static NSString * const silencedNotificationsKey = @"SilencedNotifications";
- (instancetype)init {
self = [super init];
if (self) {
@@ -38,7 +37,9 @@
return self;
}
- (void)windowDidClose {
- (void)windowDidCloseSilenceHash:(NSString *)hash {
if (hash) [self updateSilenceDate:[NSDate date] forHash:hash];
[self.pendingNotifications removeObject:self.currentWindowController];
self.currentWindowController = nil;
@@ -50,7 +51,18 @@
}
}
#pragma mark SNTNotifierXPC protocol methods
- (void)updateSilenceDate:(NSDate *)date forHash:(NSString *)hash {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *d = [[ud objectForKey:silencedNotificationsKey] mutableCopy];
if (date) {
d[hash] = date;
} else {
[d removeObjectForKey:hash];
}
[ud setObject:d forKey:silencedNotificationsKey];
}
#pragma mark SNTNotifierXPC protocol method
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
// See if this binary is already in the list of pending notifications.
@@ -58,40 +70,42 @@
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
// See if this binary is silenced.
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][event.fileSHA256];
if ([silenceDate isKindOfClass:[NSDate class]]) {
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
LOGI(@"Notification silence: date is in the future, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
LOGI(@"Notification silence: date is more than one day ago, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
} else {
LOGI(@"Notification silence: dropping notification for %@", event.fileSHA256);
return;
}
}
if (!event) {
NSLog(@"Error: Missing event object in message received from daemon!");
LOGI(@"Error: Missing event object in message received from daemon!");
return;
}
if (!message) message = (NSString *)[NSNull null];
// Notifications arrive on a background thread but UI updates must happen on the main thread.
// This includes making windows.
[self performSelectorOnMainThread:@selector(postBlockNotificationMainThread:)
withObject:@{ @"event" : event,
@"custommsg" : message }
waitUntilDone:NO];
}
dispatch_async(dispatch_get_main_queue(), ^{
SNTMessageWindowController *pendingMsg =
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
- (void)postBlockNotificationMainThread:(NSDictionary *)dict {
SNTStoredEvent *event = dict[@"event"];
NSString *msg = dict[@"custommsg"];
// Create message window
SNTMessageWindowController *pendingMsg = [[SNTMessageWindowController alloc] initWithEvent:event
andMessage:msg];
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
// If a notification isn't currently being displayed, display the incoming one.
if (!self.currentWindowController) {
self.currentWindowController = pendingMsg;
[NSApp activateIgnoringOtherApps:YES];
// It's quite likely that we're currently on a background thread, and GUI code should always be
// on main thread. Open the window on the main thread so any code it runs is also.
[pendingMsg showWindow:nil];
}
// If a notification isn't currently being displayed, display the incoming one.
if (!self.currentWindowController) {
self.currentWindowController = pendingMsg;
[pendingMsg showWindow:nil];
}
});
}
@end

View File

@@ -12,63 +12,58 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifndef SANTA__COMMON__COMMONENUMS_H
#define SANTA__COMMON__COMMONENUMS_H
///
/// These enums are used in various places throughout the Santa client code.
/// The integer values are also stored in the database and so shouldn't be changed.
///
typedef enum {
RULETYPE_UNKNOWN,
typedef NS_ENUM(NSInteger, SNTRuleType) {
SNTRuleTypeUnknown,
RULETYPE_BINARY = 1,
RULETYPE_CERT = 2,
SNTRuleTypeBinary = 1,
SNTRuleTypeCertificate = 2,
};
RULETYPE_MAX
} santa_ruletype_t;
typedef NS_ENUM(NSInteger, SNTRuleState) {
SNTRuleStateUnknown,
typedef enum {
RULESTATE_UNKNOWN,
SNTRuleStateWhitelist = 1,
SNTRuleStateBlacklist = 2,
SNTRuleStateSilentBlacklist = 3,
SNTRuleStateRemove = 4,
};
RULESTATE_WHITELIST = 1,
RULESTATE_BLACKLIST = 2,
RULESTATE_SILENT_BLACKLIST = 3,
RULESTATE_REMOVE = 4,
typedef NS_ENUM(NSInteger, SNTClientMode) {
SNTClientModeUnknown,
RULESTATE_MAX
} santa_rulestate_t;
SNTClientModeMonitor = 1,
SNTClientModeLockdown = 2,
};
typedef enum {
CLIENTMODE_UNKNOWN,
typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateUnknown,
CLIENTMODE_MONITOR = 1,
CLIENTMODE_LOCKDOWN = 2,
SNTEventStateAllowUnknown = 1,
SNTEventStateAllowBinary = 2,
SNTEventStateAllowCertificate = 3,
SNTEventStateAllowScope = 4,
CLIENTMODE_MAX
} santa_clientmode_t;
SNTEventStateBlockUnknown = 5,
SNTEventStateBlockBinary = 6,
SNTEventStateBlockCertificate = 7,
SNTEventStateBlockScope = 8,
typedef enum {
EVENTSTATE_UNKNOWN,
SNTEventStateRelatedBinary = 9,
};
EVENTSTATE_ALLOW_UNKNOWN = 1,
EVENTSTATE_ALLOW_BINARY = 2,
EVENTSTATE_ALLOW_CERTIFICATE = 3,
EVENTSTATE_ALLOW_SCOPE = 4,
EVENTSTATE_BLOCK_UNKNOWN = 5,
EVENTSTATE_BLOCK_BINARY = 6,
EVENTSTATE_BLOCK_CERTIFICATE = 7,
EVENTSTATE_BLOCK_SCOPE = 8,
EVENTSTATE_RELATED_BINARY = 9,
EVENTSTATE_MAX
} santa_eventstate_t;
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
SNTRuleTableErrorEmptyRuleArray,
SNTRuleTableErrorInsertOrReplaceFailed,
SNTRuleTableErrorInvalidRule,
SNTRuleTableErrorMissingRequiredRule,
SNTRuleTableErrorRemoveFailed
};
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
static const char *kSantaDPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santad";
static const char *kSantaCtlPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santactl";
#endif // SANTA__COMMON__COMMONENUMS_H

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
#import "SNTCommonEnums.h"
///
/// Singleton that provides an interface for managing configuration values on disk
@@ -28,7 +28,7 @@ extern NSString *const kDefaultConfigFilePath;
///
/// The operating mode.
///
@property(nonatomic) santa_clientmode_t clientMode;
@property(nonatomic) SNTClientMode clientMode;
///
/// The regex of paths to log file changes for. Regexes are specified in ICU format.

View File

@@ -96,20 +96,23 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
#pragma mark Public Interface
- (santa_clientmode_t)clientMode {
int cm = [self.configData[kClientModeKey] intValue];
if (cm > CLIENTMODE_UNKNOWN && cm < CLIENTMODE_MAX) {
return (santa_clientmode_t)cm;
- (SNTClientMode)clientMode {
NSInteger cm = [self.configData[kClientModeKey] longValue];
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
return (SNTClientMode)cm;
} else {
self.configData[kClientModeKey] = @(CLIENTMODE_MONITOR);
return CLIENTMODE_MONITOR;
LOGE(@"Client mode was set to bad value: %ld. Resetting to MONITOR.", cm);
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
return SNTClientModeMonitor;
}
}
- (void)setClientMode:(santa_clientmode_t)newMode {
if (newMode > CLIENTMODE_UNKNOWN && newMode < CLIENTMODE_MAX) {
- (void)setClientMode:(SNTClientMode)newMode {
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
self.configData[kClientModeKey] = @(newMode);
[self saveConfigToDisk];
} else {
LOGW(@"Ignoring request to change client mode to %ld", newMode);
}
}

View File

@@ -210,7 +210,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (BOOL)isDMG {
NSUInteger last512 = self.fileSize - 512;
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(last512, 4)] bytes];
return (strncmp("koly", magic, 4) == 0);
return (magic && strncmp("koly", magic, 4) == 0);
}
#pragma mark Page Zero

View File

@@ -22,7 +22,7 @@
#ifdef KERNEL
#ifdef DEBUG
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n");
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n")
#else // DEBUG
#define LOGD(...)
#endif // DEBUG

View File

@@ -42,10 +42,8 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
}
// If requested, redirect output to syslog.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
strcmp(binaryName, "santad") == 0) {
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"]) {
useSyslog = YES;
pthread_key_create(&syslogKey, syslogClientDestructor);
}
});

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
#import "SNTCommonEnums.h"
///
/// Represents a Rule.
@@ -27,12 +27,12 @@
///
/// The state of this rule
///
@property santa_rulestate_t state;
@property SNTRuleState state;
///
/// The type of object this rule is for (binary, certificate)
///
@property santa_ruletype_t type;
@property SNTRuleType type;
///
/// A custom message that will be displayed if this rule blocks a binary from executing
@@ -43,8 +43,8 @@
/// Designated initializer.
///
- (instancetype)initWithShasum:(NSString *)shasum
state:(santa_rulestate_t)state
type:(santa_ruletype_t)type
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg;
@end

View File

@@ -17,8 +17,8 @@
@implementation SNTRule
- (instancetype)initWithShasum:(NSString *)shasum
state:(santa_rulestate_t)state
type:(santa_ruletype_t)type
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
self = [super init];
if (self) {
@@ -77,7 +77,7 @@
}
- (NSString *)description {
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %d, Type: %d",
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld",
self.shasum, self.state, self.type];
}

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
#import "SNTCommonEnums.h"
///
/// Represents an event stored in the database.
@@ -73,7 +73,7 @@
///
/// The decision santad returned.
///
@property santa_eventstate_t decision;
@property SNTEventState decision;
///
/// NSArray of logged in users when the decision was made.

View File

@@ -72,7 +72,7 @@
_executingUser = DECODE(NSString, @"executingUser");
_occurrenceDate = DECODE(NSDate, @"occurrenceDate");
_decision = (santa_eventstate_t)[DECODE(NSNumber, @"decision") intValue];
_decision = (SNTEventState)[DECODE(NSNumber, @"decision") intValue];
_pid = DECODE(NSNumber, @"pid");
_ppid = DECODE(NSNumber, @"ppid");
_parentName = DECODE(NSString, @"parentName");

View File

@@ -0,0 +1,22 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#define STRONGIFY(var) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
__strong __typeof(var) var = (Weak_##var); \
_Pragma("clang diagnostic pop")
#define WEAKIFY(var) \
__weak __typeof(var) Weak_##var = (var);

View File

@@ -14,7 +14,7 @@
/**
A wrapper around NSXPCListener and NSXPCConnection to provide client multiplexing, signature
validation of connecting clients and a simpler interface.
validation of connecting clients and forced connection establishment.
Example server started by @c launchd where the @c launchd job has a @c MachServices key:
@@ -41,6 +41,11 @@
[conn.remoteObjectProxy selectorInRemoteInterface];
@endcode
One advantage of the way that SNTXPCConnection works over using NSXPCConnection directly is that
from the client-side once the resume method has finished, the connection is either valid or the
invalidation handler will be called. Ordinarily, the connection doesn't actually get made until
the first message is sent across it.
@note messages are always delivered on a background thread!
*/
@interface SNTXPCConnection : NSObject<NSXPCListenerDelegate>
@@ -48,14 +53,14 @@
/**
Initialize a new server with a given listener, provided by `[NSXPCListener anonymousListener]`.
*/
- (instancetype)initServerWithListener:(NSXPCListener *)listener;
- (nullable instancetype)initServerWithListener:(nonnull NSXPCListener *)listener;
/**
Initializer for the 'server' side of the connection, started by launchd.
@param name MachService name, must match the MachServices key in the launchd.plist
*/
- (instancetype)initServerWithName:(NSString *)name;
- (nullable instancetype)initServerWithName:(nonnull NSString *)name;
/**
Initializer a new client to a service exported by a LaunchDaemon.
@@ -63,17 +68,20 @@
@param name MachService name
@param privileged Use YES if the server is running as root.
*/
- (instancetype)initClientWithName:(NSString *)name privileged:(BOOL)privileged;
- (nullable instancetype)initClientWithName:(nonnull NSString *)name privileged:(BOOL)privileged;
/**
Initialize a new client with a listener endpoint sent from another process.
@param listener An NSXPCListenerEndpoint to connect to.
*/
- (instancetype)initClientWithListener:(NSXPCListenerEndpoint *)listener;
- (nullable instancetype)initClientWithListener:(nonnull NSXPCListenerEndpoint *)listener;
/**
Call when the properties of the object have been set-up and you're ready for connections.
For clients, this call can take up to 2s to complete for connection to finish establishing though
in basically all cases it will actually complete in a few milliseconds.
*/
- (void)resume;
@@ -84,27 +92,35 @@
/**
The interface the remote object should conform to. (client)
*/
@property(retain) NSXPCInterface *remoteInterface;
*/
@property(retain, nullable) NSXPCInterface *remoteInterface;
/**
A proxy to the object at the other end of the connection. (client)
*/
@property(readonly, nonatomic) id remoteObjectProxy;
@note If the connection to the server failed, this will be nil, so you can safely send messages
and rely on the invalidationHandler for handling the failure.
*/
@property(readonly, nonatomic, nullable) id remoteObjectProxy;
/**
The interface this object exports. (server)
*/
@property(retain) NSXPCInterface *exportedInterface;
*/
@property(retain, nullable) NSXPCInterface *exportedInterface;
/**
The object that responds to messages from the other end. (server)
*/
@property(retain) id exportedObject;
*/
@property(retain, nullable) id exportedObject;
/**
A block to run when a/the connection is invalidated/interrupted.
*/
@property(copy) void (^invalidationHandler)(void);
A block to run when a/the connection is accepted and fully established.
*/
@property(copy, nullable) void (^acceptedHandler)(void);
/**
A block to run when a/the connection is invalidated/interrupted/rejected.
*/
@property(copy, nullable) void (^invalidationHandler)(void);
@end

View File

@@ -16,6 +16,34 @@
#import "MOLCodesignChecker.h"
#import "SNTStrengthify.h"
/**
Protocol used during connection establishment, @see SNTXPCConnectionInterface
*/
@protocol SNTXPCConnectionProtocol
- (void)connectWithReply:(void (^)())reply;
@end
/**
Recipient object used during connection establishment. Each incoming connection
has one of these objects created which accept the message in the protocol
and call the block provided during creation before replying.
This allows the server to reset the connection's exporteed interface and
object to the correct values after the client has sent the establishment message.
*/
@interface SNTXPCConnectionInterface : NSObject<SNTXPCConnectionProtocol>
@property(strong) void (^block)(void);
@end
@implementation SNTXPCConnectionInterface
- (void)connectWithReply:(void (^)())reply {
if (self.block) self.block();
reply();
}
@end
@interface SNTXPCConnection ()
/// The XPC listener (server only).
@property NSXPCListener *listenerObject;
@@ -34,7 +62,6 @@
self = [super init];
if (self) {
_listenerObject = listener;
if (!_listenerObject) return nil;
_acceptedConnections = [NSMutableArray array];
}
return self;
@@ -75,10 +102,30 @@
self.listenerObject.delegate = self;
[self.listenerObject resume];
} else {
self.currentConnection.remoteObjectInterface = self.remoteInterface;
WEAKIFY(self);
// Set-up the connection with the remote interface set to the validation interface,
// send a message to the listener to finish establishing the connection
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
self.currentConnection.remoteObjectInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
self.currentConnection.interruptionHandler = self.invalidationHandler;
self.currentConnection.invalidationHandler = self.invalidationHandler;
[self.currentConnection resume];
[[self.currentConnection remoteObjectProxy] connectWithReply:^{
STRONGIFY(self);
// The connection is now established
[self.currentConnection suspend];
self.currentConnection.remoteObjectInterface = self.remoteInterface;
[self.currentConnection resume];
dispatch_semaphore_signal(sema);
if (self.acceptedHandler) self.acceptedHandler();
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
// Connection was not established in a reasonable time, invalidate.
self.currentConnection.remoteObjectInterface = nil; // ensure clients don't try to use it.
[self.currentConnection invalidate];
}
}
}
@@ -89,18 +136,39 @@
return NO;
}
[self.acceptedConnections addObject:connection];
// The client passed the code signature check, now we need to resume the listener and
// return YES so that the client can send the connectWithReply message. Once the client does
// we reset the connection's exportedInterface and exportedObject.
__weak __typeof(connection) weakConnection = connection;
connection.interruptionHandler = connection.invalidationHandler = ^{
[self.acceptedConnections removeObject:weakConnection];
if (self.invalidationHandler) self.invalidationHandler();
SNTXPCConnectionInterface *ci = [[SNTXPCConnectionInterface alloc] init];
WEAKIFY(self);
WEAKIFY(connection);
ci.block = ^{
STRONGIFY(self)
STRONGIFY(connection);
[connection suspend];
[self.acceptedConnections addObject:connection];
WEAKIFY(connection);
connection.invalidationHandler = connection.interruptionHandler = ^{
STRONGIFY(connection);
[self.acceptedConnections removeObject:connection];
if (self.invalidationHandler) self.invalidationHandler();
};
connection.exportedInterface = self.exportedInterface;
connection.exportedObject = self.exportedObject;
[connection resume];
// The connection is now established.
if (self.acceptedHandler) self.acceptedHandler();
};
connection.exportedInterface = self.exportedInterface;
connection.exportedObject = self.exportedObject;
connection.exportedInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
connection.exportedObject = ci;
[connection resume];
return YES;
}
@@ -119,11 +187,12 @@
if (self.currentConnection) {
[self.currentConnection invalidate];
self.currentConnection = nil;
} else if (self.acceptedConnections.count) {
} else if (self.listenerObject) {
for (NSXPCConnection *conn in self.acceptedConnections) {
[conn invalidate];
}
[self.acceptedConnections removeAllObjects];
[self.listenerObject invalidate];
}
}

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
#import "SNTCommonEnums.h"
@class SNTRule;
@class SNTStoredEvent;
@@ -33,12 +33,9 @@
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply;
- (void)databaseRuleAddRule:(SNTRule *)rule
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(BOOL success))reply;
- (void)databaseRuleAddRules:(NSArray *)rules
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(BOOL success))reply;
reply:(void (^)(NSError *error))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
@@ -49,8 +46,8 @@
/// Config ops
///
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
- (void)clientMode:(void (^)(santa_clientmode_t))reply;
- (void)setClientMode:(santa_clientmode_t)mode reply:(void (^)())reply;
- (void)clientMode:(void (^)(SNTClientMode))reply;
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;

View File

@@ -113,7 +113,7 @@ void SantaDecisionManager::DisconnectClient(bool itDied) {
// Ask santad to shutdown, in case it's running.
if (!itDied) {
santa_message_t *message = new santa_message_t;
auto message = new santa_message_t;
message->action = ACTION_REQUEST_SHUTDOWN;
PostToDecisionQueue(message);
delete message;
@@ -136,8 +136,8 @@ void SantaDecisionManager::DisconnectClient(bool itDied) {
}
bool SantaDecisionManager::ClientConnected() const {
proc_t p = proc_find(client_pid_);
bool is_exiting = false;
auto p = proc_find(client_pid_);
auto is_exiting = false;
if (p) {
is_exiting = proc_exiting(p);
proc_rele(p);
@@ -218,7 +218,7 @@ void SantaDecisionManager::AddToCache(
}
if (decision == ACTION_REQUEST_BINARY) {
SantaCachedDecision *pending = new SantaCachedDecision();
auto pending = new SantaCachedDecision();
pending->setAction(ACTION_REQUEST_BINARY, 0);
lck_rw_lock_exclusive(cached_decisions_lock_);
cached_decisions_->setObject(identifier, pending);
@@ -226,7 +226,7 @@ void SantaDecisionManager::AddToCache(
pending->release(); // it was retained when added to the dictionary
} else {
lck_rw_lock_exclusive(cached_decisions_lock_);
SantaCachedDecision *pending = OSDynamicCast(
auto pending = OSDynamicCast(
SantaCachedDecision, cached_decisions_->getObject(identifier));
if (pending) {
pending->setAction(decision, microsecs);
@@ -237,7 +237,7 @@ void SantaDecisionManager::AddToCache(
void SantaDecisionManager::CacheCheck(const char *identifier) {
lck_rw_lock_shared(cached_decisions_lock_);
bool shouldInvalidate = (cached_decisions_->getObject(identifier) != nullptr);
auto shouldInvalidate = (cached_decisions_->getObject(identifier) != nullptr);
if (shouldInvalidate) {
if (!lck_rw_lock_shared_to_exclusive(cached_decisions_lock_)) {
// shared_to_exclusive will return false if a previous reader upgraded
@@ -265,7 +265,7 @@ void SantaDecisionManager::ClearCache() {
#pragma mark Decision Fetching
santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
santa_action_t result = ACTION_UNSET;
auto result = ACTION_UNSET;
uint64_t decision_time = 0;
lck_rw_lock_shared(cached_decisions_lock_);
@@ -278,7 +278,7 @@ santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
lck_rw_unlock_shared(cached_decisions_lock_);
if (RESPONSE_VALID(result)) {
uint64_t diff_time = GetCurrentUptime();
auto diff_time = GetCurrentUptime();
if (result == ACTION_RESPOND_ALLOW) {
if ((kMaxAllowCacheTimeMilliseconds * 1000) > diff_time) {
@@ -307,7 +307,7 @@ santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
santa_action_t SantaDecisionManager::GetFromDaemon(
santa_message_t *message, const char *vnode_id_str) {
santa_action_t return_action = ACTION_UNSET;
auto return_action = ACTION_UNSET;
// Wait for the daemon to respond or die.
do {
@@ -350,11 +350,10 @@ santa_action_t SantaDecisionManager::FetchDecision(
const vnode_t vp,
const uint64_t vnode_id,
const char *vnode_id_str) {
santa_action_t return_action = ACTION_UNSET;
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
// Check to see if item is in cache
return_action = GetFromCache(vnode_id_str);
auto return_action = GetFromCache(vnode_id_str);
// If item was in cache return it.
if (RESPONSE_VALID(return_action)) return return_action;
@@ -366,45 +365,34 @@ santa_action_t SantaDecisionManager::FetchDecision(
path[0] = '\0';
}
santa_message_t *message = NewMessage();
auto message = NewMessage();
strlcpy(message->path, path, sizeof(message->path));
message->action = ACTION_REQUEST_BINARY;
message->vnode_id = vnode_id;
proc_name(message->ppid, message->pname, sizeof(message->pname));
santa_action_t ret = GetFromDaemon(message, vnode_id_str);
return_action = GetFromDaemon(message, vnode_id_str);
delete message;
return ret;
return return_action;
}
#pragma mark Misc
santa_message_t *SantaDecisionManager::NewMessage() const {
santa_message_t *message = new santa_message_t;
message->uid = kauth_getuid();
message->gid = kauth_getgid();
message->pid = proc_selfpid();
message->ppid = proc_selfppid();
return message;
}
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
bool kr = false;
lck_mtx_lock(decision_dataqueue_lock_);
kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
lck_mtx_unlock(decision_dataqueue_lock_);
return kr;
}
bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
bool kr = false;
lck_mtx_lock(log_dataqueue_lock_);
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
if (!kr) {
if (OSCompareAndSwap(0, 1, &failed_log_queue_requests_)) {
LOGW("Dropping log queue messages");
}
// If enqueue failed, pop an item off the queue and try again.
uint32_t dataSize = sizeof(santa_message_t);
uint32_t dataSize = 0;
log_dataqueue_->dequeue(0, &dataSize);
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
} else {
@@ -414,23 +402,6 @@ bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
return kr;
}
uint64_t SantaDecisionManager::GetVnodeIDForVnode(
const vfs_context_t ctx, const vnode_t vp) const {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fsid);
VATTR_WANTED(&vap, va_fileid);
vnode_getattr(vp, &vap, ctx);
return (((uint64_t)vap.va_fsid << 32) | vap.va_fileid);
}
uint64_t SantaDecisionManager::GetCurrentUptime() {
clock_sec_t sec;
clock_usec_t usec;
clock_get_system_microtime(&sec, &usec);
return (uint64_t)((sec * 1000000) + usec);
}
#pragma mark Invocation Tracking & PID comparison
void SantaDecisionManager::IncrementListenerInvocations() {
@@ -451,12 +422,12 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
// Get ID for the vnode and convert it to a string.
uint64_t vnode_id = GetVnodeIDForVnode(ctx, vp);
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
char vnode_str[MAX_VNODE_ID_STR];
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
// Fetch decision
santa_action_t returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
auto returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
// If file has dirty blocks, remove from cache and deny. This would usually
// be the case if a file has been written to and flushed but not yet
@@ -468,9 +439,9 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
switch (returnedAction) {
case ACTION_RESPOND_ALLOW: {
proc_t proc = vfs_context_proc(ctx);
auto proc = vfs_context_proc(ctx);
if (proc) {
SantaPIDAndPPID *pidWrapper = new SantaPIDAndPPID;
auto pidWrapper = new SantaPIDAndPPID;
pidWrapper->pid = proc_pid(proc);
pidWrapper->ppid = proc_ppid(proc);
lck_rw_lock_exclusive(vnode_pid_map_lock_);
@@ -495,8 +466,8 @@ void SantaDecisionManager::FileOpCallback(
const kauth_action_t action, const vnode_t vp,
const char *path, const char *new_path) {
if (vp) {
vfs_context_t context = vfs_context_create(nullptr);
uint64_t vnode_id = GetVnodeIDForVnode(context, vp);
auto context = vfs_context_create(nullptr);
auto vnode_id = GetVnodeIDForVnode(context, vp);
vfs_context_rele(context);
if (action == KAUTH_FILEOP_CLOSE) {
@@ -504,7 +475,7 @@ void SantaDecisionManager::FileOpCallback(
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
CacheCheck(vnode_id_str);
} else if (action == KAUTH_FILEOP_EXEC) {
santa_message_t *message = NewMessage();
auto message = NewMessage();
message->vnode_id = vnode_id;
message->action = ACTION_NOTIFY_EXEC;
strlcpy(message->path, path, sizeof(message->path));
@@ -513,7 +484,7 @@ void SantaDecisionManager::FileOpCallback(
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
lck_rw_lock_shared(vnode_pid_map_lock_);
SantaPIDAndPPID *pidWrapper = OSDynamicCast(
auto pidWrapper = OSDynamicCast(
SantaPIDAndPPID, vnode_pid_map_->getObject(vnode_str));
if (pidWrapper) {
message->pid = pidWrapper->pid;
@@ -527,9 +498,11 @@ void SantaDecisionManager::FileOpCallback(
}
}
// Filter out modifications to locations that are definitely not useful.
if (ClientConnected() && !strprefix(path, "/.") && !strprefix(path, "/dev")) {
santa_message_t *message = NewMessage();
// Filter out modifications to locations that are definitely
// not useful or made by santad.
if (proc_selfpid() != client_pid_ &&
!strprefix(path, "/.") && !strprefix(path, "/dev")) {
auto message = NewMessage();
strlcpy(message->path, path, sizeof(message->path));
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));
proc_name(message->pid, message->pname, sizeof(message->pname));
@@ -563,7 +536,7 @@ void SantaDecisionManager::FileOpCallback(
extern "C" int fileop_scope_callback(
kauth_cred_t credential, void *idata, kauth_action_t action,
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
SantaDecisionManager *sdm = OSDynamicCast(
auto sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
vnode_t vp = nullptr;
@@ -606,8 +579,8 @@ extern "C" int vnode_scope_callback(
return KAUTH_RESULT_DEFER;
}
SantaDecisionManager *sdm =
OSDynamicCast(SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
auto sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
sdm->IncrementListenerInvocations();
int result = sdm->VnodeCallback(credential,

View File

@@ -121,41 +121,41 @@ class SantaDecisionManager : public OSObject {
/// The maximum number of milliseconds a cached deny message should be
/// considered valid.
///
const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
///
/// The maximum number of milliseconds a cached allow message should be
/// considered valid.
///
const uint64_t kMaxAllowCacheTimeMilliseconds = 1000 * 60 * 60 * 24;
static const uint64_t kMaxAllowCacheTimeMilliseconds = 1000 * 60 * 60 * 24;
///
/// While waiting for a response from the daemon, this is the number of
/// milliseconds to sleep for before checking the cache for a response.
///
const int kRequestLoopSleepMilliseconds = 10;
static const uint32_t kRequestLoopSleepMilliseconds = 10;
///
/// Maximum number of entries in the in-kernel cache.
///
const int kMaxCacheSize = 10000;
static const uint32_t kMaxCacheSize = 10000;
///
/// Maximum number of PostToDecisionQueue failures to allow.
///
const int kMaxDecisionQueueFailures = 10;
static const uint32_t kMaxDecisionQueueFailures = 10;
///
/// The maximum number of messages can be kept in
/// the decision data queue at any time.
///
const int kMaxDecisionQueueEvents = 512;
static const uint32_t kMaxDecisionQueueEvents = 512;
///
/// The maximum number of messages can be kept
/// in the logging data queue at any time.
///
const int kMaxLogQueueEvents = 1024;
static const uint32_t kMaxLogQueueEvents = 1024;
/// Fetches a response from the cache, first checking to see if the
/// entry has expired.
@@ -210,17 +210,37 @@ class SantaDecisionManager : public OSObject {
/// @param vp The Vnode to get the ID for
/// @return uint64_t The Vnode ID as a 64-bit unsigned int.
///
uint64_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp) const;
static inline uint64_t GetVnodeIDForVnode(const vfs_context_t ctx,
const vnode_t vp) {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fsid);
VATTR_WANTED(&vap, va_fileid);
vnode_getattr(vp, &vap, ctx);
return (((uint64_t)vap.va_fsid << 32) | vap.va_fileid);
}
///
/// Creates a new santa_message_t with some fields pre-filled.
///
santa_message_t *NewMessage() const;
static inline santa_message_t *NewMessage() {
auto message = new santa_message_t;
message->uid = kauth_getuid();
message->gid = kauth_getgid();
message->pid = proc_selfpid();
message->ppid = proc_selfppid();
return message;
}
///
/// Returns the current system uptime in microseconds
///
static uint64_t GetCurrentUptime();
static inline uint64_t GetCurrentUptime() {
clock_sec_t sec;
clock_usec_t usec;
clock_get_system_microtime(&sec, &usec);
return (uint64_t)((sec * 1000000) + usec);
}
private:
lck_grp_t *sdm_lock_grp_;
@@ -237,10 +257,10 @@ class SantaDecisionManager : public OSObject {
IOSharedDataQueue *decision_dataqueue_;
IOSharedDataQueue *log_dataqueue_;
SInt32 failed_decision_queue_requests_;
SInt32 failed_log_queue_requests_;
int32_t failed_decision_queue_requests_;
int32_t failed_log_queue_requests_;
SInt32 listener_invocations_;
int32_t listener_invocations_;
pid_t client_pid_;

View File

@@ -139,7 +139,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
}
}
}
if (csc.certificates) {
if (csc.certificates.count) {
printf("Signing chain:\n");
[csc.certificates enumerateObjectsUsingBlock:^(MOLCertificate *c,

View File

@@ -77,8 +77,8 @@ REGISTER_COMMAND_NAME(@"rule")
}
SNTRule *newRule = [[SNTRule alloc] init];
newRule.state = RULESTATE_UNKNOWN;
newRule.type = RULETYPE_BINARY;
newRule.state = SNTRuleStateUnknown;
newRule.type = SNTRuleTypeBinary;
NSString *path;
@@ -87,15 +87,15 @@ REGISTER_COMMAND_NAME(@"rule")
NSString *arg = arguments[i];
if ([arg caseInsensitiveCompare:@"--whitelist"] == NSOrderedSame) {
newRule.state = RULESTATE_WHITELIST;
newRule.state = SNTRuleStateWhitelist;
} else if ([arg caseInsensitiveCompare:@"--blacklist"] == NSOrderedSame) {
newRule.state = RULESTATE_BLACKLIST;
newRule.state = SNTRuleStateBlacklist;
} else if ([arg caseInsensitiveCompare:@"--silent-blacklist"] == NSOrderedSame) {
newRule.state = RULESTATE_SILENT_BLACKLIST;
newRule.state = SNTRuleStateSilentBlacklist;
} else if ([arg caseInsensitiveCompare:@"--remove"] == NSOrderedSame) {
newRule.state = RULESTATE_REMOVE;
newRule.state = SNTRuleStateRemove;
} else if ([arg caseInsensitiveCompare:@"--certificate"] == NSOrderedSame) {
newRule.type = RULETYPE_CERT;
newRule.type = SNTRuleTypeCertificate;
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--path requires an argument"];
@@ -121,26 +121,29 @@ REGISTER_COMMAND_NAME(@"rule")
if (path) {
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:path];
if (newRule.type == RULETYPE_BINARY) {
if (newRule.type == SNTRuleTypeBinary) {
newRule.shasum = fi.SHA256;
} else if (newRule.type == RULETYPE_CERT) {
} else if (newRule.type == SNTRuleTypeCertificate) {
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.path];
newRule.shasum = cs.leafCertificate.SHA256;
}
}
if (newRule.state == RULESTATE_UNKNOWN) {
if (newRule.state == SNTRuleStateUnknown) {
[self printErrorUsageAndExit:@"No state specified"];
} else if (!newRule.shasum) {
[self printErrorUsageAndExit:@"Either SHA-256 or path to file must be specified"];
}
[[daemonConn remoteObjectProxy] databaseRuleAddRule:newRule cleanSlate:NO reply:^(BOOL success) {
if (!success) {
printf("Failed to modify rules.");
[[daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
cleanSlate:NO
reply:^(NSError *error) {
if (error) {
printf("Failed to modify rules: %s", [error.localizedDescription UTF8String]);
LOGD(@"Failure reason: %@", error.localizedFailureReason);
exit(1);
} else {
if (newRule.state == RULESTATE_REMOVE) {
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);

View File

@@ -50,16 +50,16 @@ REGISTER_COMMAND_NAME(@"status")
__block uint64_t cpuEvents, ramEvents;
__block double cpuPeak, ramPeak;
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] clientMode:^(santa_clientmode_t cm) {
[[daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
switch (cm) {
case CLIENTMODE_MONITOR:
case SNTClientModeMonitor:
clientMode = @"Monitor";
break;
case CLIENTMODE_LOCKDOWN:
case SNTClientModeLockdown:
clientMode = @"Lockdown";
break;
default:
clientMode = [NSString stringWithFormat:@"Unknown (%d)", cm];
clientMode = [NSString stringWithFormat:@"Unknown (%ld)", cm];
break;
}
dispatch_group_leave(group);

View File

@@ -21,7 +21,7 @@
///
/// The underlying session. Pass this session to NSURLRequest methods.
///
@property(readonly) NSURLSession *session;
@property(readonly, nonatomic) NSURLSession *session;
///
/// If set, this is the user-agent to send with requests, otherwise remains the default

View File

@@ -18,14 +18,17 @@
#import "SNTDERDecoder.h"
#import "SNTLogging.h"
@interface SNTAuthenticatingURLSession ()
@property(readwrite) NSURLSession *session;
@property NSURLSessionConfiguration *sessionConfig;
@end
@implementation SNTAuthenticatingURLSession
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (self) {
_session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:nil];
_sessionConfig = configuration;
}
return self;
}
@@ -37,16 +40,30 @@
return [self initWithSessionConfiguration:config];
}
#pragma mark Session Fetching
- (NSURLSession *)session {
if (!_session) {
_session = [NSURLSession sessionWithConfiguration:self.sessionConfig
delegate:self
delegateQueue:nil];
}
return _session;
}
#pragma mark User Agent property
- (NSString *)userAgent {
return _session.configuration.HTTPAdditionalHeaders[@"User-Agent"];
return self.sessionConfig.HTTPAdditionalHeaders[@"User-Agent"];
}
- (void)setUserAgent:(NSString *)userAgent {
NSMutableDictionary *addlHeaders = [_session.configuration.HTTPAdditionalHeaders mutableCopy];
NSMutableDictionary *addlHeaders = [self.sessionConfig.HTTPAdditionalHeaders mutableCopy];
if (!addlHeaders) addlHeaders = [NSMutableDictionary dictionary];
addlHeaders[@"User-Agent"] = userAgent;
_session.configuration.HTTPAdditionalHeaders = addlHeaders;
self.sessionConfig.HTTPAdditionalHeaders = [addlHeaders copy];
_session = nil;
}
#pragma mark NSURLSessionDelegate methods
@@ -87,8 +104,8 @@
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
return;
} else {
LOGE(@"Server asked for client authentication but no usable client certificate found.");
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
LOGW(@"Server asked for client authentication but no usable client certificate found.");
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
return;
}
} else if (authMethod == NSURLAuthenticationMethodServerTrust) {
@@ -191,6 +208,7 @@
SecCertificateRef certificate = NULL;
err = SecIdentityCopyCertificate(foundIdentity, &certificate);
MOLCertificate *clientCert = [[MOLCertificate alloc] initWithSecCertificateRef:certificate];
if (certificate) CFRelease(certificate);
LOGD(@"Client Trust: Valid client identity %@.", clientCert);
NSURLCredential *cred =
[NSURLCredential credentialWithIdentity:foundIdentity

View File

@@ -135,8 +135,15 @@ REGISTER_COMMAND_NAME(@"sync")
LOGW(@"Missing Machine Owner.");
}
if (arguments.count == 2 && [[arguments firstObject] isEqual:@"singleevent"]) {
[s eventUploadSingleEvent:arguments[1]];
if ([arguments containsObject:@"singleevent"]) {
NSUInteger idx = [arguments indexOfObject:@"singleevent"];
idx++;
NSString *obj = arguments[idx];
if (obj.length != 64) {
LOGI(@"singleevent passed without SHA-256 as next argument");
exit(1);
}
[s eventUploadSingleEvent:obj];
} else {
[s preflight];
}
@@ -148,7 +155,6 @@ REGISTER_COMMAND_NAME(@"sync")
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Preflight complete");
if (self.syncState.uploadLogURL) {
[self logUpload];
} else {
@@ -167,7 +173,6 @@ REGISTER_COMMAND_NAME(@"sync")
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Log upload complete");
} else {
LOGE(@"Log upload failed, continuing anyway");
}
@@ -182,7 +187,6 @@ REGISTER_COMMAND_NAME(@"sync")
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Event upload complete");
[self ruleDownload];
} else {
LOGE(@"Event upload failed, aborting run");
@@ -198,7 +202,6 @@ REGISTER_COMMAND_NAME(@"sync")
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Event upload complete");
exit(0);
} else {
LOGW(@"Event upload failed");
@@ -213,7 +216,6 @@ REGISTER_COMMAND_NAME(@"sync")
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Rule download complete");
[self postflight];
} else {
LOGE(@"Rule download failed, aborting run");
@@ -228,7 +230,6 @@ REGISTER_COMMAND_NAME(@"sync")
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Postflight complete");
LOGI(@"Sync completed successfully");
exit(0);
} else {

View File

@@ -158,19 +158,19 @@
ADDKEY(newEvent, kCurrentSessions, event.currentSessions);
switch (event.decision) {
case EVENTSTATE_ALLOW_UNKNOWN: ADDKEY(newEvent, kDecision, kDecisionAllowUnknown); break;
case EVENTSTATE_ALLOW_BINARY: ADDKEY(newEvent, kDecision, kDecisionAllowBinary); break;
case EVENTSTATE_ALLOW_CERTIFICATE:
case SNTEventStateAllowUnknown: ADDKEY(newEvent, kDecision, kDecisionAllowUnknown); break;
case SNTEventStateAllowBinary: ADDKEY(newEvent, kDecision, kDecisionAllowBinary); break;
case SNTEventStateAllowCertificate:
ADDKEY(newEvent, kDecision, kDecisionAllowCertificate);
break;
case EVENTSTATE_ALLOW_SCOPE: ADDKEY(newEvent, kDecision, kDecisionAllowScope); break;
case EVENTSTATE_BLOCK_UNKNOWN: ADDKEY(newEvent, kDecision, kDecisionBlockUnknown); break;
case EVENTSTATE_BLOCK_BINARY: ADDKEY(newEvent, kDecision, kDecisionBlockBinary); break;
case EVENTSTATE_BLOCK_CERTIFICATE:
case SNTEventStateAllowScope: ADDKEY(newEvent, kDecision, kDecisionAllowScope); break;
case SNTEventStateBlockUnknown: ADDKEY(newEvent, kDecision, kDecisionBlockUnknown); break;
case SNTEventStateBlockBinary: ADDKEY(newEvent, kDecision, kDecisionBlockBinary); break;
case SNTEventStateBlockCertificate:
ADDKEY(newEvent, kDecision, kDecisionBlockCertificate);
break;
case EVENTSTATE_BLOCK_SCOPE: ADDKEY(newEvent, kDecision, kDecisionBlockScope); break;
case EVENTSTATE_RELATED_BINARY: ADDKEY(newEvent, kDecision, kDecisionRelatedBinary); break;
case SNTEventStateBlockScope: ADDKEY(newEvent, kDecision, kDecisionBlockScope); break;
case SNTEventStateRelatedBinary: ADDKEY(newEvent, kDecision, kDecisionRelatedBinary); break;
default: ADDKEY(newEvent, kDecision, kDecisionUnknown);
}
@@ -245,7 +245,7 @@
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.filePath = fi.path;
se.fileSHA256 = fi.SHA256;
se.decision = EVENTSTATE_RELATED_BINARY;
se.decision = SNTEventStateRelatedBinary;
se.fileBundleID = event.fileBundleID;
se.fileBundleName = event.fileBundleName;
se.fileBundleVersion = event.fileBundleVersion;

View File

@@ -16,11 +16,11 @@
#import "NSData+Zlib.h"
#include "SNTCommonEnums.h"
#include "SNTLogging.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTCommonEnums.h"
@implementation SNTCommandSyncLogUpload

View File

@@ -81,9 +81,9 @@
syncState.uploadLogURL = [NSURL URLWithString:r[kUploadLogsURL]];
if ([r[kClientMode] isEqual:kClientModeMonitor]) {
syncState.newClientMode = CLIENTMODE_MONITOR;
syncState.newClientMode = SNTClientModeMonitor;
} else if ([r[kClientMode] isEqual:kClientModeLockdown]) {
syncState.newClientMode = CLIENTMODE_LOCKDOWN;
syncState.newClientMode = SNTClientModeLockdown;
}
if ([r[kWhitelistRegex] isKindOfClass:[NSString class]]) {

View File

@@ -91,12 +91,13 @@
if (syncState.downloadedRules.count) {
[[daemonConn remoteObjectProxy] databaseRuleAddRules:syncState.downloadedRules
cleanSlate:syncState.cleanSync
reply:^(BOOL success) {
if (success) {
reply:^(NSError *error) {
if (!error) {
LOGI(@"Added %lu rule(s)", syncState.downloadedRules.count);
handler(YES);
} else {
LOGE(@"Failed to add rules to database");
LOGE(@"Failed to add rule(s) to database: %@", error.localizedDescription);
LOGD(@"Failure reason: %@", error.localizedFailureReason);
handler(NO);
}
}];
@@ -117,22 +118,22 @@
NSString *policyString = dict[kRulePolicy];
if ([policyString isEqual:kRulePolicyWhitelist]) {
newRule.state = RULESTATE_WHITELIST;
newRule.state = SNTRuleStateWhitelist;
} else if ([policyString isEqual:kRulePolicyBlacklist]) {
newRule.state = RULESTATE_BLACKLIST;
newRule.state = SNTRuleStateBlacklist;
} else if ([policyString isEqual:kRulePolicySilentBlacklist]) {
newRule.state = RULESTATE_SILENT_BLACKLIST;
newRule.state = SNTRuleStateSilentBlacklist;
} else if ([policyString isEqual:kRulePolicyRemove]) {
newRule.state = RULESTATE_REMOVE;
newRule.state = SNTRuleStateRemove;
} else {
return nil;
}
NSString *ruleTypeString = dict[kRuleType];
if ([ruleTypeString isEqual:kRuleTypeBinary]) {
newRule.type = RULETYPE_BINARY;
newRule.type = SNTRuleTypeBinary;
} else if ([ruleTypeString isEqual:kRuleTypeCertificate]) {
newRule.type = RULETYPE_CERT;
newRule.type = SNTRuleTypeCertificate;
} else {
return nil;
}

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
#import "SNTCommonEnums.h"
/// An instance of this class is passed to each stage of the sync process for storing data
/// that might be needed in later stages.
@@ -30,7 +30,7 @@
@property BOOL cleanSync;
/// New client mode sent from server
@property santa_clientmode_t newClientMode;
@property SNTClientMode newClientMode;
/// Batch size for uploading events, sent from server
@property int32_t eventBatchSize;

View File

@@ -17,8 +17,7 @@
#include <sys/stat.h>
#include <sys/types.h>
#include "SNTCommonEnums.h"
#import "SNTCommonEnums.h"
#import "SNTConfigurator.h"
#import "SNTDaemonControlController.h"
#import "SNTDatabaseController.h"
@@ -51,8 +50,6 @@
if (!_driverManager) {
LOGE(@"Failed to connect to driver, exiting.");
// TODO(rah): Consider trying to load the extension from within santad.
return nil;
}

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
#import "SNTCommonEnums.h"
///
/// Store information about executions from decision making for later logging.
@@ -20,7 +20,7 @@
@interface SNTCachedDecision : NSObject
@property uint64_t vnodeId;
@property santa_eventstate_t decision;
@property SNTEventState decision;
@property NSString *decisionExtra;
@property NSString *sha256;
@property NSString *certSHA256;

View File

@@ -97,23 +97,20 @@ double watchdogRAMPeak = 0;
reply([rdb binaryRuleCount], [rdb certificateRuleCount]);
}
- (void)databaseRuleAddRule:(SNTRule *)rule cleanSlate:(BOOL)cleanSlate
reply:(void (^)(BOOL success))reply {
[self databaseRuleAddRules:@[ rule ] cleanSlate:cleanSlate reply:reply];
}
- (void)databaseRuleAddRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate
reply:(void (^)(BOOL success))reply {
BOOL success = [[SNTDatabaseController ruleTable] addRules:rules cleanSlate:cleanSlate];
- (void)databaseRuleAddRules:(NSArray *)rules
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(NSError *error))reply {
NSError *error;
[[SNTDatabaseController ruleTable] addRules:rules cleanSlate:cleanSlate error:&error];
// If any rules were added that were not whitelist, flush cache.
NSPredicate *p = [NSPredicate predicateWithFormat:@"SELF.state != %d", RULESTATE_WHITELIST];
NSPredicate *p = [NSPredicate predicateWithFormat:@"SELF.state != %d", SNTRuleStateWhitelist];
if ([rules filteredArrayUsingPredicate:p].count || cleanSlate) {
LOGI(@"Received non-whitelist rule, flushing cache");
[self.driverManager flushCache];
}
reply(success);
reply(error);
}
- (void)databaseEventCount:(void (^)(int64_t count))reply {
@@ -134,11 +131,11 @@ double watchdogRAMPeak = 0;
#pragma mark Config Ops
- (void)clientMode:(void (^)(santa_clientmode_t))reply {
- (void)clientMode:(void (^)(SNTClientMode))reply {
reply([[SNTConfigurator configurator] clientMode]);
}
- (void)setClientMode:(santa_clientmode_t)mode reply:(void (^)())reply {
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply {
[[SNTConfigurator configurator] setClientMode:mode];
reply();
}

View File

@@ -14,10 +14,10 @@
#import "SNTDriverManager.h"
#include <IOKit/IODataQueueClient.h>
#include <mach/mach.h>
#import <IOKit/IODataQueueClient.h>
#import <IOKit/Kext/KextManager.h>
#include "SNTLogging.h"
#import "SNTLogging.h"
@interface SNTDriverManager ()
@property io_connect_t connection;
@@ -41,6 +41,9 @@ static const int MAX_DELAY = 15;
return nil;
}
// Attempt to load driver. It may already be running, so ignore any return value.
KextManagerLoadKextWithIdentifier(CFSTR(USERCLIENT_ID), NULL);
// Locate driver. Wait for it if necessary.
int delay = 1;
do {

View File

@@ -86,10 +86,8 @@
if (newpath) {
outStr = [outStr stringByAppendingFormat:@"|newpath=%@", [self sanitizeString:newpath]];
}
char ppath[PATH_MAX];
if (proc_pidpath(message.pid, ppath, PATH_MAX) < 1) {
strncpy(ppath, "(null)", 6);
}
char ppath[PATH_MAX] = "(null)";
proc_pidpath(message.pid, ppath, PATH_MAX);
NSString *user, *group;
struct passwd *pw = getpwuid(message.uid);
@@ -121,39 +119,39 @@
NSString *d, *r, *args, *outLog;
switch (cd.decision) {
case EVENTSTATE_ALLOW_BINARY:
case SNTEventStateAllowBinary:
d = @"ALLOW";
r = @"BINARY";
args = [self argsForPid:message.pid];
break;
case EVENTSTATE_ALLOW_CERTIFICATE:
case SNTEventStateAllowCertificate:
d = @"ALLOW";
r = @"CERTIFICATE";
args = [self argsForPid:message.pid];
break;
case EVENTSTATE_ALLOW_SCOPE:
case SNTEventStateAllowScope:
d = @"ALLOW";
r = @"SCOPE";
args = [self argsForPid:message.pid];
break;
case EVENTSTATE_ALLOW_UNKNOWN:
case SNTEventStateAllowUnknown:
d = @"ALLOW";
r = @"UNKNOWN";
args = [self argsForPid:message.pid];
break;
case EVENTSTATE_BLOCK_BINARY:
case SNTEventStateBlockBinary:
d = @"DENY";
r = @"BINARY";
break;
case EVENTSTATE_BLOCK_CERTIFICATE:
case SNTEventStateBlockCertificate:
d = @"DENY";
r = @"CERT";
break;
case EVENTSTATE_BLOCK_SCOPE:
case SNTEventStateBlockScope:
d = @"DENY";
r = @"SCOPE";
break;
case EVENTSTATE_BLOCK_UNKNOWN:
case SNTEventStateBlockUnknown:
d = @"DENY";
r = @"UNKNOWN";
break;

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
#import "SNTCommonEnums.h"
#include "SNTKernelCommon.h"
@class MOLCodesignChecker;

View File

@@ -61,17 +61,17 @@
#pragma mark Binary Validation
- (santa_eventstate_t)makeDecision:(SNTCachedDecision *)cd binaryInfo:(SNTFileInfo *)fi {
- (SNTEventState)makeDecision:(SNTCachedDecision *)cd binaryInfo:(SNTFileInfo *)fi {
SNTRule *rule = [self.ruleTable binaryRuleForSHA256:cd.sha256];
if (rule) {
switch (rule.state) {
case RULESTATE_WHITELIST:
return EVENTSTATE_ALLOW_BINARY;
case RULESTATE_SILENT_BLACKLIST:
case SNTRuleStateWhitelist:
return SNTEventStateAllowBinary;
case SNTRuleStateSilentBlacklist:
cd.silentBlock = YES;
case RULESTATE_BLACKLIST:
case SNTRuleStateBlacklist:
cd.customMsg = rule.customMsg;
return EVENTSTATE_BLOCK_BINARY;
return SNTEventStateBlockBinary;
default: break;
}
}
@@ -79,13 +79,13 @@
rule = [self.ruleTable certificateRuleForSHA256:cd.certSHA256];
if (rule) {
switch (rule.state) {
case RULESTATE_WHITELIST:
return EVENTSTATE_ALLOW_CERTIFICATE;
case RULESTATE_SILENT_BLACKLIST:
case SNTRuleStateWhitelist:
return SNTEventStateAllowCertificate;
case SNTRuleStateSilentBlacklist:
cd.silentBlock = YES;
case RULESTATE_BLACKLIST:
case SNTRuleStateBlacklist:
cd.customMsg = rule.customMsg;
return EVENTSTATE_BLOCK_CERTIFICATE;
return SNTEventStateBlockCertificate;
default: break;
}
}
@@ -93,19 +93,19 @@
NSString *msg = [self fileIsScopeBlacklisted:fi];
if (msg) {
cd.decisionExtra = msg;
return EVENTSTATE_BLOCK_SCOPE;
return SNTEventStateBlockScope;
}
msg = [self fileIsScopeWhitelisted:fi];
if (msg) {
cd.decisionExtra = msg;
return EVENTSTATE_ALLOW_SCOPE;
return SNTEventStateAllowScope;
}
switch ([[SNTConfigurator configurator] clientMode]) {
case CLIENTMODE_MONITOR: return EVENTSTATE_ALLOW_UNKNOWN;
case CLIENTMODE_LOCKDOWN: return EVENTSTATE_BLOCK_UNKNOWN;
default: return EVENTSTATE_BLOCK_UNKNOWN;
case SNTClientModeMonitor: return SNTEventStateAllowUnknown;
case SNTClientModeLockdown: return SNTEventStateBlockUnknown;
default: return SNTEventStateBlockUnknown;
}
}
@@ -140,9 +140,9 @@
[self.driverManager postToKernelAction:action forVnodeID:cd.vnodeId];
// Log to database if necessary.
if (cd.decision != EVENTSTATE_ALLOW_BINARY &&
cd.decision != EVENTSTATE_ALLOW_CERTIFICATE &&
cd.decision != EVENTSTATE_ALLOW_SCOPE) {
if (cd.decision != SNTEventStateAllowBinary &&
cd.decision != SNTEventStateAllowCertificate &&
cd.decision != SNTEventStateAllowScope) {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.occurrenceDate = [[NSDate alloc] init];
se.fileSHA256 = cd.sha256;
@@ -255,20 +255,20 @@
}
}
- (santa_action_t)actionForEventState:(santa_eventstate_t)state {
- (santa_action_t)actionForEventState:(SNTEventState)state {
switch (state) {
case EVENTSTATE_ALLOW_BINARY:
case EVENTSTATE_ALLOW_CERTIFICATE:
case EVENTSTATE_ALLOW_SCOPE:
case EVENTSTATE_ALLOW_UNKNOWN:
case SNTEventStateAllowBinary:
case SNTEventStateAllowCertificate:
case SNTEventStateAllowScope:
case SNTEventStateAllowUnknown:
return ACTION_RESPOND_ALLOW;
case EVENTSTATE_BLOCK_BINARY:
case EVENTSTATE_BLOCK_CERTIFICATE:
case EVENTSTATE_BLOCK_SCOPE:
case EVENTSTATE_BLOCK_UNKNOWN:
case SNTEventStateBlockBinary:
case SNTEventStateBlockCertificate:
case SNTEventStateBlockScope:
case SNTEventStateBlockUnknown:
return ACTION_RESPOND_DENY;
default:
LOGW(@"Invalid event state %d", state);
LOGW(@"Invalid event state %ld", state);
return ACTION_RESPOND_DENY;
}
}

View File

@@ -12,10 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTCommonEnums.h"
#import "SNTDatabaseTable.h"
#include "SNTCommonEnums.h"
@class SNTRule;
@class SNTNotificationMessage;
@@ -55,8 +54,9 @@
///
/// @param rules Array of SNTRule's to add.
/// @param cleanSlate If true, remove all rules before adding the new rules.
/// @return YES if all rules were added successfully.
/// @param error When returning NO, will be filled with appropriate error.
/// @return YES if adding all rules passed, NO if any were rejected.
///
- (BOOL)addRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate;
- (BOOL)addRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate error:(NSError **)error;
@end

View File

@@ -28,9 +28,8 @@
@implementation SNTRuleTable
- (uint32_t)initializeDatabase:(FMDatabase *)db fromVersion:(uint32_t)version {
// Save hashes of the signing certs for launchd and santad
self.santadCertSHA = [[[[MOLCodesignChecker alloc] initWithSelf] leafCertificate] SHA256];
self.launchdCertSHA = [[[[MOLCodesignChecker alloc] initWithPID:1] leafCertificate] SHA256];
// Lock this database from other processes
[db executeQuery:@"PRAGMA locking_mode = EXCLUSIVE;"];
uint32_t newVersion = 0;
@@ -41,23 +40,32 @@
@"'type' INTEGER NOT NULL, "
@"'custommsg' TEXT"
@")"];
[db executeUpdate:@"CREATE VIEW binrules AS SELECT * FROM rules WHERE type=1"];
[db executeUpdate:@"CREATE VIEW certrules AS SELECT * FROM rules WHERE type=2"];
[db executeUpdate:@"CREATE UNIQUE INDEX rulesunique ON rules (shasum, type)"];
// Insert the codesigning certs for the running santad and launchd into the initial database.
// This helps prevent accidentally denying critical system components while the database
// is empty. This 'initial database' will then be cleared on the first successful sync.
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",
self.santadCertSHA, @(RULESTATE_WHITELIST), @(RULETYPE_CERT)];
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",
self.launchdCertSHA, @(RULESTATE_WHITELIST), @(RULETYPE_CERT)];
[[SNTConfigurator configurator] setSyncCleanRequired:YES];
newVersion = 1;
}
[[SNTConfigurator configurator] setSyncCleanRequired:YES];
// Save hashes of the signing certs for launchd and santad.
// Used to ensure rules for them are not removed.
self.santadCertSHA = [[[[MOLCodesignChecker alloc] initWithSelf] leafCertificate] SHA256];
self.launchdCertSHA = [[[[MOLCodesignChecker alloc] initWithPID:1] leafCertificate] SHA256];
// Ensure the certificates used to sign the running launchd/santad are whitelisted.
// If they weren't previously and the database is not new, log an error.
int ruleCount = [db intForQuery:@"SELECT COUNT(*)"
@"FROM certrules "
@"WHERE (shasum=? OR shasum=?) AND state=?",
self.santadCertSHA, self.launchdCertSHA, @(SNTRuleStateWhitelist)];
if (ruleCount != 2) {
if (version > 0) LOGE(@"Started without launchd/santad certificate rules in place!");
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",
self.santadCertSHA, @(SNTRuleStateWhitelist), @(SNTRuleTypeCertificate)];
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",
self.launchdCertSHA, @(SNTRuleStateWhitelist), @(SNTRuleTypeCertificate)];
}
return newVersion;
@@ -130,9 +138,9 @@
#pragma mark Adding
- (BOOL)addRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate {
- (BOOL)addRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate error:(NSError **)error {
if (!rules || rules.count < 1) {
LOGE(@"Received request to add rules with nil/empty array.");
[self fillError:error code:SNTRuleTableErrorEmptyRuleArray message:nil];
return NO;
}
@@ -142,13 +150,14 @@
// Protect rules for santad/launchd certificates.
NSPredicate *p = [NSPredicate predicateWithFormat:
@"(SELF.shasum = %@ OR SELF.shasum = %@) AND SELF.type = %d",
self.santadCertSHA, self.launchdCertSHA, RULETYPE_CERT];
self.santadCertSHA, self.launchdCertSHA, SNTRuleTypeCertificate];
NSArray *requiredHashes = [rules filteredArrayUsingPredicate:p];
p = [NSPredicate predicateWithFormat:@"SELF.state == %d", RULESTATE_WHITELIST];
p = [NSPredicate predicateWithFormat:@"SELF.state == %d", SNTRuleStateWhitelist];
NSArray *requiredHashesWhitelist = [requiredHashes filteredArrayUsingPredicate:p];
if ((cleanSlate && requiredHashesWhitelist.count != 2) ||
(requiredHashes.count != requiredHashesWhitelist.count)) {
LOGE(@"Received request to remove whitelist for launchd/santad ceritifcates.");
LOGE(@"Received request to remove whitelist for launchd/santad certificates.");
[self fillError:error code:SNTRuleTableErrorMissingRequiredRule message:nil];
*rollback = failed = YES;
return;
}
@@ -159,14 +168,18 @@
for (SNTRule *rule in rules) {
if (![rule isKindOfClass:[SNTRule class]] || rule.shasum.length == 0 ||
rule.state == RULESTATE_UNKNOWN || rule.type == RULETYPE_UNKNOWN) {
rule.state == SNTRuleStateUnknown || rule.type == SNTRuleTypeUnknown) {
[self fillError:error code:SNTRuleTableErrorInvalidRule message:nil];
*rollback = failed = YES;
return;
}
if (rule.state == RULESTATE_REMOVE) {
if (rule.state == SNTRuleStateRemove) {
if (![db executeUpdate:@"DELETE FROM rules WHERE shasum=? AND type=?",
rule.shasum, @(rule.type)]) {
[self fillError:error
code:SNTRuleTableErrorRemoveFailed
message:[db lastErrorMessage]];
*rollback = failed = YES;
return;
}
@@ -175,6 +188,9 @@
@"(shasum, state, type, custommsg) "
@"VALUES (?, ?, ?, ?);",
rule.shasum, @(rule.state), @(rule.type), rule.customMsg]) {
[self fillError:error
code:SNTRuleTableErrorInsertOrReplaceFailed
message:[db lastErrorMessage]];
*rollback = failed = YES;
return;
}
@@ -185,4 +201,34 @@
return !failed;
}
// Helper to create an NSError where necessary.
// The return value is irrelevant but the static analyzer complains if it's not a BOOL.
- (BOOL)fillError:(NSError **)error code:(SNTRuleTableError)code message:(NSString *)message {
if (!error) return NO;
NSMutableDictionary *d = [NSMutableDictionary dictionary];
switch (code) {
case SNTRuleTableErrorEmptyRuleArray:
d[NSLocalizedDescriptionKey] = @"Empty rule array";
break;
case SNTRuleTableErrorInvalidRule:
d[NSLocalizedDescriptionKey] = @"Rule array contained invalid entry";
break;
case SNTRuleTableErrorInsertOrReplaceFailed:
d[NSLocalizedDescriptionKey] = @"A database error occurred while inserting/replacing a rule";
break;
case SNTRuleTableErrorRemoveFailed:
d[NSLocalizedDescriptionKey] = @"A database error occurred while deleting a rule";
break;
case SNTRuleTableErrorMissingRequiredRule:
d[NSLocalizedDescriptionKey] = @"A required rule was requested to be deleted";
break;
}
if (message) d[NSLocalizedFailureReasonErrorKey] = message;
*error = [NSError errorWithDomain:@"com.google.santad.ruletable" code:code userInfo:d];
return YES;
}
@end

Binary file not shown.

View File

@@ -1,24 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG
EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy
bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP
VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv
h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE
ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ
EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC
DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7
qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD
VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g
K4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI
KwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n
ZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB
BQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY
/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/
zG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza
HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto
WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6
yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx
-----END CERTIFICATE-----

View File

@@ -1,34 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIF+DCCBOCgAwIBAgIQXvpnDpnkq4jg8gszhnt4TTANBgkqhkiG9w0BAQUFADCB
vjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug
YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE4MDYGA1UEAxMv
VmVyaVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBTR0MgQ0Ew
HhcNMTMxMTE0MDAwMDAwWhcNMTUxMTE0MjM1OTU5WjCCAQoxEzARBgsrBgEEAYI3
PAIBAxMCVVMxGzAZBgsrBgEEAYI3PAIBAhMKQ2FsaWZvcm5pYTEdMBsGA1UEDxMU
UHJpdmF0ZSBPcmdhbml6YXRpb24xETAPBgNVBAUTCEMwODA2NTkyMQswCQYDVQQG
EwJVUzEOMAwGA1UEERQFOTUwMTQxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNV
BAcUCUN1cGVydGlubzEYMBYGA1UECRQPMSBJbmZpbml0ZSBMb29wMRMwEQYDVQQK
FApBcHBsZSBJbmMuMRcwFQYDVQQLFA5JU0cgZm9yIEFrYW1haTEWMBQGA1UEAxQN
d3d3LmFwcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMRK
AzgN5IfMI0ioi1hYtFu5x6CGTH3/wpdlACJhfUtcAk7k1GaaMQKy/6f4scGI3tmj
K6qJjvEHf3cp6sliOPBrCLoby8/4r1T1+yBxanpe8arpChtj5KwD+owXQYwN7jqO
IB4nQGQin+D3aJiiqJfBc4XxhAEh+u29/RM+ux3l+QDMTzOm2hecl/z+wqtmRNeq
wCp1MHL+ejGaf60/SJUQM0QlQjqUcQBNbE73Lx+8UpZzsxjbEwOnstmSLkAjWQTE
FBVwPGqLF6ttR6ytACnUi2w6bFJsDv71nuIO56/SpzB/ZivddtZv4iYDg8ALBnFF
3AZI5jhbSid+vDzz5XECAwEAAaOCAaEwggGdMBgGA1UdEQQRMA+CDXd3dy5hcHBs
ZS5jb20wCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBaAwKAYDVR0lBCEwHwYIKwYB
BQUHAwEGCCsGAQUFBwMCBglghkgBhvhCBAEwRAYDVR0gBD0wOzA5BgtghkgBhvhF
AQcXBjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vY3Bz
MB0GA1UdDgQWBBT/EdiWLzrKfY9ayp23GIwElp5xiTAfBgNVHSMEGDAWgBROQ8gd
du83U3pP8lhvlPM44tW93zA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vRVZJbnRs
LWNybC52ZXJpc2lnbi5jb20vRVZJbnRsMjAwNi5jcmwwdgYIKwYBBQUHAQEEajBo
MCsGCCsGAQUFBzABhh9odHRwOi8vRVZJbnRsLW9jc3AudmVyaXNpZ24uY29tMDkG
CCsGAQUFBzAChi1odHRwOi8vRVZJbnRsLWFpYS52ZXJpc2lnbi5jb20vRVZJbnRs
MjAwNi5jZXIwDQYJKoZIhvcNAQEFBQADggEBAI4W8R0KFLvKzi20sd3cSaWHLqNb
K6AgFSShRtrrPIu2yNXzI+VjHZTuPhViqSGxfZJYI6c4e60zz5BkfuB0RbrJjQF9
iFjA/+bfS6+dIU95D9+zwp6u/NhSZAoqUH8LU5+GLGdCy2f9wU1Gyp47jgY9sDdF
tgkrVk1AnKU7Okd+n6ZOeUCTXgJgVm6UWFt5FXr80VMdEWv2U1JYxwNzITR+tJ//
u5BBaPG2p/6E3d9lmer8Ffq97CZ8poAeXsQXtuwNdwMCrbUGZKalaS71G/sSMrAR
xVVZakUhwnXXswczh701AK+TzB3dQNhFWooX/sn4+MiIkg+WPaxkd8AsCIM=
-----END CERTIFICATE-----

View File

@@ -53,7 +53,7 @@
event.loggedInUsers = @[ @"nobody" ];
event.currentSessions = @[ @"nobody@ttys000", @"nobody@console" ];
event.occurrenceDate = [NSDate date];
event.decision = EVENTSTATE_ALLOW_BINARY;
event.decision = SNTEventStateAllowBinary;
return event;
}

View File

@@ -94,7 +94,7 @@
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
SNTRule *rule = [[SNTRule alloc] init];
rule.state = RULESTATE_WHITELIST;
rule.state = SNTRuleStateWhitelist;
OCMStub([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
@@ -108,7 +108,7 @@
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
SNTRule *rule = [[SNTRule alloc] init];
rule.state = RULESTATE_BLACKLIST;
rule.state = SNTRuleStateBlacklist;
OCMStub([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
@@ -126,7 +126,7 @@
OCMStub([cert SHA256]).andReturn(@"a");
SNTRule *rule = [[SNTRule alloc] init];
rule.state = RULESTATE_WHITELIST;
rule.state = SNTRuleStateWhitelist;
OCMStub([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
@@ -144,7 +144,7 @@
OCMStub([cert SHA256]).andReturn(@"a");
SNTRule *rule = [[SNTRule alloc] init];
rule.state = RULESTATE_BLACKLIST;
rule.state = SNTRuleStateBlacklist;
OCMStub([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
@@ -157,12 +157,12 @@
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_MONITOR);
OCMExpect([self.mockConfigurator clientMode]).andReturn(SNTClientModeMonitor);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_ALLOW
forVnodeID:1234]);
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_LOCKDOWN);
OCMExpect([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_DENY
forVnodeID:1234]);
@@ -171,7 +171,7 @@
- (void)testOutOfScope {
OCMStub([self.mockFileInfo isMachO]).andReturn(NO);
OCMStub([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_LOCKDOWN);
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_ALLOW
forVnodeID:1234]);

View File

@@ -40,8 +40,8 @@
- (SNTRule *)_exampleBinaryRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"a";
r.state = RULESTATE_BLACKLIST;
r.type = RULETYPE_BINARY;
r.state = SNTRuleStateBlacklist;
r.type = SNTRuleTypeBinary;
r.customMsg = @"A rule";
return r;
}
@@ -49,8 +49,8 @@
- (SNTRule *)_exampleCertRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"b";
r.state = RULESTATE_WHITELIST;
r.type = RULETYPE_CERT;
r.state = SNTRuleStateWhitelist;
r.type = SNTRuleTypeCertificate;
return r;
}
@@ -58,72 +58,103 @@
NSUInteger ruleCount = self.sut.ruleCount;
NSUInteger binaryRuleCount = self.sut.binaryRuleCount;
[self.sut addRules:@[ [self _exampleBinaryRule] ] cleanSlate:NO];
NSError *error;
[self.sut addRules:@[ [self _exampleBinaryRule] ] cleanSlate:NO error:&error];
XCTAssertEqual(self.sut.ruleCount, ruleCount + 1);
XCTAssertEqual(self.sut.binaryRuleCount, binaryRuleCount + 1);
XCTAssertNil(error);
}
- (void)testAddRulesClean {
// Assert that insert without 'self' and launchd cert hashes fails
XCTAssertFalse([self.sut addRules:@[ [self _exampleBinaryRule] ] cleanSlate:YES]);
NSError *error;
XCTAssertFalse([self.sut addRules:@[ [self _exampleBinaryRule] ] cleanSlate:YES error:&error]);
XCTAssertEqual(error.code, SNTRuleTableErrorMissingRequiredRule);
// Now add a binary rule without clean slate
XCTAssertTrue([self.sut addRules:@[ [self _exampleBinaryRule] ] cleanSlate:NO]);
error = nil;
XCTAssertTrue([self.sut addRules:@[ [self _exampleBinaryRule] ] cleanSlate:NO error:&error]);
XCTAssertNil(error);
// Now add a cert rule + the required rules as a clean slate,
// assert that the binary rule was removed
SNTRule *r1 = [[SNTRule alloc] init];
r1.shasum = self.sut.launchdCertSHA;
r1.state = RULESTATE_WHITELIST;
r1.type = RULETYPE_CERT;
r1.state = SNTRuleStateWhitelist;
r1.type = SNTRuleTypeCertificate;
SNTRule *r2 = [[SNTRule alloc] init];
r2.shasum = self.sut.santadCertSHA;
r2.state = RULESTATE_WHITELIST;
r2.type = RULETYPE_CERT;
r2.state = SNTRuleStateWhitelist;
r2.type = SNTRuleTypeCertificate;
XCTAssertTrue(([self.sut addRules:@[ [self _exampleCertRule], r1, r2 ] cleanSlate:YES]));
error = nil;
XCTAssertTrue(([self.sut addRules:@[ [self _exampleCertRule], r1, r2 ]
cleanSlate:YES
error:&error]));
XCTAssertEqual([self.sut binaryRuleCount], 0);
XCTAssertNil(error);
}
- (void)testAddMultipleRules {
NSUInteger ruleCount = self.sut.ruleCount;
NSError *error;
[self.sut addRules:@[ [self _exampleBinaryRule],
[self _exampleCertRule],
[self _exampleBinaryRule] ]
cleanSlate:NO];
cleanSlate:NO
error:&error];
XCTAssertEqual(self.sut.ruleCount, ruleCount + 2);
XCTAssertNil(error);
}
- (void)testAddRulesEmptyArray {
XCTAssertFalse([self.sut addRules:@[] cleanSlate:YES]);
NSError *error;
XCTAssertFalse([self.sut addRules:@[] cleanSlate:YES error:&error]);
XCTAssertEqual(error.code, SNTRuleTableErrorEmptyRuleArray);
}
- (void)testAddRulesNilArray {
XCTAssertFalse([self.sut addRules:nil cleanSlate:YES]);
NSError *error;
XCTAssertFalse([self.sut addRules:nil cleanSlate:YES error:&error]);
XCTAssertEqual(error.code, SNTRuleTableErrorEmptyRuleArray);
}
- (void)testAddInvalidRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"a";
r.type = SNTRuleTypeCertificate;
NSError *error;
XCTAssertFalse([self.sut addRules:@[r] cleanSlate:NO error:&error]);
XCTAssertEqual(error.code, SNTRuleTableErrorInvalidRule);
}
- (void)testFetchBinaryRule {
[self.sut addRules:@[ [self _exampleBinaryRule], [self _exampleCertRule] ] cleanSlate:NO];
[self.sut addRules:@[ [self _exampleBinaryRule], [self _exampleCertRule] ]
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut binaryRuleForSHA256:@"a"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"a");
XCTAssertEqual(r.type, RULETYPE_BINARY);
XCTAssertEqual(r.type, SNTRuleTypeBinary);
r = [self.sut binaryRuleForSHA256:@"b"];
XCTAssertNil(r);
}
- (void)testFetchCertificateRule {
[self.sut addRules:@[ [self _exampleBinaryRule], [self _exampleCertRule] ] cleanSlate:NO];
[self.sut addRules:@[ [self _exampleBinaryRule], [self _exampleCertRule] ]
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut certificateRuleForSHA256:@"b"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"b");
XCTAssertEqual(r.type, RULETYPE_CERT);
XCTAssertEqual(r.type, SNTRuleTypeCertificate);
r = [self.sut certificateRuleForSHA256:@"a"];
XCTAssertNil(r);
@@ -136,8 +167,7 @@
FMDatabaseQueue *dbq = [[FMDatabaseQueue alloc] initWithPath:dbPath];
SNTRuleTable *sut = [[SNTRuleTable alloc] initWithDatabaseQueue:dbq];
[sut addRules:@[ [self _exampleBinaryRule] ] cleanSlate:NO];
[sut addRules:@[ [self _exampleBinaryRule] ] cleanSlate:NO error:nil];
XCTAssertGreaterThan(sut.ruleCount, 0);
[[NSFileManager defaultManager] removeItemAtPath:dbPath error:NULL];

View File

@@ -18,116 +18,102 @@
#import "MOLCodesignChecker.h"
#import "SNTXPCConnection.h"
@protocol XPCConnectionValidityRequest
- (void)isConnectionValidWithBlock:(void (^)(BOOL))block;
@end
@interface SNTXPCConnection (Testing)
//- (void)invokeAcceptedHandler;
//- (void)invokeRejectedHandler;
//- (void)invokeInvalidationHandler;
@end
@interface SNTXPCConnectionTest : XCTestCase
@property id mockListener;
@property id mockConnection;
@end
@implementation SNTXPCConnectionTest
- (void)setUp {
[super setUp];
self.mockListener = OCMClassMock([NSXPCListener class]);
OCMStub([self.mockListener alloc]).andReturn(self.mockListener);
self.mockConnection = OCMClassMock([NSXPCConnection class]);
OCMStub([self.mockConnection alloc]).andReturn(self.mockConnection);
}
- (void)tearDown {
[super tearDown];
}
- (void)testPlainInit {
XCTAssertThrows([[SNTXPCConnection alloc] init]);
}
- (void)testInitClient {
OCMExpect([self.mockConnection initWithMachServiceName:@"TestClient"
options:NSXPCConnectionPrivileged])
.andReturn(self.mockConnection);
id mockConnection = OCMClassMock([NSXPCConnection class]);
OCMStub([mockConnection alloc]).andReturn(mockConnection);
OCMExpect([mockConnection initWithMachServiceName:@"Client"
options:0]).andReturn(mockConnection);
SNTXPCConnection *sut = [[SNTXPCConnection alloc] initClientWithName:@"TestClient"
privileged:YES];
SNTXPCConnection *sut = [[SNTXPCConnection alloc] initClientWithName:@"Client" privileged:NO];
XCTAssertNotNil(sut);
OCMVerifyAll(self.mockConnection);
OCMExpect([mockConnection initWithMachServiceName:@"Client"
options:NSXPCConnectionPrivileged]).andReturn(
mockConnection);
sut = [[SNTXPCConnection alloc] initClientWithName:@"Client" privileged:YES];
XCTAssertNotNil(sut);
OCMVerifyAll(mockConnection);
}
- (void)testInitServer {
OCMExpect([self.mockListener initWithMachServiceName:@"TestServer"]).andReturn(self.mockListener);
id mockListener = OCMClassMock([NSXPCListener class]);
OCMStub([mockListener alloc]).andReturn(mockListener);
OCMExpect([mockListener initWithMachServiceName:@"TestServer"]).andReturn(mockListener);
SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"];
XCTAssertNotNil(sut);
OCMVerifyAll(self.mockListener);
OCMVerifyAll(mockListener);
}
- (void)testResume {
OCMExpect([self.mockListener initWithMachServiceName:OCMOCK_ANY]).andReturn(self.mockListener);
SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"];
[sut resume];
OCMVerify([(NSXPCListener *)self.mockListener setDelegate:sut]);
OCMVerify([(NSXPCListener *)self.mockListener resume]);
}
- (void)testServerNotValid {
OCMExpect([self.mockListener initWithMachServiceName:OCMOCK_ANY]).andReturn(self.mockListener);
SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"];
- (void)testConnectionRejection {
pid_t pid = [[NSProcessInfo processInfo] processIdentifier];
id mockCodesignChecker = OCMClassMock([MOLCodesignChecker class]);
OCMStub([mockCodesignChecker alloc]).andReturn(mockCodesignChecker);
OCMExpect([mockCodesignChecker initWithPID:0]).andReturn(mockCodesignChecker);
OCMExpect([mockCodesignChecker initWithPID:pid]).andReturn(mockCodesignChecker);
OCMExpect([mockCodesignChecker signingInformationMatches:OCMOCK_ANY]).andReturn(NO);
XCTAssertFalse([sut listener:self.mockListener shouldAcceptNewConnection:self.mockConnection]);
NSXPCListener *listener = [NSXPCListener anonymousListener];
[mockCodesignChecker stopMocking];
SNTXPCConnection *sutServer = [[SNTXPCConnection alloc] initServerWithListener:listener];
[sutServer resume];
__block XCTestExpectation *exp1 = [self expectationWithDescription:@"Client Invalidated"];
SNTXPCConnection *sutClient = [[SNTXPCConnection alloc] initClientWithListener:listener.endpoint];
sutClient.invalidationHandler = ^{
[exp1 fulfill];
exp1 = nil; // precent multiple fulfill violation
};
[sutClient resume];
[self waitForExpectationsWithTimeout:3.0 handler:NULL];
}
- (void)testServerValid {
OCMExpect([self.mockListener initWithMachServiceName:OCMOCK_ANY]).andReturn(self.mockListener);
SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"];
- (void)testConnectionAcceptance {
NSXPCListener *listener = [NSXPCListener anonymousListener];
id mockCodesignChecker = OCMClassMock([MOLCodesignChecker class]);
OCMStub([mockCodesignChecker alloc]).andReturn(mockCodesignChecker);
OCMExpect([mockCodesignChecker initWithPID:0]).andReturn(mockCodesignChecker);
OCMExpect([mockCodesignChecker signingInformationMatches:OCMOCK_ANY]).andReturn(YES);
XCTestExpectation *exp1 = [self expectationWithDescription:@"Server Accepted"];
SNTXPCConnection *sutServer = [[SNTXPCConnection alloc] initServerWithListener:listener];
sutServer.acceptedHandler = ^{
[exp1 fulfill];
};
[sutServer resume];
XCTAssertTrue([sut listener:self.mockListener shouldAcceptNewConnection:self.mockConnection]);
XCTestExpectation *exp2 = [self expectationWithDescription:@"Client Accepted"];
SNTXPCConnection *sutClient = [[SNTXPCConnection alloc] initClientWithListener:listener.endpoint];
sutClient.acceptedHandler = ^{
[exp2 fulfill];
};
[sutClient resume];
[mockCodesignChecker stopMocking];
[self waitForExpectationsWithTimeout:2.0 handler:NULL];
}
- (void)testServerInvalidateAllConnections {
OCMExpect([self.mockListener initWithMachServiceName:OCMOCK_ANY]).andReturn(self.mockListener);
SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"];
- (void)testConnectionInterruption {
NSXPCListener *listener = [NSXPCListener anonymousListener];
SNTXPCConnection *sutServer = [[SNTXPCConnection alloc] initServerWithListener:listener];
[sutServer resume];
int pid = [[NSProcessInfo processInfo] processIdentifier];
__block XCTestExpectation *exp1 = [self expectationWithDescription:@"Client Invalidated"];
SNTXPCConnection *sutClient = [[SNTXPCConnection alloc] initClientWithListener:listener.endpoint];
sutClient.invalidationHandler = ^{
[exp1 fulfill];
exp1 = nil; // prevent multiple fulfill violation
};
[sutClient resume];
NSMutableArray *connections = [NSMutableArray array];
for (int i = 0; i < 10; ++i) {
NSXPCConnection *fakeConn = OCMClassMock([NSXPCConnection class]);
OCMStub([fakeConn processIdentifier]).andReturn(pid);
OCMExpect([fakeConn invalidate]);
[connections addObject:fakeConn];
[sut listener:self.mockListener shouldAcceptNewConnection:fakeConn];
}
[sutServer invalidate];
sutServer = nil;
[sut invalidate];
for (NSXPCConnection *fakeConn in connections) {
OCMVerifyAll((OCMockObject *)fakeConn);
}
[self waitForExpectationsWithTimeout:1.0 handler:NULL];
}
@end