Initial commit

This commit is contained in:
Russell Hancox
2014-11-20 16:23:13 -05:00
commit 07988686ae
132 changed files with 12542 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="13F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTAboutWindowController">
<connections>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1578"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BnL-ZS-kXw">
<rect key="frame" x="199" y="140" width="83" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="UK2-2L-lPx"/>
<constraint firstAttribute="width" constant="79" id="lDf-D7-qlY"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Santa" id="VVj-gU-bzy">
<font key="font" size="34" name="HelveticaNeue-UltraLight"/>
<color key="textColor" red="0.1869618941" green="0.1869618941" blue="0.1869618941" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uh6-q0-RzL">
<rect key="frame" x="18" y="65" width="444" height="60"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" id="CcT-ul-1eA">
<font key="font" metaFont="system"/>
<string key="title">Santa is a binary whitelisting system for Mac OS X.
There are no user-configurable settings.</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Udo-BY-n7e">
<rect key="frame" x="196" y="21" width="88" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="76" id="2Xc-ax-2bV"/>
</constraints>
<buttonCell key="cell" type="push" title="OK" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="uSw-o1-lWW">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="orderOut:" target="-1" id="6oW-zI-zn5"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="BnL-ZS-kXw" firstAttribute="top" secondItem="se5-gp-TjO" secondAttribute="top" constant="20" symbolic="YES" id="Fj1-SG-mzF"/>
<constraint firstAttribute="bottom" secondItem="Udo-BY-n7e" secondAttribute="bottom" constant="28" id="bpF-hC-haN"/>
<constraint firstItem="BnL-ZS-kXw" firstAttribute="centerX" secondItem="Udo-BY-n7e" secondAttribute="centerX" constant="0.5" id="csK-2p-W94"/>
<constraint firstItem="BnL-ZS-kXw" firstAttribute="centerX" secondItem="se5-gp-TjO" secondAttribute="centerX" id="kez-S0-6Gg"/>
</constraints>
</view>
<connections>
<outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
</connections>
<point key="canvasLocation" x="323" y="317"/>
</window>
</objects>
</document>

View File

@@ -0,0 +1,65 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "santa-hat-icon-16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "santa-hat-icon-32.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "santa-hat-icon-32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "santa-hat-icon-64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "santa-hat-icon-128.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "santa-hat-icon-512.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "santa-hat-icon-512.png",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -0,0 +1,202 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14B25" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
<connections>
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
</connections>
</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">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="550" height="275"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="550" height="275"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="234" y="210" width="83" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="KoG-v6-GfK"/>
<constraint firstAttribute="width" constant="79" id="oS3-CE-1vv"/>
</constraints>
<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"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR">
<rect key="frame" x="23" y="168" width="504" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="500" id="q9O-xW-hnS"/>
</constraints>
<textFieldCell key="cell" 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"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.attributedCustomMessage" id="NH1-gV-Cor">
<dictionary key="options">
<string key="NSNullPlaceholder">The following application has been blocked from executing because its trustworthiness cannot be determined.</string>
</dictionary>
</binding>
</connections>
</textField>
<imageView horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="GYD-v8-fqH">
<rect key="frame" x="31" y="91" width="32" height="32"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSBonjour" id="jKM-qY-7mp"/>
<connections>
<binding destination="-2" name="value" keyPath="self.bundleIcon" id="X4L-aD-P21">
<dictionary key="options">
<bool key="NSConditionallySetsEnabled" value="NO"/>
</dictionary>
</binding>
</connections>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H">
<rect key="frame" x="111" y="126" width="34" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" 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="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ">
<rect key="frame" x="154" y="126" width="350" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="346" id="BYY-2q-Lmb"/>
</constraints>
<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="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.path" id="4Nh-Ue-aCb"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y">
<rect key="frame" x="96" y="99" width="46" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="SHA-1" 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 horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="155" y="99" width="88" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Binary SHA-1" id="X4W-9e-eIu">
<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"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.SHA1" id="KuE-WW-9av"/>
</connections>
</textField>
<textField verticalHuggingPriority="750" verticalCompressionResistancePriority="499" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5">
<rect key="frame" x="78" y="72" width="66" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" 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>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS">
<rect key="frame" x="322" y="75" width="10" height="10"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="c3b-iv-bWa"/>
<constraint firstAttribute="width" constant="10" id="fXl-na-Lwx"/>
</constraints>
<buttonCell key="cell" type="bevel" bezelStyle="regularSquare" image="NSFollowLinkFreestandingTemplate" 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"/>
</buttonCell>
<connections>
<action selector="showCertInfo:" target="-2" id="dB0-a3-X31"/>
<binding destination="-2" name="hidden" keyPath="self.binaryCert" id="xpJ-jl-aUN">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
</dictionary>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL">
<rect key="frame" x="220" y="23" width="110" height="25"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="HdL-6x-X4f"/>
<constraint firstAttribute="height" constant="22" id="YYm-GI-ojT"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="OK" bezelStyle="texturedRounded" alignment="center" refusesFirstResponder="YES" 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>
<connections>
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
</connections>
</button>
<textField horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w">
<rect key="frame" x="154" y="72" width="159" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="re0-7U-qcL"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" 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"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.binaryCert" id="eFt-oy-SXL">
<dictionary key="options">
<string key="NSNullPlaceholder">Not code-signed</string>
</dictionary>
</binding>
</connections>
</textField>
</subviews>
<constraints>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="113" id="3oY-g4-wHW"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" priority="800" constant="52" id="42Z-62-hKo"/>
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="25" id="4Hn-vu-fva"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="10" id="7Pr-bA-HgG"/>
<constraint firstItem="PXc-xv-A28" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="10" id="8LX-e8-bKv"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="d9e-Wv-Y5H" secondAttribute="top" id="94E-d6-Jrg"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="trailing" constant="13" id="A6N-gA-dt5"/>
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="98" id="CYj-Gm-XZp"/>
<constraint firstItem="PXc-xv-A28" firstAttribute="leading" secondItem="KEB-eH-x2Y" secondAttribute="trailing" constant="17" id="IGi-bx-nBP"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="80" id="O3p-RO-0ZJ"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="centerY" secondItem="cJf-k6-OxS" secondAttribute="centerY" constant="-1" id="R0U-iV-5Fx"/>
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="SHu-BF-01V"/>
<constraint firstAttribute="centerX" secondItem="BbV-3h-mmL" secondAttribute="centerX" id="UAx-Xk-9DE"/>
<constraint firstItem="KEB-eH-x2Y" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="top" id="YiW-o8-HZ2"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="C3G-wL-u7w" secondAttribute="bottom" constant="25" id="Zxm-Pa-Ryj"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="top" secondItem="C3G-wL-u7w" secondAttribute="top" id="adm-oT-FAf"/>
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="25" id="awW-Dh-Xl4"/>
<constraint firstItem="GYD-v8-fqH" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="31" id="btT-jY-NXw"/>
<constraint firstItem="GYD-v8-fqH" firstAttribute="centerY" secondItem="KEB-eH-x2Y" secondAttribute="centerY" id="cOS-EE-Mw8"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="14" id="ewf-Pg-nRK"/>
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="goV-ub-zwi"/>
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="25" id="mY6-FP-uEK"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" constant="25" id="pfg-1u-Yfj"/>
<constraint firstItem="cJf-k6-OxS" firstAttribute="leading" secondItem="C3G-wL-u7w" secondAttribute="trailing" constant="11" id="sMW-KK-A28"/>
</constraints>
</view>
</window>
</objects>
<resources>
<image name="NSBonjour" width="32" height="32"/>
<image name="NSFollowLinkFreestandingTemplate" width="14" height="14"/>
</resources>
</document>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.google.${PRODUCT_NAME:rfc1034identifier}GUI</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.7</string>
<key>CFBundleVersion</key>
<string>0.7</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Google, Inc.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@@ -0,0 +1,3 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif

View File

@@ -0,0 +1,17 @@
/// Copyright 2014 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.
@interface SNTAboutWindowController : NSWindowController
@end

View File

@@ -0,0 +1,23 @@
/// Copyright 2014 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 "SNTAboutWindowController.h"
@implementation SNTAboutWindowController
- (instancetype)init {
return [super initWithWindowNibName:@"AboutWindow"];
}
@end

View File

@@ -0,0 +1,17 @@
/// Copyright 2014 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.
/// Initiates and manages the connection to santad
@interface SNTAppDelegate : NSObject<NSApplicationDelegate>
@end

View File

@@ -0,0 +1,83 @@
/// Copyright 2014 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 "SNTAppDelegate.h"
#import "SNTAboutWindowController.h"
#import "SNTNotificationManager.h"
#import "SNTXPCConnection.h"
@interface SNTAppDelegate ()
@property SNTAboutWindowController *aboutWindowController;
@property SNTNotificationManager *notificationManager;
@property SNTXPCConnection *listener;
@end
@implementation SNTAppDelegate
#pragma mark App Delegate methods
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self setupMenu];
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
self.notificationManager = [[SNTNotificationManager alloc] init];
[self createConnection];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
[self.aboutWindowController showWindow:self];
return NO;
}
#pragma mark Connection handling
- (void)createConnection {
__weak __typeof(self) weakSelf = self;
self.listener =
[[SNTXPCConnection alloc] initClientWithName:[SNTXPCNotifierInterface serviceId]
options:NSXPCConnectionPrivileged];
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.listener.exportedObject = self.notificationManager;
self.listener.rejectedHandler = ^{
[weakSelf performSelectorInBackground:@selector(attemptReconnection)
withObject:nil];
};
self.listener.invalidationHandler = self.listener.rejectedHandler;
[self.listener resume];
}
- (void)attemptReconnection {
// TODO(rah): Make this smarter.
sleep(10);
[self createConnection];
}
#pragma mark Menu Management
- (void)setupMenu {
// Whilst the user will never see the menu, having one with the Copy and Select All options
// allows the shortcuts for these items to work, which is useful for being able to copy
// information from notifications. The mainMenu must have a nested menu for this to work properly.
NSMenu *mainMenu = [[NSMenu alloc] init];
NSMenu *editMenu = [[NSMenu alloc] init];
[editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
[editMenu addItemWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"];
NSMenuItem *editMenuItem = [[NSMenuItem alloc] init];
[editMenuItem setSubmenu:editMenu];
[mainMenu addItem:editMenuItem];
[NSApp setMainMenu:mainMenu];
}
@end

View File

@@ -0,0 +1,24 @@
/// Copyright 2014 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 NSPanel that can become key/main and can fade in/out.
@interface SNTMessageWindow : NSPanel
/// Fade the window in
- (IBAction)fadeIn:(id)sender;
/// Fade the window out
- (IBAction)fadeOut:(id)sender;
@end

View File

@@ -0,0 +1,53 @@
/// Copyright 2014 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 "SNTMessageWindow.h"
@implementation SNTMessageWindow
- (BOOL)canBecomeKeyWindow {
return YES;
}
- (BOOL)canBecomeMainWindow {
return YES;
}
- (IBAction)fadeIn:(id)sender {
[self setAlphaValue:0.f];
[self makeKeyAndOrderFront:sender];
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.15f];
[[NSAnimationContext currentContext] setCompletionHandler:^{
[NSApp activateIgnoringOtherApps:YES];
}];
[[self animator] setAlphaValue:1.f];
[NSAnimationContext endGrouping];
}
- (IBAction)fadeOut:(id)sender {
__weak __typeof(self) weakSelf = self;
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.15f];
[[NSAnimationContext currentContext] setCompletionHandler:^{
[weakSelf.windowController windowWillClose:nil];
[weakSelf orderOut:nil];
[weakSelf setAlphaValue:1.f];
}];
[[self animator] setAlphaValue:0.f];
[NSAnimationContext endGrouping];
}
@end

View File

@@ -0,0 +1,45 @@
/// Copyright 2014 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.
@class SNTNotificationMessage;
@protocol SNTMessageWindowControllerDelegate
- (void)windowDidClose;
@end
/// Controller for a single message window.
@interface SNTMessageWindowController : NSWindowController
- (instancetype)initWithEvent:(SNTNotificationMessage *)event;
- (IBAction)showWindow:(id)sender;
- (IBAction)closeWindow:(id)sender;
- (IBAction)showCertInfo:(id)sender;
/// The execution event that this window is for
@property SNTNotificationMessage *event;
/// The delegate to inform when the notification is dismissed
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
/// A 'friendly' string representing the certificate information
@property(readonly) IBOutlet NSString *binaryCert;
/// An optional message to display with this block.
@property(readonly) IBOutlet NSAttributedString *attributedCustomMessage;
/// If the binary is part of a bundle, this is the icon for that bundle
@property(readonly) IBOutlet NSImage *bundleIcon;
@end

View File

@@ -0,0 +1,118 @@
/// Copyright 2014 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 "SNTMessageWindowController.h"
#import <SecurityInterface/SFCertificatePanel.h>
#import "SNTBinaryInfo.h"
#import "SNTCertificate.h"
#import "SNTMessageWindow.h"
#import "SNTNotificationMessage.h"
@implementation SNTMessageWindowController
- (instancetype)initWithEvent:(SNTNotificationMessage *)event {
self = [super initWithWindowNibName:@"MessageWindow"];
if (self) {
_event = event;
[self.window setMovableByWindowBackground:NO];
[self.window setLevel:NSPopUpMenuWindowLevel];
[self.window center];
}
return self;
}
- (IBAction)showWindow:(id)sender {
[(SNTMessageWindow *)self.window fadeIn:sender];
}
- (IBAction)closeWindow:(id)sender {
[(SNTMessageWindow *)self.window fadeOut:sender];
}
- (void)windowWillClose:(NSNotification *)notification {
if (self.delegate) [self.delegate windowDidClose];
}
- (IBAction)showCertInfo:(id)sender {
// SFCertificatePanel expects an NSArray of SecCertificateRef's
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.certificates count]];
for (SNTCertificate *cert in self.event.certificates) {
[certArray addObject:(id)cert.certRef];
}
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
modalDelegate:nil
didEndSelector:nil
contextInfo:nil
certificates:certArray
showGroup:YES];
}
#pragma mark Generated properties
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if (! [key isEqualToString:@"event"]) {
return [NSSet setWithObject:@"event"];
} else {
return nil;
}
}
- (NSString *)binaryCert {
SNTCertificate *leafCert = self.event.leafCertificate;
if (leafCert.commonName && leafCert.orgName) {
return [NSString stringWithFormat:@"%@ - %@", leafCert.commonName, leafCert.orgName];
} else if (leafCert.commonName) {
return leafCert.commonName;
} else if (leafCert.orgName) {
return leafCert.orgName;
} else {
return nil;
}
}
- (NSAttributedString *)attributedCustomMessage {
if (self.event.customMessage) {
NSString *htmlHeader = @"<html><head><style>"
@"body {"
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
@" font-size: 13px;"
@" color: #666;"
@" text-align: center;"
@"}"
@"</style></head><body>";
NSString *htmlFooter = @"</body></html>";
NSString *fullHtml = [NSString stringWithFormat:@"%@%@%@", htmlHeader,
self.event.customMessage, htmlFooter];
NSData *htmlData = [fullHtml dataUsingEncoding:NSUTF8StringEncoding];
NSAttributedString *returnStr = [[NSAttributedString alloc] initWithHTML:htmlData
documentAttributes:NULL];
return returnStr;
} else {
return nil;
}
}
- (NSImage *)bundleIcon {
SNTBinaryInfo *bi = [[SNTBinaryInfo alloc] initWithPath:self.event.path];
if (!bi || !bi.bundle) return nil;
return [[NSWorkspace sharedWorkspace] iconForFile:bi.bundlePath];
}
@end

View File

@@ -0,0 +1,22 @@
/// Copyright 2014 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 "SNTMessageWindowController.h"
#import "SNTXPCNotifierInterface.h"
/// Keeps track of pending notifications and ensures only one is presented to the user at a time.
@interface SNTNotificationManager : NSObject<SNTMessageWindowControllerDelegate, SNTNotifierXPC>
@end

View File

@@ -0,0 +1,81 @@
/// Copyright 2014 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 "SNTNotificationManager.h"
#import "SNTNotificationMessage.h"
@interface SNTNotificationManager ()
/// The currently displayed notification
@property SNTMessageWindowController *currentWindowController;
/// The queue of pending notifications
@property(readonly) NSMutableArray *pendingNotifications;
@end
@implementation SNTNotificationManager
- (instancetype)init {
self = [super init];
if (self) {
_pendingNotifications = [[NSMutableArray alloc] init];
}
return self;
}
- (void)windowDidClose {
[self.pendingNotifications removeObject:self.currentWindowController];
self.currentWindowController = nil;
if ([self.pendingNotifications count]) {
self.currentWindowController = [self.pendingNotifications firstObject];
[self.currentWindowController showWindow:self];
} else {
[NSApp hide:self];
}
}
#pragma mark SNTNotifierXPC protocol methods
- (void)postBlockNotification:(SNTNotificationMessage *)event {
// See if this binary is already in the list of pending notifications.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"event.SHA1==%@", event.SHA1];
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
// 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
waitUntilDone:NO];
}
- (void)postBlockNotificationMainThread:(SNTNotificationMessage *)event {
// Create message window
SNTMessageWindowController *pendingMsg = [[SNTMessageWindowController alloc] initWithEvent:event];
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];
}
}
@end

25
Source/SantaGUI/main.m Normal file
View File

@@ -0,0 +1,25 @@
/// Copyright 2014 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 "SNTAppDelegate.h"
int main(int argc, const char *argv[]) {
@autoreleasepool {
NSApplication *app = [NSApplication sharedApplication];
SNTAppDelegate *delegate = [[SNTAppDelegate alloc] init];
[app setDelegate:delegate];
[app finishLaunching];
[app run];
}
}

View File

@@ -0,0 +1,73 @@
/// Copyright 2014 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.
/// SNTBinaryInfo represents a binary on disk, providing access to details about that binary such as
/// the SHA-1, the Info.plist and the Mach-O data.
@interface SNTBinaryInfo : NSObject
/// Designated initializer
- (instancetype)initWithPath:(NSString *)path;
/// Return SHA-1 hash of this binary
- (NSString *)SHA1;
/// Returns the type of Mach-O file:
/// Dynamic Library, Kernel Extension, Fat Binary, Thin Binary
- (NSString *)machoType;
/// Returns the architectures included in this binary (e.g. x86_64, ppc)
- (NSArray *)architectures;
/// Returns YES if this file is a Mach-O file
- (BOOL)isMachO;
/// Returns YES if this file contains multiple architectures
- (BOOL)isFat;
/// Returns YES if this file is an executable Mach-O file
- (BOOL)isExecutable;
/// Returns YES if this file is a dynamic library
- (BOOL)isDylib;
/// Returns YES if this file is a kernel extension
- (BOOL)isKext;
/// Returns YES if this file is a script (e.g. it begins #!)
- (BOOL)isScript;
/// Returns an NSBundle if this file is part of a bundle.
- (NSBundle *)bundle;
/// Returns the path to the bundle this file is a part of, if any.
- (NSString *)bundlePath;
/// Returns either the Info.plist in the bundle this file is part of, or an embedded plist if there
/// is one. In the odd case that a file has both an embedded Info.plist and is part of a bundle,
/// the Info.plist from the bundle will be returned.
- (NSDictionary *)infoPlist;
/// Returns the CFBundleIdentifier from this file's Info.plist
- (NSString *)bundleIdentifier;
/// Returns the CFBundleName from this file's Info.plist
- (NSString *)bundleName;
/// Returns the CFBundleVersion from this file's Info.plist
- (NSString *)bundleVersion;
/// Returns the CFBundleShortVersionString from this file's Info.plist
- (NSString *)bundleShortVersionString;
@end

View File

@@ -0,0 +1,291 @@
/// Copyright 2014 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 "SNTBinaryInfo.h"
#import <CommonCrypto/CommonDigest.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
@interface SNTBinaryInfo ()
@property NSString *path;
@property NSData *fileData;
@property NSBundle *bundleRef;
@property NSDictionary *infoDict;
@end
@implementation SNTBinaryInfo
- (instancetype)initWithPath:(NSString *)path {
self = [super init];
if (self) {
_path = path;
_fileData = [NSData dataWithContentsOfFile:path options:NSDataReadingMappedIfSafe error:nil];
if (!_fileData) return nil;
}
return self;
}
- (NSString *)SHA1 {
unsigned char sha1[CC_SHA1_DIGEST_LENGTH];
CC_SHA1([self.fileData bytes], (unsigned int)[self.fileData length], sha1);
// Convert the binary SHA into hex
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
[buf appendFormat:@"%02x", (unsigned char)sha1[i]];
}
return buf;
}
- (NSString *)machoType {
if ([self isDylib]) { return @"Dynamic Library"; }
if ([self isKext]) { return @"Kernel Extension"; }
if ([self isFat]) { return @"Fat Binary"; }
if ([self isMachO]) { return @"Thin Binary"; }
if ([self isScript]) { return @"Script"; }
return @"Unknown (not executable?)";
}
- (NSArray *)architectures {
if (![self isMachO]) return nil;
if ([self isFat]) {
NSMutableArray *ret = [[NSMutableArray alloc] init];
// Retrieve just the fat_header, if possible.
NSData *head = [self safeSubdataWithRange:NSMakeRange(0, sizeof(struct fat_header))];
if (!head) return nil;
struct fat_header *fat_header = (struct fat_header *)[head bytes];
// Get number of architectures in the binary
uint32_t narch = NSSwapBigIntToHost(fat_header->nfat_arch);
// Retrieve just the fat_arch's, make a mutable copy and if necessary swap the bytes
NSData *archs = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
sizeof(struct fat_arch) * narch)];
if (!archs) return nil;
struct fat_arch *fat_archs = (struct fat_arch *)[archs bytes];
// For each arch, get the name of it's architecture
for (int i = 0; i < narch; ++i) {
[ret addObject:[self nameForCPUType:NSSwapBigIntToHost(fat_archs[i].cputype)]];
}
return ret;
} else {
struct mach_header *hdr = [self firstMachHeader];
return @[ [self nameForCPUType:hdr->cputype] ];
}
return nil;
}
- (BOOL)isDylib {
struct mach_header *mach_header = [self firstMachHeader];
if (!mach_header) return NO;
if (mach_header->filetype == MH_DYLIB ||
mach_header->filetype == MH_FVMLIB) {
return YES;
}
return NO;
}
- (BOOL)isKext {
struct mach_header *mach_header = [self firstMachHeader];
if (!mach_header) return NO;
if (mach_header->filetype == MH_KEXT_BUNDLE) {
return YES;
}
return NO;
}
- (BOOL)isMachO {
return ([self.fileData length] >= 160 &&
([self isMachHeader:(struct mach_header *)[self.fileData bytes]] || [self isFat]));
}
- (BOOL)isFat {
return ([self isFatHeader:(struct fat_header *)[self.fileData bytes]]);
}
- (BOOL)isScript {
if ([self.fileData length] < 1) return NO;
char magic[2];
[self.fileData getBytes:&magic length:2];
return (strncmp("#!", magic, 2) == 0);
}
- (BOOL)isExecutable {
struct mach_header *mach_header = [self firstMachHeader];
if (!mach_header) return NO;
if (mach_header->filetype == MH_OBJECT ||
mach_header->filetype == MH_EXECUTE ||
mach_header->filetype == MH_PRELOAD) {
return YES;
}
return NO;
}
# pragma mark Bundle Information
/**
* Try and determine the bundle that the represented executable is contained within, if any.
*
* Rationale: An NSBundle has a method executablePath for discovering the main binary within a
* bundle but provides no way to get an NSBundle object when only the executablePath is known. Also,
* a bundle can contain multiple binaries within the MacOS folder and we want any of these to count
* as being part of the bundle.
*
* This method relies on executable bundles being laid out as follows:
*
*@code
* Bundle.app/
* Contents/
* MacOS/
* executable
*@endcode
*
* If @c self.path is the full path to @c executable above, this method would return an
* NSBundle reference for Bundle.app.
*/
- (NSBundle *)bundle {
if (self.bundleRef) return self.bundleRef;
NSArray *pathComponents = [self.path pathComponents];
// Check that the full path is at least 4-levels deep:
// e.g: /Calendar.app/Contents/MacOS/Calendar
if ([pathComponents count] < 4) return nil;
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 3)];
self.bundleRef = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
// Clear the bundle if it doesn't have a bundle ID
if (![self.bundleRef objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = nil;
return self.bundleRef;
}
- (NSString *)bundlePath {
return [self.bundle bundlePath];
}
- (NSDictionary *)infoPlist {
if (self.infoDict) return self.infoDict;
if ([self bundle]) {
self.infoDict = [[self bundle] infoDictionary];
return self.infoDict;
}
NSURL *url = [NSURL fileURLWithPath:self.path isDirectory:NO];
self.infoDict =
(__bridge_transfer NSDictionary*)CFBundleCopyInfoDictionaryForURL((__bridge CFURLRef) url);
return self.infoDict;
}
- (NSString *)bundleIdentifier {
return [[self infoPlist] objectForKey:@"CFBundleIdentifier"];
}
- (NSString *)bundleName {
return [[self infoPlist] objectForKey:@"CFBundleName"];
}
- (NSString *)bundleVersion {
return [[self infoPlist] objectForKey:@"CFBundleVersion"];
}
- (NSString *)bundleShortVersionString {
return [[self infoPlist] objectForKey:@"CFBundleShortVersionString"];
}
# pragma mark Internal Methods
/// Look through the file for the first mach_header. If the file is thin, this will be the
/// header at the beginning of the file. If the file is fat, it will be the first
/// architecture-specific header.
- (struct mach_header *)firstMachHeader {
if (![self isMachO]) return NULL;
struct mach_header *mach_header = (struct mach_header *)[self.fileData bytes];
struct fat_header *fat_header = (struct fat_header *)[self.fileData bytes];
if ([self isFatHeader:fat_header]) {
// Get the bytes for the fat_arch
NSData *archHdr = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
sizeof(struct fat_arch))];
if (!archHdr) return nil;
struct fat_arch *fat_arch = (struct fat_arch *)[archHdr bytes];
// Get bytes for first mach_header
NSData *machHdr = [self safeSubdataWithRange:NSMakeRange(NSSwapBigIntToHost(fat_arch->offset),
sizeof(struct mach_header))];
if (!machHdr) return nil;
mach_header = (struct mach_header *)[machHdr bytes];
}
if ([self isMachHeader:mach_header]) {
return mach_header;
}
return NULL;
}
- (BOOL)isMachHeader:(struct mach_header *)header {
return (header->magic == MH_MAGIC || header->magic == MH_MAGIC_64 ||
header->magic == MH_CIGAM || header->magic == MH_CIGAM_64);
}
- (BOOL)isFatHeader:(struct fat_header *)header {
return (header->magic == FAT_MAGIC || header->magic == FAT_CIGAM);
}
/// Wrap subdataWithRange: in a @try/@catch, returning nil on exception.
/// Useful for when the range is beyond the end of the file.
- (NSData *)safeSubdataWithRange:(NSRange)range {
@try {
return [self.fileData subdataWithRange:range];
}
@catch (NSException *exception) {
return nil;
}
}
- (NSString *)nameForCPUType:(cpu_type_t)cpuType {
switch (cpuType) {
case CPU_TYPE_X86:
return @"i386";
case CPU_TYPE_X86_64:
return @"x86-64";
case CPU_TYPE_POWERPC:
return @"ppc";
case CPU_TYPE_POWERPC64:
return @"ppc64";
default:
return @"unknown";
}
return nil;
}
@end

View File

@@ -0,0 +1,67 @@
/// Copyright 2014 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.
/// SNTCertificate wraps a @c SecCertificateRef to provide Objective-C accessors to
/// commonly used certificate data. Accessors cache data for repeated access.
@interface SNTCertificate : NSObject<NSSecureCoding>
/// Initialize a SNTCertificate object with a valid SecCertificateRef. Designated initializer.
- (instancetype)initWithSecCertificateRef:(SecCertificateRef)certRef;
/// Initialize a SNTCertificate object with certificate data in DER format.
/// Returns nil if |certData| is invalid.
- (instancetype)initWithCertificateDataDER:(NSData *)certData;
/// Initialize a SNTCertificate object with certificate data in PEM format.
/// If multiple PEM certificates exist within the string, the first is used.
/// Returns nil if |certData| is invalid.
- (instancetype)initWithCertificateDataPEM:(NSString *)certData;
/// Returns an array of SNTCertificate's for all of the certificates in |pemData|.
+ (NSArray *)certificatesFromPEM:(NSString *)pemData;
/// Access the underlying certificate ref.
@property(readonly) SecCertificateRef certRef;
/// SHA-1 hash of the certificate data.
@property(readonly) NSString *SHA1;
/// Certificate data.
@property(readonly) NSData *certData;
/// Common Name e.g: "Software Signing"
@property(readonly) NSString *commonName;
/// Country Name e.g: "US"
@property(readonly) NSString *countryName;
/// Organizational Name e.g: "Apple Inc."
@property(readonly) NSString *orgName;
/// Organizational Unit Name e.g: "Apple Software"
@property(readonly) NSString *orgUnit;
/// Issuer details, same fields as above.
@property(readonly) NSString *issuerCommonName;
@property(readonly) NSString *issuerCountryName;
@property(readonly) NSString *issuerOrgName;
@property(readonly) NSString *issuerOrgUnit;
/// Validity Not Before
@property(readonly) NSDate *validFrom;
/// Validity Not After
@property(readonly) NSDate *validUntil;
@end

View File

@@ -0,0 +1,336 @@
/// Copyright 2014 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 "SNTCertificate.h"
#import <CommonCrypto/CommonDigest.h>
#import <Security/Security.h>
@interface SNTCertificate ()
/// A container for cached property values
@property NSMutableDictionary *memoizedData;
@end
@implementation SNTCertificate
static NSString *const kCertDataKey = @"certData";
#pragma mark Init/Dealloc
- (instancetype)initWithSecCertificateRef:(SecCertificateRef)certRef {
self = [super init];
if (self) {
_certRef = certRef;
CFRetain(_certRef);
}
return self;
}
- (instancetype)initWithCertificateDataDER:(NSData *)certData {
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
if (cert) {
// Despite the header file claiming that SecCertificateCreateWithData will return NULL if
// |certData| doesn't contain a valid DER-encoded X509 cert, this isn't always true.
// radar://problem/16124651
// To workaround, check that the certificate serial number can be retrieved.
NSData *ser = CFBridgingRelease(SecCertificateCopySerialNumber(cert, NULL));
if (ser) {
self = [self initWithSecCertificateRef:cert];
} else {
self = nil;
}
CFRelease(cert); // was retained in initWithSecCertificateRef
} else {
self = nil;
}
return self;
}
- (instancetype)initWithCertificateDataPEM:(NSString *)certData {
// Find the PEM and extract the base64-encoded DER data from within
NSScanner *scanner = [NSScanner scannerWithString:certData];
NSString *base64der;
// Locate and parse DER data into |base64der|
[scanner scanUpToString:@"-----BEGIN CERTIFICATE-----" intoString:NULL];
if (!([scanner scanString:@"-----BEGIN CERTIFICATE-----" intoString:NULL] &&
[scanner scanUpToString:@"-----END CERTIFICATE-----" intoString:&base64der] &&
[scanner scanString:@"-----END CERTIFICATE-----" intoString:NULL])) {
return nil;
}
// base64-decode the DER
SecTransformRef transform = SecDecodeTransformCreate(kSecBase64Encoding, NULL);
if (!transform) return nil;
NSData *input = [base64der dataUsingEncoding:NSUTF8StringEncoding];
NSData *output = nil;
if (SecTransformSetAttribute(transform,
kSecTransformInputAttributeName,
(__bridge CFDataRef)input,
NULL)) {
output = CFBridgingRelease(SecTransformExecute(transform, NULL));
}
if (transform) CFRelease(transform);
return [self initWithCertificateDataDER:output];
}
+ (NSArray *)certificatesFromPEM:(NSString *)pemData {
NSScanner *scanner = [NSScanner scannerWithString:pemData];
NSMutableArray *certs = [[NSMutableArray alloc] init];
while (YES) {
NSString *curCert;
[scanner scanUpToString:@"-----BEGIN CERTIFICATE-----" intoString:NULL];
[scanner scanUpToString:@"-----END CERTIFICATE-----" intoString:&curCert];
// If there was no data, break.
if (!curCert) break;
curCert = [curCert stringByAppendingString:@"-----END CERTIFICATE-----"];
SNTCertificate *cert = [[SNTCertificate alloc] initWithCertificateDataPEM:curCert];
// If the data couldn't be turned into a valid SNTCertificate, continue.
if (!cert) continue;
[certs addObject:cert];
}
return certs;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (void)dealloc {
if (_certRef) CFRelease(_certRef);
}
#pragma mark Equality & description
- (BOOL)isEqual:(SNTCertificate *)other {
if (self == other) return YES;
if (![other isKindOfClass:[SNTCertificate class]]) return NO;
return [self.certData isEqual:other.certData];
}
- (NSUInteger)hash {
return [self.certData hash];
}
- (NSString *)description {
return [NSString stringWithFormat:@"/O=%@/OU=%@/CN=%@",
self.orgName,
self.orgUnit,
self.commonName];
}
#pragma mark NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.certData forKey:kCertDataKey];
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
NSData *certData = [decoder decodeObjectOfClass:[NSData class] forKey:kCertDataKey];
if ([certData length] == 0) return nil;
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
self = [self initWithSecCertificateRef:cert];
if (cert) CFRelease(cert);
return self;
}
#pragma mark Private Accessors
/// For a given selector, caches the value that selector would return on subsequent invocations,
/// using the provided block to get the value on the first invocation.
/// Assumes the selector's value will never change.
- (id)memoizedSelector:(SEL)selector forBlock:(id (^)(void))block {
NSString *selName = NSStringFromSelector(selector);
if (!self.memoizedData) {
self.memoizedData = [NSMutableDictionary dictionary];
}
if (!self.memoizedData[selName]) {
id val = block();
if (val) {
self.memoizedData[selName] = val;
} else {
self.memoizedData[selName] = [NSNull null];
}
}
// Return the value if there is one, or nil if the value is NSNull
return self.memoizedData[selName] != [NSNull null] ? self.memoizedData[selName] : nil;
}
- (NSDictionary *)allCertificateValues {
return [self memoizedSelector:_cmd forBlock:^id{
return CFBridgingRelease(SecCertificateCopyValues(self.certRef, NULL, NULL));
}];
}
- (NSDictionary *)x509SubjectName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self allCertificateValues][(__bridge NSString *)kSecOIDX509V1SubjectName];
}];
}
- (NSDictionary *)x509IssuerName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self allCertificateValues][(__bridge NSString *)kSecOIDX509V1IssuerName];
}];
}
/// Retrieve the value with the specified label from the X509 dictionary provided
/// @param desiredLabel The label you want, e.g: kSecOIDOrganizationName.
/// @param dict The dictionary to look in (Subject or Issuer)
/// @returns An @c NSString, the value for the specified label.
- (NSString *)x509ValueForLabel:(NSString *)desiredLabel fromDictionary:(NSDictionary *)dict {
@try {
NSArray *valArray = dict[(__bridge NSString *)kSecPropertyKeyValue];
for (NSDictionary *curCertVal in valArray) {
NSString *valueLabel = curCertVal[(__bridge NSString *)kSecPropertyKeyLabel];
if ([valueLabel isEqual:desiredLabel]) {
return curCertVal[(__bridge NSString *)kSecPropertyKeyValue];
}
}
return nil;
}
@catch (NSException *exception) {
return nil;
}
}
/// Retrieve the specified date from the certificate's values and convert from a reference date
/// to an NSDate object.
/// @param key The identifier for the date: @c kSecOIDX509V1ValiditityNot{Before,After}
/// @return An @c NSDate representing the date and time the certificate is valid from or expires.
- (NSDate *)dateForX509Key:(NSString *)key {
NSDictionary *curCertVal = [self allCertificateValues][key];
NSNumber *value = curCertVal[(__bridge NSString *)kSecPropertyKeyValue];
NSTimeInterval interval = [value doubleValue];
if (interval) {
return [NSDate dateWithTimeIntervalSinceReferenceDate:interval];
}
return nil;
}
#pragma mark Public Accessors
- (NSString *)SHA1 {
return [self memoizedSelector:_cmd forBlock:^id{
NSMutableData *SHA1Buffer = [[NSMutableData alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH];
CC_SHA1([self.certData bytes], (CC_LONG)[self.certData length], [SHA1Buffer mutableBytes]);
const unsigned char *bytes = (const unsigned char *)[SHA1Buffer bytes];
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8],
bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16],
bytes[17], bytes[18], bytes[19]];
}];
}
- (NSData *)certData {
return CFBridgingRelease(SecCertificateCopyData(self.certRef));
}
- (NSString *)commonName {
return [self memoizedSelector:_cmd forBlock:^id{
CFStringRef commonName = NULL;
SecCertificateCopyCommonName(self.certRef, &commonName);
return CFBridgingRelease(commonName);
}];
}
- (NSString *)countryName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCountryName
fromDictionary:[self x509SubjectName]];
}];
}
- (NSString *)orgName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationName
fromDictionary:[self x509SubjectName]];
}];
}
- (NSString *)orgUnit {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationalUnitName
fromDictionary:[self x509SubjectName]];
}];
}
- (NSDate *)validFrom {
return [self memoizedSelector:_cmd forBlock:^id{
return [self dateForX509Key:(__bridge NSString *)kSecOIDX509V1ValidityNotBefore];
}];
}
- (NSDate *)validUntil {
return [self memoizedSelector:_cmd forBlock:^id{
return [self dateForX509Key:(__bridge NSString *)kSecOIDX509V1ValidityNotAfter];
}];
}
- (NSString *)issuerCommonName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCommonName
fromDictionary:[self x509IssuerName]];
}];
}
- (NSString *)issuerCountryName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCountryName
fromDictionary:[self x509IssuerName]];
}];
}
- (NSString *)issuerOrgName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationName
fromDictionary:[self x509IssuerName]];
}];
}
- (NSString *)issuerOrgUnit {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationalUnitName
fromDictionary:[self x509IssuerName]];
}];
}
@end

View File

@@ -0,0 +1,55 @@
/// Copyright 2014 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.
@class SNTCertificate;
/// SNTCodesignChecker validates a binary (either on-disk or in memory) has been signed
/// and if so allows for pulling out the certificates that were used to sign it.
@interface SNTCodesignChecker : NSObject
/// The SecStaticCodeRef that this SNTCodesignChecker is working around
@property(readonly) SecStaticCodeRef codeRef;
/// Returns a dictionary of raw signing information
@property(readonly) NSDictionary *signingInformation;
/// Returns an array of @c SNTCertificate objects representing the chain that signed this binary.
@property(readonly) NSArray *certificates;
/// Returns the leaf certificate that this binary was signed with
@property(readonly) SNTCertificate *leafCertificate;
/// Returns the on-disk path of this binary.
@property(readonly) NSString *binaryPath;
/// Initialize an @c SNTCodesignChecker with a SecStaticCodeRef
/// Designated initializer.
/// Takes ownership of @c codeRef.
- (instancetype)initWithSecStaticCodeRef:(SecStaticCodeRef)codeRef;
/// Initialize an @c SNTCodesignChecker with a binary on disk.
/// Returns nil if @c binaryPath does not exist, is not a binary or is not codesigned.
- (instancetype)initWithBinaryPath:(NSString *)binaryPath;
/// Initialize an @c SNTCodesignChecker with the PID of a running process.
- (instancetype)initWithPID:(pid_t)PID;
/// Initialize an @c SNTCodesignChecker for the currently-running process.
- (instancetype)initWithSelf;
/// Returns true if the binary represented by @c otherChecker has signing information that matches
/// this binary.
- (BOOL)signingInformationMatches:(SNTCodesignChecker *)otherChecker;
@end

View File

@@ -0,0 +1,190 @@
/// Copyright 2014 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 "SNTCodesignChecker.h"
#import <Security/Security.h>
#import "SNTCertificate.h"
// kStaticSigningFlags are the flags used when validating signatures on disk.
//
// Don't validate resources but do validate nested code. Ignoring resources _dramatically_ speeds
// up validation (see below) but does mean images, plists, etc will not be checked and modifying
// these will not be considered invalid. To ensure any code inside the binary is still checked,
// we check nested code.
//
// Timings with different flags:
// Checking Xcode 5.1.1 bundle:
// kSecCSDefaultFlags: 3.895s
// kSecCSDoNotValidateResources: 0.013s
// kSecCSDoNotValidateResources | kSecCSCheckNestedCode: 0.013s
//
// Checking Google Chrome 36.0.1985.143 bundle:
// kSecCSDefaultFlags: 0.529s
// kSecCSDoNotValidateResources: 0.032s
// kSecCSDoNotValidateResources | kSecCSCheckNestedCode: 0.033s
//
const SecCSFlags kStaticSigningFlags = kSecCSDoNotValidateResources | kSecCSCheckNestedCode;
// kSigningFlags are the flags used when validating signatures for running binaries.
//
// No special flags needed currently.
const SecCSFlags kSigningFlags = kSecCSDefaultFlags;
@implementation SNTCodesignChecker {
/// Array of @c SNTCertificate's representing the chain of certs this executable was signed with.
NSMutableArray *_certificates;
}
#pragma mark Init/dealloc
- (instancetype)initWithSecStaticCodeRef:(SecStaticCodeRef)codeRef {
self = [super init];
if (self) {
// First check the signing is valid
if (CFGetTypeID(codeRef) == SecStaticCodeGetTypeID()) {
if (SecStaticCodeCheckValidity(codeRef, kStaticSigningFlags, NULL) != errSecSuccess) {
return nil;
}
} else if (CFGetTypeID(codeRef) == SecCodeGetTypeID()) {
if (SecCodeCheckValidity((SecCodeRef)codeRef, kSigningFlags, NULL) != errSecSuccess) {
return nil;
}
} else {
return nil;
}
// Get CFDictionary of signing information for binary
OSStatus status = errSecSuccess;
CFDictionaryRef signingDict = NULL;
status = SecCodeCopySigningInformation(codeRef, kSecCSSigningInformation, &signingDict);
_signingInformation = CFBridgingRelease(signingDict);
if (status != errSecSuccess) return nil;
// Get array of certificates.
NSArray *certs = _signingInformation[(id)kSecCodeInfoCertificates];
if (!certs) return nil;
// Wrap SecCertificateRef objects in SNTCertificate and put in a new NSArray
_certificates = [[NSMutableArray alloc] initWithCapacity:certs.count];
for (int i = 0; i < certs.count; ++i) {
SecCertificateRef certRef = (__bridge SecCertificateRef)certs[i];
SNTCertificate *newCert = [[SNTCertificate alloc] initWithSecCertificateRef:certRef];
[_certificates addObject:newCert];
}
_codeRef = codeRef;
CFRetain(_codeRef);
}
return self;
}
- (instancetype)initWithBinaryPath:(NSString *)binaryPath {
SecStaticCodeRef codeRef = NULL;
// Get SecStaticCodeRef for binary
if (SecStaticCodeCreateWithPath((__bridge CFURLRef)[NSURL fileURLWithPath:binaryPath
isDirectory:NO],
kSecCSDefaultFlags,
&codeRef) == errSecSuccess) {
self = [self initWithSecStaticCodeRef:codeRef];
} else {
self = nil;
}
if (codeRef) CFRelease(codeRef);
return self;
}
- (instancetype)initWithPID:(pid_t)PID {
SecCodeRef codeRef = NULL;
NSDictionary *attributes = @{(__bridge NSString *)kSecGuestAttributePid: @(PID)};
if (SecCodeCopyGuestWithAttributes(NULL,
(__bridge CFDictionaryRef)attributes,
kSecCSDefaultFlags,
&codeRef) == errSecSuccess) {
self = [self initWithSecStaticCodeRef:codeRef];
} else {
self = nil;
}
if (codeRef) CFRelease(codeRef);
return self;
}
- (instancetype)initWithSelf {
SecCodeRef codeSelf = NULL;
if (SecCodeCopySelf(kSecCSDefaultFlags, &codeSelf) == errSecSuccess) {
self = [self initWithSecStaticCodeRef:codeSelf];
} else {
self = nil;
}
if (codeSelf) CFRelease(codeSelf);
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (void)dealloc {
if (_codeRef) {
CFRelease(_codeRef);
_codeRef = NULL;
}
}
#pragma mark Description
- (NSString *)description {
NSString *binarySource;
if (CFGetTypeID(self.codeRef) == SecStaticCodeGetTypeID()) {
binarySource = @"On-disk";
} else {
binarySource = @"In-memory";
}
return [NSString stringWithFormat:@"%@ binary, signed by %@, located at: %@",
binarySource,
self.leafCertificate.orgName,
self.binaryPath];
}
#pragma mark Public accessors
- (SNTCertificate *)leafCertificate {
return [self.certificates firstObject];
}
- (NSString *)binaryPath {
CFURLRef path;
OSStatus status = SecCodeCopyPath(_codeRef, kSecCSDefaultFlags, &path);
NSURL *pathURL = CFBridgingRelease(path);
if (status != errSecSuccess) return nil;
return [pathURL path];
}
- (BOOL)signingInformationMatches:(SNTCodesignChecker *)otherChecker {
return [self.certificates isEqual:otherChecker.certificates];
}
@end

View File

@@ -0,0 +1,213 @@
#import "SNTCodesignChecker.h"
#import <Security/Security.h>
#import "SNTCertificate.h"
// kStaticSigningFlags are the flags used when validating signatures on disk.
//
// Don't validate resources but do validate nested code. Ignoring resources _dramatically_ speeds
// up validation (see below) but does mean images, plists, etc will not be checked and modifying
// these will not be considered invalid. To ensure any code inside the binary is still checked,
// we check nested code.
//
// Timings with different flags:
// Checking Xcode 5.1.1 bundle:
// kSecCSDefaultFlags: 3.895s
// kSecCSDoNotValidateResources: 0.013s
// kSecCSDoNotValidateResources | kSecCSCheckNestedCode: 0.013s
//
// Checking Google Chrome 36.0.1985.143 bundle:
// kSecCSDefaultFlags: 0.529s
// kSecCSDoNotValidateResources: 0.032s
// kSecCSDoNotValidateResources | kSecCSCheckNestedCode: 0.033s
//
const SecCSFlags kStaticSigningFlags = kSecCSDoNotValidateResources | kSecCSCheckNestedCode;
// kSigningFlags are the flags used when validating signatures for running binaries.
//
// No special flags needed currently.
const SecCSFlags kSigningFlags = kSecCSDefaultFlags;
@implementation SNTCodesignChecker
#pragma mark Init/dealloc
- (instancetype)initWithSecStaticCodeRef:(SecStaticCodeRef)codeRef {
self = [super init];
if (self) {
_codeRef = codeRef;
CFRetain(_codeRef);
}
return self;
}
- (instancetype)initWithBinaryPath:(NSString *)binaryPath {
SecStaticCodeRef codeRef = NULL;
// Get SecStaticCodeRef for binary
if (SecStaticCodeCreateWithPath((__bridge CFURLRef)[NSURL fileURLWithPath:binaryPath
isDirectory:NO],
kSecCSDefaultFlags,
&codeRef) == errSecSuccess) {
self = [self initWithSecStaticCodeRef:codeRef];
} else {
self = nil;
}
if (codeRef) CFRelease(codeRef);
return self;
}
- (instancetype)initWithPID:(pid_t)PID {
SecCodeRef codeRef = NULL;
NSDictionary *attributes = @{(__bridge NSString *)kSecGuestAttributePid: @(PID)};
if (SecCodeCopyGuestWithAttributes(NULL,
(__bridge CFDictionaryRef)attributes,
kSecCSDefaultFlags,
&codeRef) == errSecSuccess) {
self = [self initWithSecStaticCodeRef:codeRef];
} else {
self = nil;
}
if (codeRef) CFRelease(codeRef);
return self;
}
- (instancetype)initWithSelf {
SecCodeRef codeSelf = NULL;
if (SecCodeCopySelf(kSecCSDefaultFlags, &codeSelf) == errSecSuccess) {
self = [self initWithSecStaticCodeRef:codeSelf];
} else {
self = nil;
}
if (codeSelf) CFRelease(codeSelf);
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (void)dealloc {
if (_codeRef) CFRelease(_codeRef);
}
#pragma mark Validate
- (OSStatus)validate {
return [self validateWithRequirement:NULL];
}
- (OSStatus)validateAppleAnchor {
SecRequirementRef req = NULL;
SecRequirementCreateWithString(CFSTR("anchor apple"), kSecCSDefaultFlags, &req);
return [self validateWithRequirement:req];
}
- (OSStatus)validateAppleAnchorGeneric {
SecRequirementRef req = NULL;
SecRequirementCreateWithString(CFSTR("anchor apple generic"), kSecCSDefaultFlags, &req);
return [self validateWithRequirement:req];
}
- (OSStatus)validateWithRequirement:(SecRequirementRef)requirement {
// Validate the binary and save the return code.
if (CFGetTypeID(self.codeRef) == SecStaticCodeGetTypeID()) {
return SecStaticCodeCheckValidity(self.codeRef, kStaticSigningFlags, requirement);
} else if (CFGetTypeID(self.codeRef) == SecCodeGetTypeID()) {
return SecCodeCheckValidity((SecCodeRef)self.codeRef, kSigningFlags, requirement);
} else {
return errSecCSSignatureNotVerifiable;
}
}
#pragma mark Description
- (NSString *)description {
NSString *retStr;
if (CFGetTypeID(self.codeRef) == SecStaticCodeGetTypeID()) {
retStr = @"On-disk binary, ";
} else {
retStr = @"In-memory binary, ";
}
if ([self validate] == errSecSuccess) {
[retStr appendFormat:@"signed by %@, ", self.leafCertificate.orgName];
} else {
[retStr appendFormat:@"unsigned, "];
}
[retStr appendFormat:@"located at: %@", self.binaryPath];
return retStr;
}
#pragma mark Public accessors
- (NSDictionary *)signingInformation {
static NSDictionary *signingInformation = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Get dictionary of signing information for binary
CFDictionaryRef signingDict = NULL;
SecCodeCopySigningInformation(self.codeRef, kSecCSSigningInformation, &signingDict);
signingInformation = CFBridgingRelease(signingDict);
});
return signingInformation;
}
- (NSArray *)certificates {
static NSArray *certificates = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Get array of certificates, wrap each one in a SNTCertificate and store in a new array.
NSArray *certs = self.signingInformation[(__bridge NSString *)kSecCodeInfoCertificates];
NSMutableArray *tempCerts = [[NSMutableArray alloc] initWithCapacity:certs.count];
for (id cert in certs) {
SNTCertificate *newCert =
[[SNTCertificate alloc] initWithSecCertificateRef:(SecCertificateRef)cert];
if (newCert) [tempCerts addObject:newCert];
}
certificates = [tempCerts copy];
});
return certificates;
}
- (SNTCertificate *)leafCertificate {
return [self.certificates firstObject];
}
- (NSString *)binaryPath {
CFURLRef path;
OSStatus status = SecCodeCopyPath(_codeRef, kSecCSDefaultFlags, &path);
NSURL *pathURL = CFBridgingRelease(path);
if (status != errSecSuccess) return nil;
return [pathURL path];
}
#pragma mark Comparisons
- (BOOL)signingChainMatches:(SNTCodesignChecker *)otherChecker {
return [self.certificates isEqual:otherChecker.certificates];
}
- (BOOL)teamSigningMatches:(SNTCodesignChecker *)otherChecker {
SNTCertificate *myLeaf = [self.certificates firstObject];
SNTCertificate *otherLeaf = [otherChecker.certificates firstObject];
return ([myLeaf.orgUnit isEqual:otherLeaf.orgUnit] &&
[self validateAppleAnchorGeneric] &&
[otherChecker validateAppleAnchorGeneric]);
}
@end

View File

@@ -0,0 +1,66 @@
/// Copyright 2014 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.
#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.
// Each enum contains an _UNKNOWN and a _MAX value, which the valid values must be between so that
// the code can easily verify valid values.
typedef enum {
RULETYPE_UNKNOWN,
RULETYPE_BINARY = 1,
RULETYPE_CERT = 2,
RULETYPE_MAX
} santa_ruletype_t;
typedef enum {
RULESTATE_UNKNOWN,
RULESTATE_WHITELIST = 1,
RULESTATE_BLACKLIST = 2,
RULESTATE_SILENT_BLACKLIST = 3,
RULESTATE_REMOVE = 4,
RULESTATE_MAX
} santa_rulestate_t;
typedef enum {
CLIENTMODE_UNKNOWN,
CLIENTMODE_MONITOR = 1,
CLIENTMODE_LOCKDOWN = 2,
CLIENTMODE_MAX
} santa_clientmode_t;
typedef enum {
EVENTSTATE_UNKNOWN,
EVENTSTATE_ALLOW_UNKNOWN = 1,
EVENTSTATE_ALLOW_BINARY = 2,
EVENTSTATE_ALLOW_CERTIFICATE = 3,
EVENTSTATE_BLOCK_UNKNOWN = 4,
EVENTSTATE_BLOCK_BINARY = 5,
EVENTSTATE_BLOCK_CERTIFICATE = 6,
EVENTSTATE_MAX
} santa_eventstate_t;
#endif // SANTA__COMMON__COMMONENUMS_H

View File

@@ -0,0 +1,61 @@
/// Copyright 2014 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.
#include "SNTCommonEnums.h"
/// Singleton that provides an interface for managing configuration values on disk
/// n.b: This class is designed as a singleton but is not enforced.
@interface SNTConfigurator : NSObject
/// The operating mode
@property santa_clientmode_t clientMode;
/// If YES, debug logging is enabled
@property(readonly) BOOL debugLogging;
# pragma mark - Sync Settings
/// The base URL of the sync server
@property(readonly) NSURL *syncBaseURL;
/// The machine owner
@property(readonly) NSString *machineOwner;
/// If set, this over-rides the default machine ID used for syncing
@property(readonly) NSString *machineIDOverride;
# pragma mark Server Auth Settings
/// If set, this is valid PEM containing one or more certificates to be used to evaluate the
/// server's SSL chain, overriding the list of trusted CAs distributed with the OS.
@property(readonly) NSData *syncServerAuthRootsData;
/// This property is the same as the above but is a file on disk containing the PEM data.
@property(readonly) NSString *syncServerAuthRootsFile;
# pragma mark Client Auth Settings
/// If set, this is the Common Name of a certificate in the System keychain to be used for
/// sync authentication. The corresponding private key must also be in the keychain.
@property(readonly) NSString *syncClientAuthCertificateCn;
/// If set, this is the Issuer Name of a certificate in the System keychain to be used for
/// sync authentication. The corresponding private key must also be in the keychain.
@property(readonly) NSString *syncClientAuthCertificateIssuer;
/// Retrieve the initialized singleton configurator object
+ (instancetype)configurator;
@end

View File

@@ -0,0 +1,151 @@
/// Copyright 2014 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 "SNTConfigurator.h"
#import "SNTLogging.h"
@interface SNTConfigurator ()
@property NSMutableDictionary *configData;
@end
@implementation SNTConfigurator
/// The hard-coded path to the config file
static NSString * const kConfigFilePath = @"/var/db/santa/config.plist";
/// The keys in the config file
static NSString * const kSyncBaseURLKey = @"SyncBaseURL";
static NSString * const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
static NSString * const kClientAuthCertificateIssuerKey = @"ClientAuthCertificateIssuerCN";
static NSString * const kServerAuthRootsDataKey = @"ServerAuthRootsData";
static NSString * const kServerAuthRootsFileKey = @"ServerAuthRootsFile";
static NSString * const kDebugLoggingKey = @"DebugLogging";
static NSString * const kClientModeKey = @"ClientMode";
static NSString * const kMachineOwnerKey = @"MachineOwner";
static NSString * const kMachineIDKey = @"MachineID";
static NSString * const kMachineOwnerPlistFileKey = @"MachineOwnerPlist";
static NSString * const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
static NSString * const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
- (instancetype)init {
self = [super init];
if (self) {
[self reloadConfigData];
}
return self;
}
# pragma mark Singleton retriever
+ (instancetype)configurator {
static SNTConfigurator *sharedConfigurator = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedConfigurator = [[SNTConfigurator alloc] init];
});
return sharedConfigurator;
}
# pragma mark Public Interface
- (NSURL *)syncBaseURL {
return [NSURL URLWithString:self.configData[kSyncBaseURLKey]];
}
- (NSString *)syncClientAuthCertificateCn {
return self.configData[kClientAuthCertificateCNKey];
}
- (NSString *)syncClientAuthCertificateIssuer {
return self.configData[kClientAuthCertificateIssuerKey];
}
- (NSData *)syncServerAuthRootsData {
return self.configData[kServerAuthRootsDataKey];
}
- (NSString *)syncServerAuthRootsFile {
return self.configData[kServerAuthRootsFileKey];
}
- (NSString *)machineOwner {
if (self.configData[kMachineOwnerPlistFileKey] && self.configData[kMachineOwnerPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineOwnerPlistFileKey]];
return plist[kMachineOwnerPlistKeyKey];
}
if (self.configData[kMachineOwnerKey]) {
return self.configData[kMachineOwnerKey];
}
return @"";
}
- (NSString *)machineIDOverride {
if (self.configData[kMachineIDPlistFileKey] && self.configData[kMachineIDPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineIDPlistFileKey]];
return plist[kMachineIDPlistKeyKey];
}
if (self.configData[kMachineIDKey]) {
return self.configData[kMachineIDKey];
}
return @"";
}
- (BOOL)debugLogging {
return [self.configData[kDebugLoggingKey] boolValue];
}
- (santa_clientmode_t)clientMode {
int cm = [self.configData[kClientModeKey] intValue];
if (cm > CLIENTMODE_UNKNOWN && cm < CLIENTMODE_MAX) {
return cm;
} else {
self.configData[kClientModeKey] = @(CLIENTMODE_MONITOR);
return CLIENTMODE_MONITOR;
}
}
- (void)setClientMode:(santa_clientmode_t)newMode {
if (newMode > CLIENTMODE_UNKNOWN && newMode < CLIENTMODE_MAX) {
[self reloadConfigData];
self.configData[kClientModeKey] = @(newMode);
[self saveConfigToDisk];
}
}
#pragma mark Private
- (void)saveConfigToDisk {
[self.configData writeToFile:kConfigFilePath atomically:YES];
}
- (void)reloadConfigData {
_configData = [[NSDictionary dictionaryWithContentsOfFile:kConfigFilePath] mutableCopy];
if (!_configData) {
_configData = [NSMutableDictionary dictionary];
}
}
@end

View File

@@ -0,0 +1,17 @@
/// Copyright 2014 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.
/// Simple function to check and drop root privileges.
/// @return True if dropping was successful or unnecessary.
BOOL DropRootPrivileges();

View File

@@ -0,0 +1,30 @@
/// Copyright 2014 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 "SNTDropRootPrivs.h"
BOOL DropRootPrivileges() {
if (getuid() == 0 || geteuid() == 0 || getgid() == 0 || getegid() == 0) {
if (setgid(-2) != 0 || setgroups(0, NULL) != 0 || setegid(-2) != 0 ||
setuid(-2) != 0 || seteuid(-2) != 0) {
return false;
}
if (getuid() != geteuid() || getgid() != getegid()) {
return false;
}
}
return true;
}

View File

@@ -0,0 +1,71 @@
/// Copyright 2014 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.
/// Common defines between kernel <-> userspace
#ifndef SANTA__COMMON__KERNELCOMMON_H
#define SANTA__COMMON__KERNELCOMMON_H
// Defines the lengths of paths and SHA-1's passed around.
#define MAX_PATH_LEN 1024
#define MAX_SHA1_LEN 20
#define MAX_SHA1_STRING 41
#define MAX_VNODE_ID_STR 21
// Defines the name of the userclient class and the driver bundle ID.
#define USERCLIENT_CLASS "com_google_SantaDriver"
#define USERCLIENT_ID "com.google.santa-driver"
// List of methods supported by the driver.
enum SantaDriverMethods {
kSantaUserClientOpen,
kSantaUserClientClose,
kSantaUserClientAllowBinary,
kSantaUserClientDenyBinary,
kSantaUserClientClearCache,
kSantaUserClientCacheCount,
// Any methods supported by the driver should be added above this line to
// ensure this remains the count of methods.
kSantaUserClientNMethods,
};
// Enum defining actions that can be passed down the IODataQueue and in
// response methods.
typedef enum {
ACTION_UNSET = 0,
// CHECKBW
ACTION_REQUEST_CHECKBW = 10,
ACTION_RESPOND_CHECKBW_ALLOW = 11,
ACTION_RESPOND_CHECKBW_DENY = 12,
// SHUTDOWN
ACTION_REQUEST_SHUTDOWN = 60,
// ERROR
ACTION_ERROR = 99,
} santa_action_t;
// Message struct that is sent down the IODataQueue.
typedef struct {
santa_action_t action;
uid_t userId;
pid_t pid;
char sha1[MAX_SHA1_STRING];
char path[MAX_PATH_LEN];
uint64_t vnode_id;
} santa_message_t;
#endif // SANTA__COMMON__KERNELCOMMON_H

View File

@@ -0,0 +1,53 @@
/// Copyright 2014 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.
/// Logging definitions, for both kernel and user space.
#ifndef SANTA__COMMON__LOGGING_H
#define SANTA__COMMON__LOGGING_H
#ifdef KERNEL
#ifdef DEBUG
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n");
#else // DEBUG
#define LOGD(...)
#endif // DEBUG
#define LOGI(...) IOLog("I santa-driver: " __VA_ARGS__); IOLog("\n")
#define LOGW(...) IOLog("W santa-driver: " __VA_ARGS__); IOLog("\n")
#define LOGE(...) IOLog("E santa-driver: " __VA_ARGS__); IOLog("\n")
#else // KERNEL
#define LOG_LEVEL_ERROR 1
#define LOG_LEVEL_WARN 2
#define LOG_LEVEL_INFO 3
#define LOG_LEVEL_DEBUG 4
/// Logging function.
/// level is one of the levels defined above
/// error is the destination a FILE, generally should be stdout or stderr
/// format is the printf style format string
/// ... is the arguments to format.
void logMessage(int level, FILE *destination, NSString *format, ...);
/// Simple logging macros
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__);
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__);
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__);
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__);
#endif // KERNEL
#endif // SANTA__COMMON__LOGGING_H

View File

@@ -0,0 +1,65 @@
/// Copyright 2014 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 "SNTLogging.h"
#import "SNTConfigurator.h"
#ifdef DEBUG
static int logLevel = LOG_LEVEL_DEBUG; // default to info
#else
static int logLevel = LOG_LEVEL_INFO;
#endif
void logMessage(int level, FILE *destination, NSString *format, ...) {
static NSDateFormatter *dateFormatter;
static NSString *binaryName;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
[dateFormatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS'Z"];
binaryName = [[NSProcessInfo processInfo] processName];
// If debug logging is enabled, the process must be restarted.
if ([[SNTConfigurator configurator] debugLogging]) {
logLevel = LOG_LEVEL_DEBUG;
}
});
if (logLevel < level) return;
va_list args;
va_start(args, format);
NSString *s = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
// Only prepend timestamp, severity and binary name if stdout is not a TTY
if (isatty(fileno(destination))) {
fprintf(destination, "%s\n", [s UTF8String]);
} else {
NSString *levelName;
switch (level) {
case LOG_LEVEL_ERROR: levelName = @"E"; break;
case LOG_LEVEL_WARN: levelName = @"W"; break;
case LOG_LEVEL_INFO: levelName = @"I"; break;
case LOG_LEVEL_DEBUG: levelName = @"D"; break;
}
fprintf(destination, "%s\n", [[NSString stringWithFormat:@"[%@] %@ %@: %@",
[dateFormatter stringFromDate:[NSDate date]], levelName, binaryName, s] UTF8String]);
}
}

View File

@@ -0,0 +1,37 @@
/// Copyright 2014 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.
@class SNTCertificate;
/// An SNTEvent is created when Santa is making a decision about an execution request.
/// All of the information required to make that decision, log it, notify the user etc. must be
/// encapsulated within this class.
@interface SNTNotificationMessage : NSObject<NSSecureCoding>
/// The path of the binary that was blocked.
@property(copy) NSString *path;
/// The SHA-1 of the binary that was blocked.
@property(copy) NSString *SHA1;
/// An array of @c SNTCertificate objects representing the certificate chain the binary was signed with.
@property(copy) NSArray *certificates;
/// A custom message to display to the user when blocking this binary, if any.
@property(copy) NSString *customMessage;
// A convenience accessor to the first certificate in @c certificates.
@property(readonly) SNTCertificate *leafCertificate;
@end

View File

@@ -0,0 +1,55 @@
/// Copyright 2014 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 "SNTNotificationMessage.h"
#import "SNTCertificate.h"
@implementation SNTNotificationMessage
static NSString *const kPathKey = @"path";
static NSString *const kSHA1Key = @"sha1";
static NSString *const kCertificatesKey = @"certificates";
static NSString *const kCustomMessageKey = @"custommessage";
#pragma mark NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.path forKey:kPathKey];
[coder encodeObject:self.SHA1 forKey:kSHA1Key];
[coder encodeObject:self.customMessage forKey:kCustomMessageKey];
[coder encodeObject:self.certificates forKey:kCertificatesKey];
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
_path = [decoder decodeObjectOfClass:[NSString class] forKey:kPathKey];
_SHA1 = [decoder decodeObjectOfClass:[NSString class] forKey:kSHA1Key];
_customMessage = [decoder decodeObjectOfClass:[NSString class] forKey:kCustomMessageKey];
NSSet *certClasses = [NSSet setWithObjects:[NSArray class], [SNTCertificate class], nil];
_certificates = [decoder decodeObjectOfClasses:certClasses forKey:kCertificatesKey];
return self;
}
#pragma mark Calculated Properties
- (SNTCertificate *)leafCertificate {
return [self.certificates firstObject];
}
@end

38
Source/common/SNTRule.h Normal file
View File

@@ -0,0 +1,38 @@
/// Copyright 2014 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.
#include "SNTCommonEnums.h"
/// Represents a Rule.
@interface SNTRule : NSObject<NSSecureCoding>
/// The SHA-1 hash of the object this rule is for
@property NSString *SHA1;
/// The state of this rule
@property santa_rulestate_t state;
/// The type of object this rule is for (binary, certificate)
@property santa_ruletype_t type;
/// A custom message that will be displayed if this rule blocks a binary from executing
@property NSString *customMsg;
/// Designated initializer.
- (instancetype)initWithSHA1:(NSString *)SHA1
state:(santa_rulestate_t)state
type:(santa_ruletype_t)type
customMsg:(NSString *)customMsg;
@end

59
Source/common/SNTRule.m Normal file
View File

@@ -0,0 +1,59 @@
/// Copyright 2014 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 "SNTRule.h"
@implementation SNTRule
static NSString *const kSHA1Key = @"sha1";
static NSString *const kStateKey = @"state";
static NSString *const kTypeKey = @"type";
static NSString *const kCustomMessageKey = @"custommsg";
- (instancetype)initWithSHA1:(NSString *)SHA1
state:(santa_rulestate_t)state
type:(santa_ruletype_t)type
customMsg:(NSString *)customMsg {
self = [super init];
if (self) {
_SHA1 = SHA1;
_state = state;
_type = type;
_customMsg = customMsg;
}
return self;
}
#pragma mark NSSecureCoding
+ (BOOL)supportsSecureCoding { return YES; }
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.SHA1 forKey:kSHA1Key];
[coder encodeInt:self.state forKey:kStateKey];
[coder encodeInt:self.type forKey:kTypeKey];
[coder encodeObject:self.customMsg forKey:kCustomMessageKey];
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
NSSet *stringPlusNull = [NSSet setWithObjects:[NSString class], [NSNull class], nil];
_SHA1 = [decoder decodeObjectOfClass:[NSString class] forKey:kSHA1Key];
_state = [decoder decodeIntForKey:kStateKey];
_type = [decoder decodeIntForKey:kTypeKey];
_customMsg = [decoder decodeObjectOfClasses:stringPlusNull forKey:kCustomMessageKey];
return self;
}
@end

View File

@@ -0,0 +1,40 @@
/// Copyright 2014 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.
#include "SNTCommonEnums.h"
/// Represents an event stored in the database.
@interface SNTStoredEvent : NSObject<NSSecureCoding>
@property NSNumber *idx;
@property NSString *fileSHA1;
@property NSString *filePath;
@property NSString *fileBundleName;
@property NSString *fileBundleID;
@property NSString *fileBundleVersion;
@property NSString *fileBundleVersionString;
@property NSString *certSHA1;
@property NSString *certCN;
@property NSString *certOrg;
@property NSString *certOU;
@property NSDate *certValidFromDate;
@property NSDate *certValidUntilDate;
@property NSString *executingUser;
@property NSDate *occurrenceDate;
@property santa_eventstate_t decision;
@property NSArray *loggedInUsers;
@property NSArray *currentSessions;
@property NSNumber *pid;
@end

View File

@@ -0,0 +1,102 @@
/// Copyright 2014 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 "SNTStoredEvent.h"
@implementation SNTStoredEvent
+ (BOOL)supportsSecureCoding { return YES; }
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.idx forKey:@"idx"];
[coder encodeObject:self.fileSHA1 forKey:@"fileSHA1"];
[coder encodeObject:self.filePath forKey:@"filePath"];
if (self.fileBundleName) [coder encodeObject:self.fileBundleName forKey:@"fileBundleName"];
if (self.fileBundleID) [coder encodeObject:self.fileBundleID forKey:@"fileBundleID"];
if (self.fileBundleVersion) {
[coder encodeObject:self.fileBundleVersion forKey:@"fileBundleVersion"];
}
if (self.fileBundleVersionString) {
[coder encodeObject:self.fileBundleVersionString forKey:@"fileBundleVersionString"];
}
if (self.certSHA1) [coder encodeObject:self.certSHA1 forKey:@"certSHA1"];
if (self.certCN) [coder encodeObject:self.certCN forKey:@"certCN"];
if (self.certOrg) [coder encodeObject:self.certOrg forKey:@"certOrg"];
if (self.certOU) [coder encodeObject:self.certOU forKey:@"certOU"];
if (self.certValidFromDate) {
[coder encodeObject:self.certValidFromDate forKey:@"certValidFromDate"];
}
if (self.certValidUntilDate) {
[coder encodeObject:self.certValidUntilDate forKey:@"certValidUntilDate"];
}
[coder encodeObject:self.executingUser forKey:@"executingUser"];
[coder encodeObject:self.occurrenceDate forKey:@"occurrenceDate"];
[coder encodeInt:self.decision forKey:@"decision"];
if (self.loggedInUsers) [coder encodeObject:self.loggedInUsers forKey:@"loggedInUsers"];
if (self.currentSessions) [coder encodeObject:self.currentSessions forKey:@"currentSessions"];
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
_idx = [decoder decodeObjectOfClass:[NSNumber class] forKey:@"idx"];
_fileSHA1 = [decoder decodeObjectOfClass:[NSString class] forKey:@"fileSHA1"];
_filePath = [decoder decodeObjectOfClass:[NSString class] forKey:@"filePath"];
_fileBundleName = [decoder decodeObjectOfClass:[NSString class] forKey:@"fileBundleName"];
_fileBundleID = [decoder decodeObjectOfClass:[NSString class] forKey:@"fileBundleID"];
_fileBundleVersion = [decoder decodeObjectOfClass:[NSString class] forKey:@"fileBundleVersion"];
_fileBundleVersionString =
[decoder decodeObjectOfClass:[NSString class] forKey:@"fileBundleVersionString"];
_certSHA1 = [decoder decodeObjectOfClass:[NSString class] forKey:@"certSHA1"];
_certCN = [decoder decodeObjectOfClass:[NSString class] forKey:@"certCN"];
_certOrg = [decoder decodeObjectOfClass:[NSString class] forKey:@"certOrg"];
_certOU = [decoder decodeObjectOfClass:[NSString class] forKey:@"certOU"];
_certValidFromDate = [decoder decodeObjectOfClass:[NSDate class] forKey:@"certValidFromDate"];
_certValidUntilDate = [decoder decodeObjectOfClass:[NSDate class] forKey:@"certValidUntilDate"];
_executingUser = [decoder decodeObjectOfClass:[NSString class] forKey:@"executingUser"];
_occurrenceDate = [decoder decodeObjectOfClass:[NSDate class] forKey:@"occurrenceDate"];
_decision = [decoder decodeIntForKey:@"decision"];
NSSet *stringAndArrayClasses = [NSSet setWithObjects:[NSArray class], [NSString class], nil];
_loggedInUsers = [decoder decodeObjectOfClasses:stringAndArrayClasses forKey:@"loggedInUsers"];
_currentSessions = [decoder decodeObjectOfClasses:stringAndArrayClasses
forKey:@"currentSessions"];
return self;
}
- (BOOL)isEqual:(SNTStoredEvent *)other {
if (other == self) return YES;
if (![other isKindOfClass:[SNTStoredEvent class]]) return NO;
return ([self.fileSHA1 isEqual:other.fileSHA1] &&
[self.idx isEqual:other.idx]);
}
- (NSUInteger)hash {
NSUInteger prime = 31;
NSUInteger result = 1;
result = prime * result + [self.idx hash];
result = prime * result + [self.fileSHA1 hash];
result = prime * result + [self.filePath hash];
return result;
}
- (NSString *)description {
return [NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-1: %@", self.idx, self.fileSHA1];
}
@end

View File

@@ -0,0 +1,28 @@
/// Copyright 2014 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.
/// Simple class for fetching system information
@interface SNTSystemInfo : NSObject
+ (NSString *)serialNumber;
+ (NSString *)hardwareUUID;
+ (NSString *)osVersion;
+ (NSString *)osBuild;
+ (NSString *)shortHostname;
+ (NSString *)longHostname;
@end

View File

@@ -0,0 +1,73 @@
/// Copyright 2014 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 "SNTSystemInfo.h"
@implementation SNTSystemInfo
+ (NSString *)serialNumber {
io_service_t platformExpert = IOServiceGetMatchingService(
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
if (!platformExpert) return nil;
NSString *serial = CFBridgingRelease(
IORegistryEntryCreateCFProperty(platformExpert,
CFSTR(kIOPlatformSerialNumberKey),
kCFAllocatorDefault,
0));
IOObjectRelease(platformExpert);
return serial;
}
+ (NSString *)hardwareUUID {
io_service_t platformExpert = IOServiceGetMatchingService(
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
if (!platformExpert) return nil;
NSString *uuid = CFBridgingRelease(
IORegistryEntryCreateCFProperty(platformExpert,
CFSTR(kIOPlatformUUIDKey),
kCFAllocatorDefault, 0));
IOObjectRelease(platformExpert);
return uuid;
}
+ (NSString *)osVersion {
return [SNTSystemInfo _systemVersionDictionary][@"ProductVersion"];
}
+ (NSString *)osBuild {
return [SNTSystemInfo _systemVersionDictionary][@"ProductBuildVersion"];
}
+ (NSString *)shortHostname {
return [[[SNTSystemInfo longHostname] componentsSeparatedByString:@"."] firstObject];
}
+ (NSString *)longHostname {
return [[NSHost currentHost] name];
}
# pragma mark - Internal
+ (NSDictionary *)_systemVersionDictionary {
return [NSDictionary dictionaryWithContentsOfFile:
@"/System/Library/CoreServices/SystemVersion.plist"];
}
@end

View File

@@ -0,0 +1,87 @@
/// Copyright 2014 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.
/**
* A validating XPC connection/listener which uses codesigning to validate that both ends of the
* connection were signed by the same certificate chain.
*
* Example server started by @c launchd where the @c launchd job has a @c MachServices key:
*
*@code
* SNTXPCConnection *conn = [[SNTXPCConnection alloc] initServerWithName:@"MyServer"];
* conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
* conn.exportedObject = myObject;
* conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyClientProtocol)];
* [conn resume];
*@endcode
*
* Example client, connecting to above server:
*
*@code
* SNTXPCConnection *conn = [[SNTXPCConnection alloc] initClientWithName:"MyServer" withOptions:0];
* conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyClientProtocol)];
* conn.exportedObject = myObject;
* conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
* conn.invalidationHandler = ^{ NSLog(@"Connection invalidated") };
* [conn resume];
*@endcode
*
* Either side can then send a message to the other with:
*
*@code
* [conn.remoteObjectProxy selectorInRemoteInterface];
*@endcode
*
* Note: messages are always delivered on a background thread!
**/
@interface SNTXPCConnection : NSObject<NSXPCListenerDelegate>
typedef void (^SNTXPCInvalidationBlock)(void);
typedef void (^SNTXPCAcceptedBlock)(void);
typedef void (^SNTXPCRejectedBlock)(void);
/// The interface the remote object should conform to.
@property(retain) NSXPCInterface *remoteInterface;
/// A proxy to the object at the other end of the connection.
/// *Warning*: Do not send a message to this object if you didn't set @c remoteInterface above
/// before calling the @c resume method, doing so will throw an exception.
@property(readonly) id remoteObjectProxy;
/// The interface this object conforms to.
@property(retain) NSXPCInterface *exportedInterface;
/// The object that responds to messages from the other end.
@property(retain) id exportedObject;
/// A block to run when the connection is invalidated.
@property(copy) SNTXPCInvalidationBlock invalidationHandler;
/// A block to run when the connection has been accepted.
@property(copy) SNTXPCAcceptedBlock acceptedHandler;
/// A block to run when the connection has been rejected.
@property(copy) SNTXPCRejectedBlock rejectedHandler;
/// Initializer for the 'server' side of the connection, the binary that was started by launchd.
- (instancetype)initServerWithName:(NSString *)name;
/// Initializer for the 'client' side of the connection. If the 'server' was started as a
/// LaunchDaemon (running as root), pass |NSXPCConnectionPrivileged| for |options|, otherwise use 0.
- (instancetype)initClientWithName:(NSString *)name options:(NSXPCConnectionOptions)options;
/// Call when the properties of the object have been set-up and you're ready for connections.
- (void)resume;
@end

View File

@@ -0,0 +1,197 @@
/// Copyright 2014 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 "SNTXPCConnection.h"
#import "SNTCodesignChecker.h"
@protocol XPCConnectionValidityRequest
- (void)isConnectionValidWithBlock:(void (^)(BOOL))block;
@end
@interface SNTXPCConnection ()
/// The XPC listener (used on server-side only).
@property NSXPCListener *listenerObject;
/// The current connection object.
@property NSXPCConnection *currentConnection;
/// The remote interface to use while the connection hasn't been validated.
@property NSXPCInterface *validatorInterface;
@end
@implementation SNTXPCConnection
#pragma mark Initializers
- (instancetype)initServerWithName:(NSString *)name {
self = [super init];
if (self) {
Protocol *validatorProtocol = @protocol(XPCConnectionValidityRequest);
_validatorInterface = [NSXPCInterface interfaceWithProtocol:validatorProtocol];
_listenerObject = [[NSXPCListener alloc] initWithMachServiceName:name];
if (!_validatorInterface || !_listenerObject) return nil;
}
return self;
}
- (instancetype)initClientWithName:(NSString *)name options:(NSXPCConnectionOptions)options {
self = [super init];
if (self) {
Protocol *validatorProtocol = @protocol(XPCConnectionValidityRequest);
_validatorInterface = [NSXPCInterface interfaceWithProtocol:validatorProtocol];
_currentConnection = [[NSXPCConnection alloc] initWithMachServiceName:name
options:options];
if (!_validatorInterface || !_currentConnection) return nil;
}
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
#pragma mark Connection set-up
- (void)resume {
if (_listenerObject) {
// A new listener doesn't do anything until a client connects.
self.listenerObject.delegate = self;
[self.listenerObject resume];
} else {
// A new client begins the validation process.
NSXPCConnection *connection = _currentConnection;
connection.remoteObjectInterface = _validatorInterface;
connection.invalidationHandler = ^{
[self invokeInvalidationHandler];
self.currentConnection = nil;
};
connection.interruptionHandler = ^{
[self.currentConnection invalidate];
};
[connection resume];
[[connection remoteObjectProxy] isConnectionValidWithBlock:^void(BOOL response) {
pid_t pid = self.currentConnection.processIdentifier;
SNTCodesignChecker *selfCS = [[SNTCodesignChecker alloc] initWithSelf];
SNTCodesignChecker *otherCS = [[SNTCodesignChecker alloc] initWithPID:pid];
if (response && [otherCS signingInformationMatches:selfCS]) {
[self.currentConnection suspend];
self.currentConnection.remoteObjectInterface = self.remoteInterface;
self.currentConnection.exportedInterface = self.exportedInterface;
self.currentConnection.exportedObject = self.exportedObject;
[self invokeAcceptedHandler];
[self.currentConnection resume];
} else {
[self invokeRejectedHandler];
[self.currentConnection invalidate];
self.currentConnection = nil;
}
}];
}
}
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)connection {
// Reject connection if a connection already exists. As the invalidation/interruption handlers
// both cause the currentConnection to be nil'd out, this should be OK.
if (self.currentConnection) return NO;
connection.exportedObject = self;
connection.exportedInterface = _validatorInterface;
connection.invalidationHandler = ^{
[self invokeInvalidationHandler];
self.currentConnection = nil;
};
connection.interruptionHandler = ^{
// Invalidate the connection, causing the handler above to run
[self.currentConnection invalidate];
};
// At this point the client is connected and can send messages but the only message it can send
// is isConnectionValidWithBlock: and we won't send anything to it until it has.
self.currentConnection = connection;
[connection resume];
return YES;
}
- (void)isConnectionValidWithBlock:(void (^)(BOOL))block {
pid_t pid = self.currentConnection.processIdentifier;
SNTCodesignChecker *selfCS = [[SNTCodesignChecker alloc] initWithSelf];
SNTCodesignChecker *otherCS = [[SNTCodesignChecker alloc] initWithPID:pid];
if ([otherCS signingInformationMatches:selfCS]) {
[self.currentConnection suspend];
self.currentConnection.remoteObjectInterface = self.remoteInterface;
self.currentConnection.exportedInterface = self.exportedInterface;
self.currentConnection.exportedObject = self.exportedObject;
[self.currentConnection resume];
[self invokeAcceptedHandler];
// Let remote end know that we accepted. Note: in acception this must come last otherwise
// the remote end might start sending messages before the interface is fully set-up.
block(YES);
} else {
// Let remote end know that we rejected. Note: in rejection this must come first otherwise
// the connection is invalidated before the client ever realizes.
block(NO);
[self invokeRejectedHandler];
[self.currentConnection invalidate];
self.currentConnection = nil;
}
}
- (id)remoteObjectProxy {
if (self.currentConnection && self.currentConnection.remoteObjectInterface) {
return [self.currentConnection remoteObjectProxyWithErrorHandler:^(NSError *error) {
[self.currentConnection invalidate];
}];
}
return nil;
}
- (void)invokeAcceptedHandler {
if (self.acceptedHandler) {
self.acceptedHandler();
}
}
- (void)invokeRejectedHandler {
if (self.rejectedHandler) {
self.rejectedHandler();
}
}
- (void)invokeInvalidationHandler {
if (self.invalidationHandler) {
self.invalidationHandler();
}
}
@end

View File

@@ -0,0 +1,52 @@
/// Copyright 2014 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.
#include "SNTCommonEnums.h"
@class SNTRule;
@class SNTStoredEvent;
/// Protocol implemented by santad and utilized by santactl
@protocol SNTDaemonControlXPC
/// Kernel ops
- (void)cacheCount:(void (^)(uint64_t))reply;
- (void)flushCache:(void (^)(BOOL))reply;
/// Database ops
- (void)databaseRuleCounts:(void (^)(uint64_t binary, uint64_t certificate))reply;
- (void)databaseRuleAddRule:(SNTRule *)rule withReply:(void (^)())reply;
- (void)databaseRuleAddRules:(NSArray *)rules withReply:(void (^)())reply;
- (void)databaseEventCount:(void (^)(uint64_t count))reply;
- (void)databaseEventForSHA1:(NSString *)sha1 withReply:(void (^)(SNTStoredEvent *))reply;
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
/// Misc ops
- (void)clientMode:(void (^)(santa_clientmode_t))reply;
- (void)setClientMode:(santa_clientmode_t)mode withReply:(void (^)())reply;
@end
@interface SNTXPCControlInterface : NSObject
/// Returns the MachService ID for this service.
+ (NSString *)serviceId;
/// Returns an initialized NSXPCInterface for the SNTDaemonControlXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
+ (NSXPCInterface *)controlInterface;
@end

View File

@@ -0,0 +1,42 @@
/// Copyright 2014 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 "SNTXPCControlInterface.h"
#import "SNTRule.h"
#import "SNTStoredEvent.h"
@implementation SNTXPCControlInterface
+ (NSString *)serviceId {
return @"SantaXPCControl";
}
+ (NSXPCInterface *)controlInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTDaemonControlXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(databaseEventsPending:)
argumentIndex:0
ofReply:YES];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTRule class], nil]
forSelector:@selector(databaseRuleAddRules:withReply:)
argumentIndex:0
ofReply:NO];
return r;
}
@end

View File

@@ -0,0 +1,30 @@
/// Copyright 2014 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.
/// Protocol implemented by SantaNotifier and utilized by santad
@class SNTNotificationMessage;
@protocol SNTNotifierXPC
- (void)postBlockNotification:(SNTNotificationMessage *)event;
@end
@interface SNTXPCNotifierInterface : NSObject
/// Returns the MachService ID for this service.
+ (NSString *)serviceId;
/// Returns an initialized NSXPCInterface for the SNTNotifierXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
+ (NSXPCInterface *)notifierInterface;
@end

View File

@@ -0,0 +1,28 @@
/// Copyright 2014 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 "SNTXPCNotifierInterface.h"
@implementation SNTXPCNotifierInterface
+ (NSString *)serviceId {
return @"SantaXPCNotifications";
}
+ (NSXPCInterface *)notifierInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTNotifierXPC)];
return r;
}
@end

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>com.google.santa-driver</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>0.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.7</string>
<key>IOKitPersonalities</key>
<dict>
<key>SantaDriver</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.google.santa-driver</string>
<key>IOClass</key>
<string>com_google_SantaDriver</string>
<key>IOProviderClass</key>
<string>IOResources</string>
<key>IOResourceMatch</key>
<string>IOKit</string>
<key>IOUserClientClass</key>
<string>com_google_SantaDriverClient</string>
</dict>
</dict>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.kpi.bsd</key>
<string>9.0.0</string>
<key>com.apple.kpi.iokit</key>
<string>9.0.0</string>
<key>com.apple.kpi.libkern</key>
<string>9.0.0</string>
<key>com.apple.kpi.mach</key>
<string>9.0.0</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,505 @@
/// Copyright 2014 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.
#include "SantaDecisionManager.h"
#define super OSObject
OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
#pragma mark Object Lifecycle
SantaDecisionManager *SantaDecisionManager::WithQueueAndPID(
IOSharedDataQueue *queue, pid_t pid) {
SantaDecisionManager *me = new SantaDecisionManager;
if (me && !me->InitWithQueueAndPID(queue, pid)) {
me->free();
return NULL;
}
return me;
}
bool SantaDecisionManager::InitWithQueueAndPID(
IOSharedDataQueue *queue, pid_t pid) {
if (!super::init()) return false;
if (!pid) return false;
if (!queue) return false;
listener_invocations_ = 0;
dataqueue_ = queue;
owning_pid_ = pid;
owning_proc_ = proc_find(pid);
if (!(dataqueue_lock_ = IORWLockAlloc())) return FALSE;
if (!(cached_decisions_lock_ = IORWLockAlloc())) return FALSE;
if (!(cached_decisions_ = OSDictionary::withCapacity(1000))) return FALSE;
return TRUE;
}
void SantaDecisionManager::free() {
proc_rele(owning_proc_);
if (cached_decisions_) {
cached_decisions_->release();
cached_decisions_ = NULL;
}
if (cached_decisions_lock_) {
IORWLockFree(cached_decisions_lock_);
cached_decisions_lock_ = NULL;
}
if (dataqueue_lock_) {
IORWLockFree(dataqueue_lock_);
dataqueue_lock_ = NULL;
}
super::free();
}
# pragma mark Cache Management
bool SantaDecisionManager::AddToCache(
const char *identifier, santa_action_t decision, uint64_t microsecs) {
IORWLockWrite(cached_decisions_lock_);
if (cached_decisions_->getCount() > kMaxCacheSize) {
// This could be made a _lot_ smarter, say only removing entries older
// than a certain time period. However, with a kMaxCacheSize set
// sufficiently large and a kMaxAllowCacheTimeMilliseconds set
// sufficiently low, this should only ever occur if someone is purposefully
// trying to make the cache grow.
LOGD("Cache too large, flushing.");
cached_decisions_->flushCollection();
}
bool result = false;
if (decision == ACTION_REQUEST_CHECKBW) {
SantaMessage *pending = new SantaMessage();
pending->setAction(ACTION_REQUEST_CHECKBW, 0);
result = cached_decisions_->setObject(identifier, pending);
pending->release(); // it was retained when added to the dictionary
} else {
SantaMessage *pending = OSDynamicCast(
SantaMessage, cached_decisions_->getObject(identifier));
if (pending) {
pending->setAction(decision, microsecs);
result = true;
}
}
IORWLockUnlock(cached_decisions_lock_);
return result;
}
void SantaDecisionManager::CacheCheck(const char *identifier) {
IORWLockRead(cached_decisions_lock_);
bool shouldInvalidate = (cached_decisions_->getObject(identifier) != NULL);
IORWLockUnlock(cached_decisions_lock_);
if (shouldInvalidate) {
IORWLockWrite(cached_decisions_lock_);
cached_decisions_->removeObject(identifier);
IORWLockUnlock(cached_decisions_lock_);
}
}
uint64_t SantaDecisionManager::CacheCount() {
return cached_decisions_->getCount();
}
void SantaDecisionManager::ClearCache() {
IORWLockWrite(cached_decisions_lock_);
cached_decisions_->flushCollection();
IORWLockUnlock(cached_decisions_lock_);
}
santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
santa_action_t result = ACTION_UNSET;
uint64_t decision_time = 0;
IORWLockRead(cached_decisions_lock_);
SantaMessage *cached_decision = OSDynamicCast(
SantaMessage, cached_decisions_->getObject(identifier));
if (cached_decision) {
result = cached_decision->getAction();
decision_time = cached_decision->getMicrosecs();
}
IORWLockUnlock(cached_decisions_lock_);
if (result == ACTION_REQUEST_CHECKBW) {
return ACTION_UNSET;
}
if (result == ACTION_RESPOND_CHECKBW_ALLOW ||
result == ACTION_RESPOND_CHECKBW_DENY) {
uint64_t diff_time = GetCurrentUptime();
if (result == ACTION_RESPOND_CHECKBW_ALLOW) {
if ((kMaxAllowCacheTimeMilliseconds * 1000) > diff_time) {
diff_time = 0;
} else {
diff_time -= (kMaxAllowCacheTimeMilliseconds * 1000);
}
} else if (result == ACTION_RESPOND_CHECKBW_DENY) {
if ((kMaxDenyCacheTimeMilliseconds * 1000) > diff_time) {
diff_time = 0;
} else {
diff_time -= (kMaxDenyCacheTimeMilliseconds * 1000);
}
}
if (decision_time < diff_time) {
IORWLockWrite(cached_decisions_lock_);
cached_decisions_->removeObject(identifier);
IORWLockUnlock(cached_decisions_lock_);
return ACTION_UNSET;
}
}
return result;
}
# pragma mark Queue Management
bool SantaDecisionManager::PostToQueue(santa_message_t message) {
IORWLockWrite(dataqueue_lock_);
bool kr = dataqueue_->enqueue(&message, sizeof(message));
IORWLockUnlock(dataqueue_lock_);
return kr;
}
santa_action_t SantaDecisionManager::FetchDecision(
const kauth_cred_t credential,
const vfs_context_t vfs_context,
const vnode_t vnode) {
santa_action_t return_action = ACTION_UNSET;
// Fetch Vnode ID & string
uint64_t vnode_id = GetVnodeIDForVnode(vfs_context, vnode);
char vnode_id_str[MAX_VNODE_ID_STR];
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
// Check to see if item is in cache
return_action = GetFromCache(vnode_id_str);
// If item wasn't in cache, fetch decision from daemon.
if (return_action == ACTION_UNSET) {
// Add pending request to cache
AddToCache(vnode_id_str, ACTION_REQUEST_CHECKBW, 0);
// Get SHA-1
// TODO(rah): Investigate possible race condition where file is modified
// in between SHA-1 being calculated and response for said file being
// received.
char sha[MAX_SHA1_STRING];
if (!CalculateSHA1ForVnode(credential, vfs_context, vnode, sha)) {
LOGD("Unable to get SHA-1 for file, denying execution");
return ACTION_RESPOND_CHECKBW_DENY;
}
// Get path
char path[MAX_PATH_LEN];
int name_len = MAX_PATH_LEN;
if (vn_getpath(vnode, path, &name_len) != 0) {
path[0] = '\0';
}
// Prepare to send message to daemon
santa_message_t message;
strncpy(message.sha1, sha, MAX_SHA1_STRING);
strncpy(message.path, path, MAX_PATH_LEN);
message.userId = kauth_cred_getuid(credential);
message.pid = proc_selfpid();
message.action = ACTION_REQUEST_CHECKBW;
message.vnode_id = vnode_id;
// Wait for the daemon to respond or die.
do {
// Send request to daemon...
if (!PostToQueue(message)) {
LOGE("Failed to queue request for %s.", path);
return ACTION_ERROR;
}
// ... and wait for it to respond. If after kRequestLoopSleepMilliseconds
// * kMaxRequestLoops it still hasn't responded, send request again.
for (int i = 0; i < kMaxRequestLoops; ++i) {
IOSleep(kRequestLoopSleepMilliseconds);
return_action = GetFromCache(vnode_id_str);
if (return_action != ACTION_UNSET) break;
}
} while (return_action == ACTION_UNSET && proc_exiting(owning_proc_) == 0);
if (return_action == ACTION_UNSET || return_action == ACTION_ERROR) {
LOGE("Daemon process did not respond correctly. Allowing executions "
"until it comes back.");
return ACTION_ERROR;
}
}
return return_action;
}
# pragma mark Misc
bool SantaDecisionManager::CalculateSHA1ForVnode(const kauth_cred_t credential,
const vfs_context_t context,
const vnode_t vp,
char *out) {
out[0] = '\0';
// Get binary size
uint64_t binary_size;
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_data_size);
vnode_getattr(vp, &vap, context);
binary_size = vap.va_data_size;
// Initialize the SHA1 context
SHA1_CTX sha1_ctx;
SHA1Init(&sha1_ctx);
// |chunkSize| should equal one page so that where possible
// the kernel can offload the calculation to dedicated hardware.
int chunkSize = PAGE_SIZE_64;
void *readChunk = IOMalloc(chunkSize);
// Credentials needed for vn_rdwr
kauth_cred_t kerncred = vfs_context_ucred(context);
proc_t p = vfs_context_proc(context);
// Read the file in chunks, updating the SHA as we go
for (uint64_t offset = 0; offset < binary_size; offset += chunkSize) {
int readSize;
if (offset + chunkSize > binary_size) {
readSize = (int)(binary_size - offset);
} else {
readSize = chunkSize;
}
int resid; // unused
if (vn_rdwr(UIO_READ, vp, (caddr_t)readChunk, readSize, offset,
UIO_SYSSPACE, IO_NOAUTH, kerncred, &resid, p) != 0) {
IOFree(readChunk, chunkSize);
return false;
}
SHA1Update(&sha1_ctx, readChunk, readSize);
}
// Free |readChunk|
IOFree(readChunk, chunkSize);
// Finalize the SHA-1 into |buf|
char buf[MAX_SHA1_LEN];
SHA1Final(buf, &sha1_ctx);
// Convert the binary SHA into a hex digest string
for (int i = 0; i < MAX_SHA1_LEN; i++) {
snprintf(out + (2*i), 3, "%02x", (unsigned char)buf[i]);
}
return true;
}
uint64_t SantaDecisionManager::GetVnodeIDForVnode(const vfs_context_t context,
const vnode_t vp) {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fileid);
vnode_getattr(vp, &vap, context);
return 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
SInt32 SantaDecisionManager::GetListenerInvocations() {
return listener_invocations_;
}
void SantaDecisionManager::IncrementListenerInvocations() {
OSIncrementAtomic(&listener_invocations_);
}
void SantaDecisionManager::DecrementListenerInvocations() {
OSDecrementAtomic(&listener_invocations_);
}
bool SantaDecisionManager::MatchesOwningPID(const pid_t other_pid) {
return (owning_pid_ == other_pid);
}
# pragma mark Listener Control
kern_return_t SantaDecisionManager::StartListener() {
process_listener_ = kauth_listen_scope(KAUTH_SCOPE_PROCESS,
process_scope_callback,
reinterpret_cast<void *>(this));
if (!process_listener_) return kIOReturnInternalError;
LOGD("Process listener started.");
vnode_listener_ = kauth_listen_scope(KAUTH_SCOPE_VNODE,
vnode_scope_callback,
reinterpret_cast<void *>(this));
if (!vnode_listener_) return kIOReturnInternalError;
LOGD("Vnode listener started.");
return kIOReturnSuccess;
}
kern_return_t SantaDecisionManager::StopListener() {
kauth_unlisten_scope(vnode_listener_);
vnode_listener_ = NULL;
kauth_unlisten_scope(process_listener_);
process_listener_ = NULL;
// Wait for any active invocations to finish before returning
do {
IOSleep(5);
} while (GetListenerInvocations());
// Delete any cached decisions
ClearCache();
LOGD("Vnode listener stopped.");
return kIOReturnSuccess;
}
#undef super
#pragma mark Kauth Callbacks
extern int process_scope_callback(kauth_cred_t credential,
void *idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3) {
if (idata == NULL) {
LOGE("Process callback established without valid decision manager.");
return KAUTH_RESULT_ALLOW;
}
SantaDecisionManager *sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
// Note: this prevents a debugger from attaching to an existing santad
// process but doesn't prevent starting santad under a debugger. This check
// is only here to try and prevent the user from deadlocking their machine
// by attaching a debugger, so if they work around it and end up deadlocking,
// that's their problem.
if (action == KAUTH_PROCESS_CANTRACE &&
sdm->MatchesOwningPID(proc_pid((proc_t)arg0))) {
*(reinterpret_cast<int *>(arg1)) = EPERM;
LOGD("Denied debugger access");
return KAUTH_RESULT_DENY;
}
return KAUTH_RESULT_ALLOW;
}
extern int vnode_scope_callback(kauth_cred_t credential,
void *idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3) {
// The default action is to defer
int returnResult = KAUTH_RESULT_DEFER;
// Cast arguments to correct types
if (idata == NULL) {
LOGE("Vnode callback established without valid decision manager.");
return returnResult;
}
SantaDecisionManager *sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
vfs_context_t vfs_context = reinterpret_cast<vfs_context_t>(arg0);
vnode_t vnode = reinterpret_cast<vnode_t>(arg1);
// Only operate on regular files (not directories, symlinks, etc.)
vtype vt = vnode_vtype(vnode);
if (vt != VREG) return returnResult;
if (action & KAUTH_VNODE_ACCESS) return returnResult;
// Filter for only WRITE_DATA actions
if (action & KAUTH_VNODE_WRITE_DATA || action & KAUTH_VNODE_APPEND_DATA) {
char vnode_id_str[MAX_VNODE_ID_STR];
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu",
sdm->GetVnodeIDForVnode(vfs_context, vnode));
sdm->CacheCheck(vnode_id_str);
return returnResult;
}
// Filter for only EXECUTE actions
if (action & KAUTH_VNODE_EXECUTE) {
sdm->IncrementListenerInvocations();
// Fetch decision
santa_action_t returnedAction = sdm->FetchDecision(
credential, vfs_context, vnode);
switch (returnedAction) {
case ACTION_RESPOND_CHECKBW_ALLOW:
returnResult = KAUTH_RESULT_ALLOW;
break;
case ACTION_RESPOND_CHECKBW_DENY:
*(reinterpret_cast<int *>(arg3)) = EACCES;
returnResult = KAUTH_RESULT_DENY;
break;
default:
// NOTE: Any unknown response or error condition causes us to fail open.
// Whilst from a security perspective this is bad, it's important that
// we don't break user's machines. Every fallen open response will come
// through this code path and cause this log entry to be created, so we
// can investigate each case and try to fix the root cause.
char path[MAX_PATH_LEN];
int name_len = MAX_PATH_LEN;
if (vn_getpath(vnode, path, &name_len) != 0) {
path[0] = '\0';
}
LOGW("Didn't receive a valid response for %s. Received: %d.",
path,
returnedAction);
break;
}
sdm->DecrementListenerInvocations();
return returnResult;
}
return returnResult;
}

View File

@@ -0,0 +1,156 @@
/// Copyright 2014 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.
#ifndef SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
#define SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
#include <IOKit/IOLib.h>
#include <IOKit/IOSharedDataQueue.h>
#include <libkern/c++/OSDictionary.h>
#include <libkern/crypto/sha1.h>
#include <sys/kauth.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include "SantaMessage.h"
#include "SNTKernelCommon.h"
#include "SNTLogging.h"
/// The maximum number of milliseconds a cached deny message should be
/// considered valid.
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;
/// 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;
/// While waiting for a response from the daemon, this is the maximum number
/// of loops to wait before sending the request again.
const int kMaxRequestLoops = 50;
/// Maximum number of entries in the in-kernel cache.
const int kMaxCacheSize = 10000;
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
/// and responding to the request appropriately.
///
/// Documentation on the Kauth parts can be found here:
/// https://developer.apple.com/library/mac/technotes/tn2127/_index.html
class SantaDecisionManager : public OSObject {
OSDeclareDefaultStructors(SantaDecisionManager);
public:
// Convenience constructor
// Queue remains owned by caller but must exist for lifetime of
// SantaDecisionManager instance.
static SantaDecisionManager *WithQueueAndPID(
IOSharedDataQueue *queue, pid_t pid);
bool InitWithQueueAndPID(IOSharedDataQueue *queue, pid_t pid);
void free();
// Decision Fetching / Daemon Communication
bool PostToQueue(santa_message_t);
santa_action_t FetchDecision(const kauth_cred_t credential,
const vfs_context_t vfs_context,
const vnode_t vnode);
// Hash calculation
bool CalculateSHA1ForVnode(const kauth_cred_t credential,
const vfs_context_t context,
const vnode_t vnode,
char *out);
// Vnode ID string
uint64_t GetVnodeIDForVnode(const vfs_context_t context, const vnode_t vp);
// Cache management
bool AddToCache(const char *identifier,
const santa_action_t decision,
const uint64_t microsecs);
void CacheCheck(const char *identifier);
uint64_t CacheCount();
void ClearCache();
santa_action_t GetFromCache(const char *identifier);
// Listener invocation management
SInt32 GetListenerInvocations();
void IncrementListenerInvocations();
void DecrementListenerInvocations();
// Owning PID comparison
bool MatchesOwningPID(const pid_t other_pid);
// Returns the current system uptime in microseconds
uint64_t GetCurrentUptime();
// Starting and stopping the listener
kern_return_t StartListener();
kern_return_t StopListener();
private:
OSDictionary *cached_decisions_;
IORWLock *cached_decisions_lock_;
IOSharedDataQueue *dataqueue_;
IORWLock *dataqueue_lock_;
SInt32 listener_invocations_;
pid_t owning_pid_;
proc_t owning_proc_;
kauth_listener_t vnode_listener_;
kauth_listener_t process_listener_;
};
extern "C" {
/// The callback function for the Vnode scope
/// @param actor's credentials
/// @param data that was passed when the listener was registered
/// @param action that was requested
/// @param VFS context
/// @param Vnode being operated on
/// @param Parent Vnode. May be NULL.
/// @param Pointer to an errno-style error.
extern int vnode_scope_callback(kauth_cred_t credential,
void *idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3);
/// The callback function for the Process scope
/// @param actor's credentials
/// @param data that was passed when the listener was registered
/// @param action that was requested (KAUTH_PROCESS_{CANTRACE,CANSIGNAL})
/// @param target process
/// @param Pointer to an errno-style error.
/// @param unused
/// @param unused
extern int process_scope_callback(kauth_cred_t credential,
void *idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3);
} // extern C
#endif // SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H

View File

@@ -0,0 +1,37 @@
/// Copyright 2014 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.
#include "SantaDriver.h"
#define super IOService
#define SantaDriver com_google_SantaDriver
// The defines above can'be used in this function, we must use the full names.
OSDefineMetaClassAndStructors(com_google_SantaDriver, IOService);
bool SantaDriver::start(IOService *provider) {
if (!super::start(provider)) return false;
registerService();
LOGI("Loaded, version %s.", OSKextGetCurrentVersionString());
return true;
}
void SantaDriver::stop(IOService *provider) {
LOGI("Unloaded.");
}
#undef super

View File

@@ -0,0 +1,33 @@
/// Copyright 2014 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.
#ifndef SANTA__SANTA_DRIVER__SANTADRIVER_H
#define SANTA__SANTA_DRIVER__SANTADRIVER_H
#include <IOKit/IOService.h>
#include <libkern/OSKextLib.h>
#include "SantaDecisionManager.h"
#include "SNTLogging.h"
/// The driver class, which provides just the start/stop functions.
class com_google_SantaDriver : public IOService {
OSDeclareDefaultStructors(com_google_SantaDriver);
public:
bool start(IOService *provider);
void stop(IOService *provider);
};
#endif // SANTA__SANTA_DRIVER__SANTADRIVER_H

View File

@@ -0,0 +1,308 @@
/// Copyright 2014 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.
#include "SantaDriverClient.h"
#define super IOUserClient
#define SantaDriverClient com_google_SantaDriverClient
// The defines above can'be used in this function, must use the full names.
OSDefineMetaClassAndStructors(com_google_SantaDriverClient, IOUserClient);
# pragma mark Driver Management
bool SantaDriverClient::initWithTask(
task_t owningTask, void *securityID, UInt32 type) {
if (clientHasPrivilege(
owningTask, kIOClientPrivilegeAdministrator) != KERN_SUCCESS) {
LOGW("Unprivileged client attempted to connect.");
return false;
}
if (!super::initWithTask(owningTask, securityID, type)) return false;
return true;
}
bool SantaDriverClient::start(IOService *provider) {
fProvider = OSDynamicCast(com_google_SantaDriver, provider);
if (!fProvider) return false;
if (!super::start(provider)) return false;
fSDMLock = IOLockAlloc();
return true;
}
void SantaDriverClient::stop(IOService *provider) {
super::stop(provider);
}
IOReturn SantaDriverClient::clientClose() {
close();
terminate(kIOServiceSynchronous);
fProvider = NULL;
return kIOReturnSuccess;
}
bool SantaDriverClient::terminate(IOOptionBits options) {
// We have to lock before this check in case the client exits and the kext
// is unloaded very shortly afterwards.
IOLockLock(fSDMLock);
if (fSDM) {
fSDM->StopListener();
// Ask santad to shutdown
santa_message_t message;
message.action = ACTION_REQUEST_SHUTDOWN;
message.userId = 0;
message.pid = 0;
message.vnode_id = 0;
fSDM->PostToQueue(message);
LOGI("Client disconnected.");
fSDM->release();
fSDM = NULL;
}
IOLockUnlock(fSDMLock);
if (fProvider && fProvider->isOpen(this)) fProvider->close(this);
return super::terminate(options);
}
IOReturn SantaDriverClient::registerNotificationPort(mach_port_t port,
UInt32 type,
UInt32 ref) {
if ((!fDataQueue) || (port == MACH_PORT_NULL)) return kIOReturnError;
fDataQueue->setNotificationPort(port);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::clientMemoryForType(UInt32 type,
IOOptionBits *options,
IOMemoryDescriptor **memory) {
*memory = NULL;
*options = 0;
if (type == kIODefaultMemoryType) {
if (!fSharedMemory) return kIOReturnNoMemory;
fSharedMemory->retain(); // client will decrement this ref
*memory = fSharedMemory;
return fSDM->StartListener();
}
return kIOReturnNoMemory;
}
#pragma mark Callable Methods
IOReturn SantaDriverClient::open() {
if (isInactive()) return kIOReturnNotAttached;
if (!fProvider->open(this)) {
LOGW("A second client tried to connect.");
return kIOReturnExclusiveAccess;
}
fDataQueue = IOSharedDataQueue::withCapacity((sizeof(santa_message_t) +
DATA_QUEUE_ENTRY_HEADER_SIZE)
* kMaxQueueEvents);
if (!fDataQueue) return kIOReturnNoMemory;
fSharedMemory = fDataQueue->getMemoryDescriptor();
if (!fSharedMemory) {
fDataQueue->release();
fDataQueue = NULL;
return kIOReturnVMError;
}
IOLockLock(fSDMLock);
fSDM = SantaDecisionManager::WithQueueAndPID(fDataQueue, proc_selfpid());
IOLockUnlock(fSDMLock);
LOGI("Client connected, PID: %d.", proc_selfpid());
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_open(
SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
return target->open();
}
IOReturn SantaDriverClient::close() {
if (!fProvider) return kIOReturnNotAttached;
if (fProvider->isOpen(this)) fProvider->close(this);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_close(
SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
return target->close();
}
IOReturn SantaDriverClient::allow_binary(const uint64_t vnode_id) {
char vnode_id_str[21];
snprintf(vnode_id_str, sizeof(vnode_id_str), "%llu", vnode_id);
fSDM->AddToCache(vnode_id_str,
ACTION_RESPOND_CHECKBW_ALLOW,
fSDM->GetCurrentUptime());
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_allow_binary(
SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
return target->allow_binary(
*(static_cast<const uint64_t *>(arguments->scalarInput)));
}
IOReturn SantaDriverClient::deny_binary(const uint64_t vnode_id) {
char vnode_id_str[21];
snprintf(vnode_id_str, sizeof(vnode_id_str), "%llu", vnode_id);
fSDM->AddToCache(vnode_id_str,
ACTION_RESPOND_CHECKBW_DENY,
fSDM->GetCurrentUptime());
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_deny_binary(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
return target->deny_binary(
*(static_cast<const uint64_t *>(arguments->scalarInput)));
}
IOReturn SantaDriverClient::clear_cache() {
fSDM->ClearCache();
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_clear_cache(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
return target->clear_cache();
}
IOReturn SantaDriverClient::cache_count(uint64_t *output) {
*output = fSDM->CacheCount();
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_cache_count(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
return target->cache_count(&(arguments->scalarOutput[0]));
}
#pragma mark Method Resolution
IOReturn SantaDriverClient::externalMethod(
UInt32 selector,
IOExternalMethodArguments *arguments,
IOExternalMethodDispatch *dispatch,
OSObject *target,
void *reference) {
// Array of methods callable by clients. The order of these must match the
// order of the items in |SantaDriverMethods| in SNTKernelCommon.h
IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
{
reinterpret_cast<IOExternalMethodAction>(&SantaDriverClient::static_open),
0, // input scalar
0, // input struct
0, // output scalar
0 // output struct
},
{
reinterpret_cast<IOExternalMethodAction>(
&SantaDriverClient::static_close),
0,
0,
0,
0
},
{
reinterpret_cast<IOExternalMethodAction>(
&SantaDriverClient::static_allow_binary),
1,
0,
0,
0
},
{
reinterpret_cast<IOExternalMethodAction>(
&SantaDriverClient::static_deny_binary),
1,
0,
0,
0
},
{
reinterpret_cast<IOExternalMethodAction>(
&SantaDriverClient::static_clear_cache),
0,
0,
0,
0
},
{
reinterpret_cast<IOExternalMethodAction>(
&SantaDriverClient::static_cache_count),
0,
0,
1,
0
}
};
if (selector < static_cast<UInt32>(kSantaUserClientNMethods)) {
dispatch = &(sMethods[selector]);
if (!target) target = this;
} else {
return kIOReturnBadArgument;
}
return super::externalMethod(selector,
arguments,
dispatch,
target,
reference);
}
#undef super

View File

@@ -0,0 +1,112 @@
/// Copyright 2014 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.
#ifndef SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
#define SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
#include <IOKit/IOUserClient.h>
#include <IOKit/IOSharedDataQueue.h>
#include <IOKit/IOLib.h>
#include <IOKit/IODataQueueShared.h>
#include <libkern/crypto/sha1.h>
#include <sys/kauth.h>
#include <sys/vnode.h>
#include <sys/proc.h>
#include "SantaDecisionManager.h"
#include "SantaDriver.h"
#include "SantaMessage.h"
#include "SNTKernelCommon.h"
// The maximum number of messages can be kept in the IODataQueue at any time.
const int kMaxQueueEvents = 64;
/// This class is instantiated by IOKit when a new client process attempts to
/// connect to the driver. Starting, stopping, handling connections, allocating
/// shared memory and establishing a data queue is handled here.
///
/// Documentation on how the IOUserClient parts of this code work can be found
/// here:
/// @link https://developer.apple.com/library/mac/samplecode/SimpleUserClient/Listings/User_Client_Info_txt.html
class com_google_SantaDriverClient : public IOUserClient {
OSDeclareDefaultStructors(com_google_SantaDriverClient);
private:
IOSharedDataQueue *fDataQueue;
IOMemoryDescriptor *fSharedMemory;
com_google_SantaDriver *fProvider;
SantaDecisionManager *fSDM;
IOLock *fSDMLock;
public:
bool start(IOService *provider);
void stop(IOService *provider);
IOReturn clientClose();
bool terminate(IOOptionBits options);
bool initWithTask(task_t owningTask, void *securityID, UInt32 type);
IOReturn registerNotificationPort(
mach_port_t port, UInt32 type, UInt32 refCon);
IOReturn clientMemoryForType(
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory);
IOReturn externalMethod(
UInt32 selector,
IOExternalMethodArguments *arguments,
IOExternalMethodDispatch *dispatch,
OSObject *target, void *reference);
IOReturn open();
static IOReturn static_open(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
IOReturn close();
static IOReturn static_close(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to allow a binary.
IOReturn allow_binary(uint64_t vnode_id);
static IOReturn static_allow_binary(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to deny a binary.
IOReturn deny_binary(uint64_t vnode_id);
static IOReturn static_deny_binary(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to empty the cache.
IOReturn clear_cache();
static IOReturn static_clear_cache(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in the cache
IOReturn cache_count(uint64_t *output);
static IOReturn static_cache_count(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
};
#endif // SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H

View File

@@ -0,0 +1,31 @@
/// Copyright 2014 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.
#include "SantaMessage.h"
OSDefineMetaClassAndStructors(SantaMessage, OSObject);
uint64_t SantaMessage::getMicrosecs() const {
return microsecs_;
}
santa_action_t SantaMessage::getAction() const {
return action_;
}
void SantaMessage::setAction(const santa_action_t action,
const uint64_t microsecs) {
action_ = action;
microsecs_ = microsecs;
}

View File

@@ -0,0 +1,42 @@
/// Copyright 2014 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.
#ifndef SANTA__SANTA_DRIVER__SANTAMESSAGE_H
#define SANTA__SANTA_DRIVER__SANTAMESSAGE_H
#include <libkern/c++/OSObject.h>
#include "SNTKernelCommon.h"
/// An OSObject wrapper around a @c santa_action_t and a time.
/// Only OSObject subclasses can be inserted into an OSDictionary.
class SantaMessage : public OSObject {
OSDeclareDefaultStructors(SantaMessage)
private:
santa_action_t action_;
uint64_t microsecs_;
public:
// Returns the time the action was last set.
uint64_t getMicrosecs() const;
// Returns the set action.
santa_action_t getAction() const;
// Sets the acion and receive time.
void setAction(const santa_action_t action, const uint64_t microsecs);
};
#endif // SANTA__SANTA_DRIVER__SANTAMESSAGE_H

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.google.santactl</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleShortVersionString</key>
<string>0.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.7</string>
<key>CSFlags</key>
<string>kill</string>
</dict>
</plist>

View File

@@ -0,0 +1,3 @@
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

View File

@@ -0,0 +1,76 @@
/// Copyright 2014 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.
@class SNTXPCConnection;
/// Protocol that each command must adhere to.
@protocol SNTCommand <NSObject>
/// Return YES if command requires root.
+ (BOOL)requiresRoot;
/// A small summary of the command, to be printed with the list of available commands
+ (NSString *)shortHelpText;
/// A longer description of the command when the user runs "santactl help x"
+ (NSString *)longHelpText;
@optional
/// Either of the following two methods needs to be implemented
/// Called when the user is running the command
/// @param arguments an array of arguments passed in
/// @note This method (or one of the methods it calls) is responsible for calling exit().
+ (void)runWithArguments:(NSArray *)arguments;
/// Called when the user is running the command
/// @param arguments an array of arguments passed in
/// @param connection to santad. Will be nil if connection failed.
/// @note This method (or one of the methods it calls) is responsible for calling exit().
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn;
@end
/// Responsible for maintaining the list of available commands by name, printing their help text
/// when requested and launching them when requested. All of the methods in this class are
/// class methods because the @c registerCommand:named: method is called by the @c +load method
/// of each command class and so we cannot rely on its instantiation.
@interface SNTCommandController : NSObject
/// Register a new command with the specified name. Do not use this directly, use the
/// @c REGISTER_COMMAND_NAME macro instead.
+ (void)registerCommand:(Class<SNTCommand>)command named:(NSString *)name;
/// Returns a usage string listing all of the available commands
+ (NSString *)usage;
/// Returns the descriptive text for the given command, if it exists
+ (NSString *)helpForCommandWithName:(NSString *)command;
/// Returns YES if @c commandName exists.
+ (BOOL)hasCommandWithName:(NSString *)commandName;
/// Runs the given command with the given arguments.
/// @c commandName the name of a previously-registered command
/// @c arguments an array of arguments to pass to the command
/// @return an integer return code to exit with.
+ (int)runCommandWithName:(NSString *)commandName arguments:(NSArray *)arguments;
@end
/// This macro registers a given class as a command with the name passed in @c a (which must be an
/// NSString). Must be placed just inside the implementation of the class, ideally at the top.
/// The class that uses this macro must implement the SNTCommand protcol.
#define REGISTER_COMMAND_NAME(a) \
+ (void)load { [SNTCommandController registerCommand:[self class] named:a]; }

View File

@@ -0,0 +1,132 @@
/// Copyright 2014 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 "SNTCommandController.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@implementation SNTCommandController
/// A dictionary to hold all of the available commands.
/// Key is the name of the command
/// Value is the Class
static NSMutableDictionary *registeredCommands;
+ (void)registerCommand:(Class<SNTCommand>)command named:(NSString *)name {
if (!registeredCommands) {
registeredCommands = [NSMutableDictionary dictionary];
}
registeredCommands[name] = command;
}
+ (NSString *)usage {
NSMutableString *helpText = [[NSMutableString alloc] init];
int longestCommandName = 0;
for (NSString *cmdName in registeredCommands) {
if ([cmdName length] > longestCommandName) {
longestCommandName = (int)[cmdName length];
}
}
for (NSString *cmdName in
[[registeredCommands allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]) {
Class<SNTCommand> cmd = registeredCommands[cmdName];
[helpText appendFormat:@"\t%*s - %@\n", longestCommandName,
[cmdName UTF8String], [cmd shortHelpText]];
}
return helpText;
}
+ (NSString *)helpForCommandWithName:(NSString *)commandName {
Class<SNTCommand> command = registeredCommands[commandName];
if (command) {
NSMutableString *helpText = [[NSMutableString alloc] init];
[helpText appendFormat:@"Help for '%@':\n", commandName];
[helpText appendString:[command longHelpText]];
return helpText;
}
return nil;
}
+ (SNTXPCConnection *)connectToDaemon {
// TODO(rah): Re-factor this so that successfully establishing the connection runs the command,
// instead of having to sleep until the connection is made.
SNTXPCConnection *daemonConn =
[[SNTXPCConnection alloc] initClientWithName:[SNTXPCControlInterface serviceId]
options:NSXPCConnectionPrivileged];
daemonConn.remoteInterface = [SNTXPCControlInterface controlInterface];
__block int connected = -1;
daemonConn.acceptedHandler = ^{
connected = 1;
};
daemonConn.rejectedHandler = ^{
connected = 0;
printf("The daemon rejected the connection\n");
exit(1);
};
daemonConn.invalidationHandler = ^{
connected = 0;
printf("An error occurred communicating with the daemon\n");
exit(1);
};
[daemonConn resume];
int idx = 10;
do {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
--idx;
} while (connected == -1 && idx > 0);
if (connected > 0) {
return daemonConn;
} else {
return nil;
}
}
+ (BOOL)hasCommandWithName:(NSString *)commandName {
return ([registeredCommands objectForKey:commandName] != nil);
}
+ (int)runCommandWithName:(NSString *)commandName arguments:(NSArray *)arguments {
Class<SNTCommand> command = registeredCommands[commandName];
if (command) {
if ([command requiresRoot] && getuid() != 0) {
printf("The command '%s' requires root privileges.\n", [commandName UTF8String]);
return 2;
}
if ([(id)command respondsToSelector:@selector(runWithArguments:daemonConnection:)]) {
[command runWithArguments:arguments daemonConnection:[self connectToDaemon]];
} else if ([(id)command respondsToSelector:@selector(runWithArguments:)]) {
[command runWithArguments:arguments];
} else {
printf("The command '%s' has not been implemented correctly.\n", [commandName UTF8String]);
}
// The command is responsible for quitting.
[[NSRunLoop mainRunLoop] run];
}
return 128;
}
@end

View File

@@ -0,0 +1,107 @@
/// Copyright 2014 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 "SNTCommandController.h"
#include "SNTLogging.h"
#import "SNTBinaryInfo.h"
#import "SNTCertificate.h"
#import "SNTCodesignChecker.h"
@interface SNTCommandBinaryInfo : NSObject<SNTCommand>
@end
@implementation SNTCommandBinaryInfo
REGISTER_COMMAND_NAME(@"binaryinfo");
+ (BOOL)requiresRoot {
return NO;
}
+ (NSString *)shortHelpText {
return @"Prints information about the given binary.";
}
+ (NSString *)longHelpText {
return (@"The details provided will be the same ones Santa uses to make a decision about binaries"
@"This includes SHA-1, code signing information and the type of binary");
}
+ (void)runWithArguments:(NSArray *)arguments {
NSString *filePath = [arguments firstObject];
if (!filePath) {
LOGI(@"Missing file path");
exit(1);
}
BOOL directory;
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&directory]) {
LOGI(@"File does not exist");
exit(1);
}
if (directory) {
LOGI(@"Not a regular file");
exit(1);
}
// Convert to absolute, standardized path
filePath = [filePath stringByStandardizingPath];
if (![filePath isAbsolutePath]) {
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
filePath = [cwd stringByAppendingPathComponent:filePath];
}
LOGI(@"Info for file: %@", filePath);
LOGI(@"-----------------------------------------------------------");
SNTBinaryInfo *ftd = [[SNTBinaryInfo alloc] initWithPath:filePath];
LOGI(@"%-20s: %@", "SHA-1", [ftd SHA1]);
NSArray *archs = [ftd architectures];
if (archs) {
LOGI(@"%-20s: %@ (%@)", "Type", [ftd machoType], [archs componentsJoinedByString:@", "]);
} else {
LOGI(@"%-20s: %@", "Type", [ftd machoType]);
}
SNTCodesignChecker *csc = [[SNTCodesignChecker alloc] initWithBinaryPath:filePath];
LOGI(@"%-20s: %s", "Code-signed", (csc) ? "Yes" : "No");
if (csc) {
LOGI(@"Signing chain\n");
[csc.certificates enumerateObjectsUsingBlock:^(SNTCertificate *c,
unsigned long idx,
BOOL *stop) {
idx++; // index from 1
LOGI(@" %2lu. %-20s: %@", idx, "SHA-1", c.SHA1);
LOGI(@" %-20s: %@", "Common Name", c.commonName);
LOGI(@" %-20s: %@", "Organization", c.orgName);
LOGI(@" %-20s: %@", "Organizational Unit", c.orgUnit);
LOGI(@" %-20s: %@", "Valid From", c.validFrom);
LOGI(@" %-20s: %@", "Valid Until", c.validUntil);
LOGI(@"");
}];
}
exit(0);
}
@end

View File

@@ -0,0 +1,53 @@
/// Copyright 2014 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 "SNTCommandController.h"
#import "SNTLogging.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTCommandFlushCache : NSObject<SNTCommand>
@end
@implementation SNTCommandFlushCache
REGISTER_COMMAND_NAME(@"flushcache");
+ (BOOL)requiresRoot {
return YES;
}
+ (NSString *)shortHelpText {
return @"Flush the kernel cache";
}
+ (NSString *)longHelpText {
return @"Flushes the in-kernel cache of whitelisted binaries.\n\n"
@"Returns 0 if successful, 1 otherwise";
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
[[daemonConn remoteObjectProxy] flushCache:^(BOOL success) {
if (success) {
LOGI(@"Cache flush requested");
exit(0);
} else {
LOGE(@"Cache flush failed");
exit(1);
}
}];
}
@end

80
Source/santactl/main.m Normal file
View File

@@ -0,0 +1,80 @@
/// Copyright 2014 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 "SNTCommandController.h"
/**
* santactl is a command-line utility for managing Santa.
* As it can be used for a number of distinct operations, its operation is split into different
* 'commands' which are expected to be the first argument to the binary. The main function below
* is simply responsible for either passing control to the specified command or printing a useful
* usage string.
**/
void print_usage() {
printf("Usage: santactl:\n%s\n", [[SNTCommandController usage] UTF8String]);
}
void print_unknown_command(NSString *commandName) {
printf("Unknown command: %s\n", [commandName UTF8String]);
}
void print_string(NSString *string) {
printf("%s\n", [string UTF8String]);
}
int main(int argc, const char *argv[]) {
// Do not buffer stdout
setbuf(stdout, NULL);
@autoreleasepool {
NSMutableArray *arguments = [[[NSProcessInfo processInfo] arguments] mutableCopy];
[arguments removeObjectAtIndex:0];
NSString *commandName = [arguments firstObject];
if (!commandName ||
[commandName isEqualToString:@"usage"] ||
[commandName isEqualToString:@"commands"]) {
print_usage();
return 1;;
}
[arguments removeObjectAtIndex:0];
if ([commandName isEqualToString:@"help"]) {
if ([arguments count]) {
// User wants help for specific command
commandName = [arguments firstObject];
if (![SNTCommandController hasCommandWithName:commandName]) {
print_unknown_command(commandName);
return 1;
} else {
print_string([SNTCommandController helpForCommandWithName:commandName]);
return 1;
}
} else {
// User generally wants help
print_usage();
return 0;
}
}
// User knows what command they want, does it exist?
if (![SNTCommandController hasCommandWithName:commandName]) {
print_unknown_command(commandName);
return 128;
}
return [SNTCommandController runCommandWithName:commandName arguments:arguments];
}
}

View File

@@ -0,0 +1,114 @@
/// Copyright 2014 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 "SNTCommandController.h"
#include <IOKit/kext/KextManager.h>
#import "SNTBinaryInfo.h"
#import "SNTKernelCommon.h"
#import "SNTLogging.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTCommandStatus : NSObject<SNTCommand>
@end
@implementation SNTCommandStatus
REGISTER_COMMAND_NAME(@"status");
+ (BOOL)requiresRoot {
return NO;
}
+ (NSString *)shortHelpText {
return @"Get status about Santa";
}
+ (NSString *)longHelpText {
return @"Returns status information about Santa.";
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
// Version information
LOGI(@">>> Versions");
LOGI(@"%-30s | %@", "santa-driver version", [self kextVersion]);
LOGI(@"%-30s | %@", "santad version", [self daemonVersion]);
LOGI(@"%-30s | %@",
"santactl version",
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]);
LOGI(@"%-30s | %@", "SantaGUI version", [self guiVersion]);
LOGI(@"");
// Kext status
__block uint64_t cacheCount = -1;
[[daemonConn remoteObjectProxy] cacheCount:^(uint64_t count) {
cacheCount = count;
}];
do { usleep(5000); } while (cacheCount == -1);
LOGI(@">>> Kernel Info");
LOGI(@"%-30s | %d", "Kernel cache count", cacheCount);
LOGI(@"");
// Database counts
__block uint64_t eventCount = 1, binaryRuleCount = -1, certRuleCount = -1;
[[daemonConn remoteObjectProxy] databaseRuleCounts:^(uint64_t binary, uint64_t certificate) {
binaryRuleCount = binary;
certRuleCount = certificate;
}];
[[daemonConn remoteObjectProxy] databaseEventCount:^(uint64_t count) {
eventCount = count;
}];
do { usleep(5000); } while (eventCount == -1 || binaryRuleCount == -1 || certRuleCount == -1);
LOGI(@">>> Database Info");
LOGI(@"%-30s | %d", "Binary Rules", binaryRuleCount);
LOGI(@"%-30s | %d", "Certificate Rules", certRuleCount);
LOGI(@"%-30s | %d", "Events Pending Upload", eventCount);
LOGI(@"");
exit(0);
}
+ (NSString *)kextVersion {
NSDictionary *loadedKexts = CFBridgingRelease(
KextManagerCopyLoadedKextInfo((__bridge CFArrayRef)@[ @(USERCLIENT_ID) ],
(__bridge CFArrayRef)@[ @"CFBundleVersion" ]));
if (loadedKexts[@(USERCLIENT_ID)] && loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"]) {
return loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"];
}
SNTBinaryInfo *driverInfo =
[[SNTBinaryInfo alloc] initWithPath:@"/System/Library/Extensions/santa-driver.kext"];
if (driverInfo) {
return [driverInfo.bundleVersion stringByAppendingString:@" (unloaded)"];
}
return @"not found";
}
+ (NSString *)daemonVersion {
SNTBinaryInfo *daemonInfo = [[SNTBinaryInfo alloc] initWithPath:@"/usr/libexec/santad"];
return daemonInfo.bundleVersion;
}
+ (NSString *)guiVersion {
SNTBinaryInfo *guiInfo =
[[SNTBinaryInfo alloc] initWithPath:@"/Applications/Santa.app/Contents/MacOS/Santa"];
return guiInfo.bundleVersion;
}
@end

View File

@@ -0,0 +1,54 @@
/// Copyright 2014 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 authenticating NSURLSession, which can do both pinned verification of the SSL server
/// and handle client certificate authentication from the keychain.
@interface SNTAuthenticatingURLSession : NSObject<NSURLSessionDelegate>
/// The underlying session. Pass this session to NSURLRequest methods.
@property(readonly) NSURLSession *session;
/// If set, this is the user-agent to send with requests, otherwise remains the default
/// CFNetwork-based name.
@property(nonatomic) NSString *userAgent;
/// If set, the server that we connect to _must_ match this string. Redirects to other
/// hosts will not be allowed.
@property(nonatomic) NSString *serverHostname;
/// This should be PEM data containing one or more certificates to use to verify the server's
/// certificate chain. This will override the trusted roots in the System Roots.
@property(nonatomic) NSData *serverRootsPemData;
/// If set and client certificate authentication is needed, will search the keychain for a
/// certificate matching this common name and use that for authentication
/// @note: Not case sensitive
/// @note: If multiple matching certificates are found, the first one is used.
/// @note: If this property is not set and neither is |clientCertIssuerCn|, the allowed issuers
/// provided by the server will be used to find a matching certificate.
@property(nonatomic) NSString *clientCertCommonName;
/// If set and client certificate authentication is needed, will search the keychain for a
/// certificate issued by an issuer with this name and use that for authentication.
///
/// @note: Not case sensitive
/// @note: If multiple matching certificates are found, the first one is used.
/// @note: If this property is not set and neither is |clientCertCommonName|, the allowed issuers
/// provided by the server will be used to find a matching certificate.
@property(nonatomic) NSString *clientCertIssuerCn;
/// Designated initializer
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration;
@end

View File

@@ -0,0 +1,261 @@
/// Copyright 2014 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 "SNTAuthenticatingURLSession.h"
#import "SNTCertificate.h"
#import "SNTConfigurator.h"
#import "SNTDERDecoder.h"
#import "SNTLogging.h"
@implementation SNTAuthenticatingURLSession
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (self) {
_session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:nil];
}
return self;
}
- (instancetype)init {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
[config setTLSMinimumSupportedProtocol:kTLSProtocol12];
[config setHTTPShouldUsePipelining:YES];
return [self initWithSessionConfiguration:config];
}
#pragma mark User Agent property
- (NSString *)userAgent {
return _session.configuration.HTTPAdditionalHeaders[@"User-Agent"];
}
- (void)setUserAgent:(NSString *)userAgent {
NSMutableDictionary *addlHeaders = [_session.configuration.HTTPAdditionalHeaders mutableCopy];
addlHeaders[@"User-Agent"] = userAgent;
_session.configuration.HTTPAdditionalHeaders = addlHeaders;
}
#pragma mark NSURLSessionDelegate methods
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler {
NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;
if (challenge.previousFailureCount > 0) {
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
return;
}
if (self.serverHostname && ![self.serverHostname isEqual:protectionSpace.host]) {
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
return;
}
if (![protectionSpace.protocol isEqual:NSURLProtectionSpaceHTTPS]) {
LOGD(@"Protection Space: %@ is not a secure protocol", protectionSpace.protocol);
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
return;
}
if (!protectionSpace.receivesCredentialSecurely) {
LOGD(@"Protection Space: secure authentication or protocol cannot be established");
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
return;
}
NSString *authMethod = [protectionSpace authenticationMethod];
if (authMethod == NSURLAuthenticationMethodClientCertificate && NO) {
NSURLCredential *cred = [self clientCredentialForProtectionSpace:protectionSpace];
if (cred) {
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
return;
} else {
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
return;
}
} else if (authMethod == NSURLAuthenticationMethodServerTrust) {
NSURLCredential *cred = [self serverCredentialForProtectionSpace:protectionSpace];
if (cred) {
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
return;
} else {
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
return;
}
}
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
#pragma mark Private Helpers for URLSession:didReceiveChallenge:completionHandler:
/// Handles the process of locating a valid client certificate for authentication.
/// Operates in one of three modes, depending on the configuration in config.plist
///
/// Mode 1: if syncClientAuthCertificateCn is set, look for an identity in the keychain with a
/// matching common name and return it.
/// Mode 2: if syncClientAuthCertificateIssuer is set, look for an identity in the keychain with a
/// matching issuer common name and return it.
/// Mode 3: use the list of issuer details sent down by the server to find an identity in the
/// keychain.
///
/// If a valid identity cannot be found, returns nil.
- (NSURLCredential *)clientCredentialForProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
__block OSStatus err = errSecSuccess;
CFArrayRef cfIdentities = NULL;
err = SecItemCopyMatching((__bridge CFDictionaryRef)@{
(id)kSecClass : (id)kSecClassIdentity,
(id)kSecReturnRef : @YES,
(id)kSecMatchLimit : (id)kSecMatchLimitAll }, (CFTypeRef *)&cfIdentities);
if (err != noErr) {
LOGD(@"Client Trust: Failed to load client identities, SecItemCopyMatching returned: %d",
(int)err);
return nil;
}
NSArray *identities = CFBridgingRelease(cfIdentities);
__block SecIdentityRef _foundIdentity;
// Manually iterate through available identities to find one with an allowed issuer.
[identities enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
SecIdentityRef identityRef = (__bridge SecIdentityRef)obj;
SecCertificateRef certificate = NULL;
err = SecIdentityCopyCertificate(identityRef, &certificate);
if (err != errSecSuccess) {
LOGD(@"Client Trust: Failed to read certificate data: %d. Skipping identity", (int)err);
return;
}
SNTCertificate *clientCert = [[SNTCertificate alloc] initWithSecCertificateRef:certificate];
CFRelease(certificate);
// Switch identity finding method depending on config
if (self.clientCertCommonName) {
if ([clientCert.commonName compare:self.clientCertCommonName
options:NSCaseInsensitiveSearch]) {
LOGD(@"Client Trust: Valid client identity %@", clientCert);
_foundIdentity = identityRef;
CFRetain(_foundIdentity);
*stop = YES;
return; // return from enumeration block
}
} else if (self.clientCertIssuerCn) {
if ([clientCert.issuerCommonName compare:self.clientCertIssuerCn
options:NSCaseInsensitiveSearch]) {
LOGD(@"Client Trust: Valid client identity %@", clientCert);
_foundIdentity = identityRef;
CFRetain(_foundIdentity);
*stop = YES;
return; // return from enumeration block
}
} else {
for (NSData *allowedIssuer in protectionSpace.distinguishedNames) {
SNTDERDecoder *decoder = [[SNTDERDecoder alloc] initWithData:allowedIssuer];
if (!decoder) continue;
if ([clientCert.issuerCommonName isEqual:decoder.commonName] &&
[clientCert.issuerCountryName isEqual:decoder.countryName] &&
[clientCert.issuerOrgName isEqual:decoder.organizationName] &&
[clientCert.issuerOrgUnit isEqual:decoder.organizationalUnit]) {
LOGD(@"Client Trust: Valid client identity %@", clientCert);
_foundIdentity = identityRef;
CFRetain(_foundIdentity);
*stop = YES;
return; // return from enumeration block
}
}
}
}];
if (_foundIdentity == NULL) {
return nil;
}
return [NSURLCredential credentialWithIdentity:_foundIdentity
certificates:nil
persistence:NSURLCredentialPersistenceForSession];
}
/// Handles the process of evaluating the server's certificate chain.
/// Operates in one of three modes, depending on the configuration in config.plist
///
/// Mode 1: if syncServerAuthRootsData is set, evaluates the server's certificate chain contains
/// one of the certificates in the PEM data in the config plist.
/// Mode 2: if syncServerAuthRootsFile is set, evaluates the server's certificate chain contains
/// one of the certificates in the PEM data in the file specified.
/// Mode 3: evaluates the server's certificate chain is trusted by the keychain.
///
/// If the server's certificate chain does not evaluate for any reason, returns nil.
- (NSURLCredential *)serverCredentialForProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
SecTrustRef serverTrust = protectionSpace.serverTrust;
if (serverTrust == NULL) {
LOGD(@"Server Trust: No server trust information available");
return nil;
}
OSStatus err = errSecSuccess;
if (self.serverRootsPemData) {
NSString *pemStrings = [[NSString alloc] initWithData:self.serverRootsPemData
encoding:NSASCIIStringEncoding];
NSArray *certs = [SNTCertificate certificatesFromPEM:pemStrings];
// Make a new array of the SecCertificateRef's from the SNTCertificate's.
NSMutableArray *certRefs = [[NSMutableArray alloc] initWithCapacity:certs.count];
for (SNTCertificate *cert in certs) {
[certRefs addObject:(id)cert.certRef];
}
// Set this array of certs as the anchors to trust.
err = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)certRefs);
if (err != errSecSuccess) {
LOGE(@"Server Trust: Could not set anchor certificates");
return nil;
}
}
// Evaluate the server's cert chain.
SecTrustResultType result = kSecTrustResultInvalid;
err = SecTrustEvaluate(serverTrust, &result);
if (err != errSecSuccess) {
LOGE(@"Server Trust: Unable to evaluate certificate chain for server");
return nil;
}
// Print details about the server's leaf certificate.
SecCertificateRef firstCert = SecTrustGetCertificateAtIndex(protectionSpace.serverTrust, 0);
if (firstCert) {
SNTCertificate *cert = [[SNTCertificate alloc] initWithSecCertificateRef:firstCert];
LOGD(@"Server Trust: Server leaf cert: %@", cert);
}
// Having a trust level "unspecified" by the user is the usual result, described at
// https://developer.apple.com/library/mac/qa/qa1360
if (result != kSecTrustResultProceed && result != kSecTrustResultUnspecified) {
LOGE(@"Server Trust: Server isn't trusted. SecTrustResultType: %d", result);
return nil;
}
return [NSURLCredential credentialForTrust:serverTrust];
}
@end

View File

@@ -0,0 +1,219 @@
/// Copyright 2014 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 "SNTCommandController.h"
#import "SNTAuthenticatingURLSession.h"
#import "SNTCommandSyncEventUpload.h"
#import "SNTCommandSyncLogUpload.h"
#import "SNTCommandSyncPostflight.h"
#import "SNTCommandSyncPreflight.h"
#import "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncStatus.h"
#import "SNTConfigurator.h"
#import "SNTDropRootPrivs.h"
#import "SNTLogging.h"
#import "SNTSystemInfo.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTCommandSync : NSObject<SNTCommand>
@property NSURLSession *session;
@property SNTXPCConnection *daemonConn;
@property SNTCommandSyncStatus *progress;
@end
@implementation SNTCommandSync
REGISTER_COMMAND_NAME(@"sync");
+ (BOOL)requiresRoot {
return NO;
}
+ (NSString *)shortHelpText {
return @"Synchronizes Santa with the server";
}
+ (NSString *)longHelpText {
return @"";
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
SNTConfigurator *config = [SNTConfigurator configurator];
// Ensure we have no privileges
if (!DropRootPrivileges()) {
LOGE(@"Failed to drop root privileges. Exiting.");
exit(1);
}
// Dropping root privileges to the 'nobody' user causes the default NSURLCache to throw
// sandbox errors, which are benign but annoying. This line disables the cache entirely.
[NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:0
diskCapacity:0
diskPath:nil]];
SNTCommandSync *s = [[self alloc] init];
SNTAuthenticatingURLSession *authURLSession = [[SNTAuthenticatingURLSession alloc] init];
authURLSession.userAgent = @"santactl-sync/";
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
if (santactlVersion) {
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
}
// Configure server auth
if ([config syncServerAuthRootsFile]) {
NSData *rootsData = [NSData dataWithContentsOfFile:[config syncServerAuthRootsFile]];
authURLSession.serverRootsPemData = rootsData;
} else if ([config syncServerAuthRootsData]) {
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
}
// Configure client auth
if ([config syncClientAuthCertificateCn]) {
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
} else if ([config syncClientAuthCertificateIssuer]) {
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
}
s.session = [authURLSession session];
s.daemonConn = daemonConn;
// Gather some data needed during some sync stages
s.progress = [[SNTCommandSyncStatus alloc] init];
s.progress.syncBaseURL = config.syncBaseURL;
if (!s.progress.syncBaseURL) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
exit(1);
}
authURLSession.serverHostname = s.progress.syncBaseURL.host;
s.progress.machineID = config.machineIDOverride;
if (!s.progress.machineID || [s.progress.machineID isEqual:@""]) {
s.progress.machineID = [SNTSystemInfo hardwareUUID];
}
if (!s.progress.machineID || [s.progress.machineID isEqual:@""]) {
LOGE(@"Missing Machine ID. Can't sync without it.");
exit(1);
}
s.progress.machineOwner = config.machineOwner;
if (arguments.count == 2 && [[arguments firstObject] isEqual:@"singleevent"]) {
[s eventUploadSingleEvent:arguments[1]];
} else {
[s preflight];
}
}
- (void)preflight {
[SNTCommandSyncPreflight performSyncInSession:self.session
progress:self.progress
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Preflight complete");
if (self.progress.uploadLogURL) {
[self logUpload];
} else {
[self eventUpload];
}
} else {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
}];
}
- (void)logUpload {
[SNTCommandSyncLogUpload performSyncInSession:self.session
progress:self.progress
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Log upload complete");
[self eventUpload];
} else {
LOGE(@"Log upload failed, aborting run");
exit(1);
}
}];
}
- (void)eventUpload {
[SNTCommandSyncEventUpload performSyncInSession:self.session
progress:self.progress
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Event upload complete");
[self ruleDownload];
} else {
LOGE(@"Event upload failed, aborting run");
exit(1);
}
}];
}
- (void)eventUploadSingleEvent:(NSString *)sha1 {
[SNTCommandSyncEventUpload uploadSingleEventWithSHA1:sha1
session:self.session
progress:self.progress
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Event upload complete");
exit(0);
} else {
LOGW(@"Event upload failed");
exit(1);
}
}];
}
- (void)ruleDownload {
[SNTCommandSyncRuleDownload performSyncInSession:self.session
progress:self.progress
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Rule download complete");
[self postflight];
} else {
LOGE(@"Rule download failed, aborting run");
exit(1);
}
}];
}
- (void)postflight {
[SNTCommandSyncPostflight performSyncInSession:self.session
progress:self.progress
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Postflight complete");
LOGI(@"Sync completed successfully");
exit(0);
} else {
LOGE(@"Postflight failed");
exit(1);
}
}];
}
@end

View File

@@ -0,0 +1,31 @@
/// Copyright 2014 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.
@class SNTCommandSyncStatus;
@class SNTXPCConnection;
@interface SNTCommandSyncEventUpload : NSObject
+ (void)performSyncInSession:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
+ (void)uploadSingleEventWithSHA1:(NSString *)SHA1
session:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
@end

View File

@@ -0,0 +1,156 @@
/// Copyright 2014 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 "SNTCommandSyncEventUpload.h"
#include "SNTLogging.h"
#import "SNTCommandSyncStatus.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@implementation SNTCommandSyncEventUpload
+ (void)performSyncInSession:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[@"eventupload/" stringByAppendingString:progress.machineID]
relativeToURL:progress.syncBaseURL];
[[daemonConn remoteObjectProxy] databaseEventsPending:^(NSArray *events) {
if ([events count] == 0) {
handler(YES);
} else {
[self uploadEventsFromArray:events
toURL:url
inSession:session
batchSize:progress.eventBatchSize
daemonConn:daemonConn
completionHandler:handler];
}
}];
}
+ (void)uploadSingleEventWithSHA1:(NSString *)SHA1
session:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[@"eventupload/" stringByAppendingString:progress.machineID]
relativeToURL:progress.syncBaseURL];
[[daemonConn remoteObjectProxy] databaseEventForSHA1:SHA1 withReply:^(SNTStoredEvent *event) {
if (!event) {
handler(YES);
return;
}
[self uploadEventsFromArray:@[ event ]
toURL:url
inSession:session
batchSize:1
daemonConn:daemonConn
completionHandler:handler];
}];
}
+ (void)uploadEventsFromArray:(NSArray *)events
toURL:(NSURL *)url
inSession:(NSURLSession *)session
batchSize:(int32_t)batchSize
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSMutableArray *uploadEvents = [[NSMutableArray alloc] init];
NSMutableArray *eventIds = [NSMutableArray arrayWithCapacity:events.count];
for (SNTStoredEvent *event in events) {
NSMutableDictionary *newEvent = [@{
@"file_sha1": event.fileSHA1,
@"file_path": [event.filePath stringByDeletingLastPathComponent],
@"file_name": [event.filePath lastPathComponent],
@"executing_user": event.executingUser,
@"execution_time": @([event.occurrenceDate timeIntervalSince1970]),
@"decision": @(event.decision),
@"logged_in_users": event.loggedInUsers,
@"current_sessions": event.currentSessions} mutableCopy];
if (event.fileBundleID) newEvent[@"file_bundle_id"] = event.fileBundleID;
if (event.fileBundleName) newEvent[@"file_bundle_name"] = event.fileBundleName;
if (event.fileBundleVersion) newEvent[@"file_bundle_version"] = event.fileBundleVersion;
if (event.fileBundleVersionString) {
newEvent[@"file_bundle_version_string"] = event.fileBundleVersionString;
}
if (event.certSHA1) newEvent[@"cert_sha1"] = event.certSHA1;
if (event.certCN) newEvent[@"cert_cn"] = event.certCN;
if (event.certOrg) newEvent[@"cert_org"] = event.certOrg;
if (event.certOU) newEvent[@"cert_ou"] = event.certOU;
if (event.certValidFromDate) {
newEvent[@"cert_valid_from"] = @([event.certValidFromDate timeIntervalSince1970]);
}
if (event.certValidUntilDate) {
newEvent[@"cert_valid_until"] = @([event.certValidUntilDate timeIntervalSince1970]);
}
[uploadEvents addObject:newEvent];
[eventIds addObject:event.idx];
if (eventIds.count >= batchSize) break;
}
NSDictionary *uploadReq = @{@"events": uploadEvents};
NSData *requestBody;
@try {
requestBody = [NSJSONSerialization dataWithJSONObject:uploadReq options:0 error:nil];
} @catch (NSException *exception) {
LOGE(@"Failed to parse event into JSON");
}
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPMethod:@"POST"];
[req setHTTPBody:requestBody];
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if ([(NSHTTPURLResponse *)response statusCode] != 200) {
LOGD(@"HTTP Response Code: %d", [(NSHTTPURLResponse *)response statusCode]);
handler(NO);
} else {
LOGI(@"Uploaded %d events", eventIds.count);
[[daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:eventIds];
NSArray *nextEvents = [events subarrayWithRange:NSMakeRange(eventIds.count,
events.count - eventIds.count)];
if (nextEvents.count == 0) {
handler(YES);
} else {
[self uploadEventsFromArray:nextEvents
toURL:url
inSession:session
batchSize:batchSize
daemonConn:daemonConn
completionHandler:handler];
}
}
}] resume];
}
@end

View File

@@ -0,0 +1,25 @@
/// Copyright 2014 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.
@class SNTCommandSyncStatus;
@class SNTXPCConnection;
@interface SNTCommandSyncLogUpload : NSObject
+ (void)performSyncInSession:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
@end

View File

@@ -0,0 +1,88 @@
/// Copyright 2014 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 "SNTCommandSyncLogUpload.h"
#include "SNTCommonEnums.h"
#include "SNTLogging.h"
#import "SNTCommandSyncStatus.h"
@implementation SNTCommandSyncLogUpload
+ (void)performSyncInSession:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = progress.uploadLogURL;
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPMethod:@"POST"];
NSString *boundary = @"santa-sync-upload-boundary";
NSString *contentType =
[NSString stringWithFormat:@"multipart/form-data; charset=UTF-8; boundary=%@", boundary];
[req setValue:contentType forHTTPHeaderField:@"Content-Type"];
// General logs
NSMutableArray *logsToUpload = [@[ @"/var/log/santa.log",
@"/var/log/system.log" ] mutableCopy];
// Kernel Panics, santad & santactl crashes
NSString *diagsDir = @"/Library/Logs/DiagnosticReports/";
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:diagsDir];
NSString *file;
while (file = [dirEnum nextObject]) {
if ([[file pathExtension] isEqualToString: @"panic"] ||
[file hasPrefix:@"santad"] ||
[file hasPrefix:@"santactl"]) {
[logsToUpload addObject:[diagsDir stringByAppendingString:file]];
}
}
// Prepare the body of the request, encoded as a multipart/form-data.
// Along the way, gzip the individual log files (they'll be stored in blobstore gzipped, which is
// what we want) and append .gz to their filenames.
NSMutableData *reqBody = [[NSMutableData alloc] init];
for (NSString *log in logsToUpload) {
[reqBody appendData:
[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[reqBody appendData:
[[NSString stringWithFormat:@"Content-Disposition: multipart/form-data; "
@"name=\"files\"; "
@"filename=\"%@.gz\"\r\n", [log lastPathComponent]]
dataUsingEncoding:NSUTF8StringEncoding]];
[reqBody appendData:
[@"Content-Type: application/x-gzip\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[reqBody appendData:[NSData dataWithContentsOfFile:log]];
[reqBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
[reqBody appendData:
[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// Upload the logs
[[session uploadTaskWithRequest:req
fromData:reqBody
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if ([(NSHTTPURLResponse *)response statusCode] != 200) {
LOGD(@"HTTP Response Code: %d", [(NSHTTPURLResponse *)response statusCode]);
handler(NO);
} else {
LOGI(@"Uploaded %d logs", [logsToUpload count]);
handler(YES);
}
}] resume];
}
@end

View File

@@ -0,0 +1,25 @@
/// Copyright 2014 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.
@class SNTCommandSyncStatus;
@class SNTXPCConnection;
@interface SNTCommandSyncPostflight : NSObject
+ (void)performSyncInSession:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
@end

View File

@@ -0,0 +1,45 @@
/// Copyright 2014 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 "SNTCommandSyncPostflight.h"
#include "SNTLogging.h"
#import "SNTCommandSyncStatus.h"
@implementation SNTCommandSyncPostflight
+ (void)performSyncInSession:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[@"postflight/" stringByAppendingString:progress.machineID]
relativeToURL:progress.syncBaseURL];
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPMethod:@"POST"];
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if ([(NSHTTPURLResponse *)response statusCode] != 200) {
LOGD(@"HTTP Response Code: %d", [(NSHTTPURLResponse *)response statusCode]);
handler(NO);
} else {
handler(YES);
}
}] resume];
}
@end

View File

@@ -0,0 +1,25 @@
/// Copyright 2014 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.
@class SNTCommandSyncStatus;
@class SNTXPCConnection;
@interface SNTCommandSyncPreflight : NSObject
+ (void)performSyncInSession:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
@end

View File

@@ -0,0 +1,72 @@
/// Copyright 2014 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 "SNTCommandSyncPreflight.h"
#include "SNTKernelCommon.h"
#include "SNTLogging.h"
#import "SNTCommandSyncStatus.h"
#import "SNTSystemInfo.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@implementation SNTCommandSyncPreflight
+ (void)performSyncInSession:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[@"preflight/" stringByAppendingString:progress.machineID]
relativeToURL:progress.syncBaseURL];
NSMutableDictionary *requestDict = [NSMutableDictionary dictionary];
requestDict[@"serial_no"] = [SNTSystemInfo serialNumber];
requestDict[@"hostname"] = [SNTSystemInfo shortHostname];
requestDict[@"santa_version"] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
requestDict[@"os_version"] = [SNTSystemInfo osVersion];
requestDict[@"os_build"] = [SNTSystemInfo osBuild];
requestDict[@"primary_user"] = progress.machineOwner;
NSData *requestBody = [NSJSONSerialization dataWithJSONObject:requestDict
options:0
error:nil];
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPMethod:@"POST"];
[req setHTTPBody:requestBody];
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGD(@"HTTP Response: %@",
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
} else {
NSDictionary *r = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
progress.eventBatchSize = [r[@"batch_size"] intValue];
progress.uploadLogURL = [NSURL URLWithString:r[@"upload_logs_url"]];
if (r[@"client_mode"]) {
[[daemonConn remoteObjectProxy] setClientMode:[r[@"client_mode"] intValue] withReply:^{}];
}
handler(YES);
}
}] resume];
}
@end

View File

@@ -0,0 +1,25 @@
/// Copyright 2014 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.
@class SNTCommandSyncStatus;
@class SNTXPCConnection;
@interface SNTCommandSyncRuleDownload : NSObject
+ (void)performSyncInSession:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
@end

View File

@@ -0,0 +1,113 @@
/// Copyright 2014 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 "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncStatus.h"
#import "SNTRule.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#include "SNTLogging.h"
@implementation SNTCommandSyncRuleDownload
+ (void)performSyncInSession:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[@"ruledownload/" stringByAppendingString:progress.machineID]
relativeToURL:progress.syncBaseURL];
[self ruleDownloadWithCursor:nil
url:url
session:session
progress:progress
daemonConn:daemonConn
completionHandler:handler];
}
+ (void)ruleDownloadWithCursor:(NSString *)cursor
url:(NSURL *)url
session:(NSURLSession *)session
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSDictionary *requestDict;
if (cursor) {
requestDict = @{@"cursor": cursor};
} else {
requestDict = @{};
}
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPBody:[NSJSONSerialization dataWithJSONObject:requestDict
options:0
error:nil]];
[req setHTTPMethod:@"POST"];
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if ([(NSHTTPURLResponse *)response statusCode] != 200) {
LOGD(@"HTTP Response Code: %d", [(NSHTTPURLResponse *)response statusCode]);
handler(NO);
} else {
NSDictionary *resp = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSArray *receivedRules = resp[@"rules"];
if (receivedRules.count == 0) {
handler(YES);
return;
}
NSMutableArray *rules = [[NSMutableArray alloc] initWithCapacity:receivedRules.count];
for (NSDictionary *rule in receivedRules) {
SNTRule *newRule = [[SNTRule alloc] init];
newRule.SHA1 = rule[@"sha1"];
newRule.state = [rule[@"state"] intValue];
if (newRule.state <= RULESTATE_UNKNOWN || newRule.state >= RULESTATE_MAX) continue;
newRule.type = [rule[@"type"] intValue];
if (newRule.type <= RULETYPE_UNKNOWN || newRule.type >= RULETYPE_MAX) continue;
NSString *customMsg = rule[@"custom_msg"];
if (customMsg) {
newRule.customMsg = customMsg;
}
[rules addObject:newRule];
}
[[daemonConn remoteObjectProxy] databaseRuleAddRules:rules withReply:^{
LOGI(@"Downloaded %d rule(s)", rules.count);
if (resp[@"cursor"]) {
[self ruleDownloadWithCursor:resp[@"cursor"]
url:url
session:session
progress:progress
daemonConn:daemonConn
completionHandler:handler];
} else {
handler(YES);
}
}];
}
}] resume];
}
@end

View File

@@ -0,0 +1,32 @@
/// Copyright 2014 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 instance of this class is passed to each stage of the sync process for storing data
/// that might be needed in later stages.
@interface SNTCommandSyncStatus : NSObject
/// The base API URL
@property NSURL *syncBaseURL;
/// Machine identifier and owner
@property NSString *machineID;
@property NSString *machineOwner;
/// Batch size for uploading events, sent from server
@property int32_t eventBatchSize;
/// Log upload URL sent from server
@property NSURL *uploadLogURL;
@end

View File

@@ -0,0 +1,18 @@
/// Copyright 2014 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 "SNTCommandSyncStatus.h"
@implementation SNTCommandSyncStatus
@end

View File

@@ -0,0 +1,15 @@
/// This is a simple ASN.1 decoder that utilizes Apple's SecAsn1Decode
/// to parse the @c distinguishedNames property of NSURLProtectionSpace.
@interface SNTDERDecoder : NSObject
@property(readonly) NSString *commonName;
@property(readonly) NSString *organizationName;
@property(readonly) NSString *organizationalUnit;
@property(readonly) NSString *countryName;
/// Designated initializer. Pass in one of the NSData objects in the
/// NSURLProtectionSpace.distinguishedNames array
/// Returns nil if decoding fails to find any expected objects
- (instancetype)initWithData:(NSData *)data;
@end

View File

@@ -0,0 +1,201 @@
#import "SNTDERDecoder.h"
#import <Security/SecAsn1Coder.h>
#import <Security/SecAsn1Templates.h>
@interface SNTDERDecoder ()
@property NSDictionary *decodedObjects;
@end
@implementation SNTDERDecoder
#pragma mark Init
- (instancetype)initWithData:(NSData *)data {
self = [super init];
if (self) {
if (!data) return nil;
_decodedObjects = [self decodeData:data];
if (!_decodedObjects || [_decodedObjects count] == 0) return nil;
}
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (NSString *)description {
return [NSString stringWithFormat:@"/C=%@/O=%@/OU=%@/CN=%@",
self.countryName,
self.organizationName,
self.organizationalUnit,
self.commonName];
}
# pragma mark Accessors
- (NSString *)commonName {
return self.decodedObjects[(__bridge id)kSecOIDCommonName];
}
- (NSString *)organizationName {
return self.decodedObjects[(__bridge id)kSecOIDOrganizationName];
}
- (NSString *)organizationalUnit {
return self.decodedObjects[(__bridge id)kSecOIDOrganizationalUnitName];
}
- (NSString *)countryName {
return self.decodedObjects[(__bridge id)kSecOIDCountryName];
}
#pragma mark Private
/**
* The DER data provided by NSURLProtectionSpace.distinguishedNames looks like
* this:
*
* SEQUENCE {
* SET {
* SEQUENCE {
* OBJECT IDENTIFIER (2 5 4 6)
* PrintableString 'US'
* }
* }
* SET {
* SEQUENCE {
* OBJECT IDENTIFIER (2 5 4 10)
* PrintableString 'Megaco Inc'
* }
* }
* }
*
* This method assumes the passed in data will be in that format. If it isn't,
* the DER decoding will fail and this method will return nil.
**/
- (NSDictionary *)decodeData:(NSData *)data {
typedef struct {
SecAsn1Oid oid;
SecAsn1Item value;
} OIDKeyValue;
static const SecAsn1Template kOIDValueTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(OIDKeyValue) },
{ SEC_ASN1_OBJECT_ID, offsetof(OIDKeyValue, oid), NULL, 0 },
{ SEC_ASN1_ANY_CONTENTS, offsetof(OIDKeyValue, value), NULL, 0 },
{ 0, 0, NULL, 0 }
};
typedef struct {
OIDKeyValue **vals;
} OIDKeyValueList;
static const SecAsn1Template kSetOfOIDValueTemplate[] = {
{ SEC_ASN1_SET_OF, 0, kOIDValueTemplate, sizeof(OIDKeyValueList) },
{ 0, 0, NULL, 0 }
};
typedef struct {
OIDKeyValueList **lists;
} OIDKeyValueListSeq;
static const SecAsn1Template kSequenceOfSetOfOIDValueTemplate[] = {
{ SEC_ASN1_SEQUENCE_OF, 0, kSetOfOIDValueTemplate, sizeof(OIDKeyValueListSeq) },
{ 0, 0, NULL, 0 }
};
OSStatus err = errSecSuccess;
SecAsn1CoderRef coder;
err = SecAsn1CoderCreate(&coder);
if (err != errSecSuccess) return nil;
OIDKeyValueListSeq a;
err = SecAsn1Decode(coder,
data.bytes,
data.length,
kSequenceOfSetOfOIDValueTemplate,
&a);
SecAsn1CoderRelease(coder);
if (err != errSecSuccess) return nil;
// The data is decoded but now it's in a number of embedded structs.
// Massage that into a nice dictionary of OID->String pairs.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
OIDKeyValueList *anAttr;
for (NSUInteger i = 0; (anAttr = a.lists[i]); i++) {
OIDKeyValue *keyValue = anAttr->vals[0];
// Sanity check
if (keyValue->value.Length > data.length) return nil;
// Get the string value. First try creating as a UTF-8 string. If that fails,
// fallback to trying as an ASCII string. If it still doesn't work, continue on
// to the next value.
NSString *valueString;
valueString = [[NSString alloc] initWithBytes:keyValue->value.Data
length:keyValue->value.Length
encoding:NSUTF8StringEncoding];
if (!valueString) {
valueString = [[NSString alloc] initWithBytes:keyValue->value.Data
length:keyValue->value.Length
encoding:NSASCIIStringEncoding];
}
if (!valueString) continue;
// The OID is still encoded, so we need to decode it.
NSString *objectId = [SNTDERDecoder decodeOIDWithBytes:keyValue->oid.Data
length:keyValue->oid.Length];
// Add to the dictionary
dict[objectId] = valueString;
}
return dict;
}
/**
* Decodes an ASN.1 Object Identifier into a string separated by periods.
* See http://msdn.microsoft.com/en-us/library/bb540809(v=vs.85).aspx for
* details of the encoding.
**/
+ (NSString *)decodeOIDWithBytes:(unsigned char *)bytes length:(NSUInteger)length {
NSMutableArray *objectId = [NSMutableArray array];
BOOL inVariableLengthByte = NO;
NSUInteger variableLength = 0;
for (NSUInteger i = 0; i < length; ++i) {
if (i == 0) {
// The first byte is actually two values, the top 4 bits are the first value * 40
// and the bottom 4 bits are the second value.
[objectId addObject:@((NSUInteger)bytes[i] / 40)];
[objectId addObject:@((NSUInteger)bytes[i] % 40)];
} else {
// The remaining bytes are encoded with Variable Length Quantity.
unsigned char byte = bytes[i];
if (byte & 0x80) {
inVariableLengthByte = YES;
NSUInteger a = (NSUInteger) (byte & ~0x80);
variableLength = variableLength << 7;
variableLength += a;
} else if (inVariableLengthByte) {
NSUInteger a = (NSUInteger) (byte & ~0x80);
variableLength = variableLength << 7;
variableLength += a;
inVariableLengthByte = NO;
[objectId addObject:@(variableLength)];
variableLength = 0;
} else {
[objectId addObject:@((NSUInteger)byte)];
}
}
}
return [objectId componentsJoinedByString:@"."];
}
@end

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>com.google.santad</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleShortVersionString</key>
<string>0.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.7</string>
</dict>
</plist>

View File

@@ -0,0 +1,3 @@
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

View File

@@ -0,0 +1,21 @@
/// Copyright 2014 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.
/// The main controller class for santad
@interface SNTApplication : NSObject
/// Begins fielding requests from the driver
- (int)run;
@end

View File

@@ -0,0 +1,158 @@
/// Copyright 2014 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.
#include <pwd.h>
#include <sys/types.h>
#import "SNTApplication.h"
#include "SNTCommonEnums.h"
#include "SNTLogging.h"
#import "SNTConfigurator.h"
#import "SNTDaemonControlController.h"
#import "SNTDatabaseController.h"
#import "SNTDriverManager.h"
#import "SNTEventTable.h"
#import "SNTExecutionController.h"
#import "SNTRuleTable.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#import "SNTXPCNotifierInterface.h"
@interface SNTApplication ()
@property SNTDriverManager *driverManager;
@property SNTEventTable *eventTable;
@property SNTExecutionController *execController;
@property SNTRuleTable *ruleTable;
@property SNTXPCConnection *controlConnection;
@property SNTXPCConnection *notifierConnection;
@end
@implementation SNTApplication
- (instancetype)init {
self = [super init];
if (self) {
// Locate and connect to driver
_driverManager = [[SNTDriverManager alloc] init];
if (!_driverManager) {
LOGE(@"Failed to connect to driver, exiting.");
// TODO(rah): Consider trying to load the extension from within santad.
return nil;
}
// Initialize tables
_ruleTable = [SNTDatabaseController ruleTable];
if (! _ruleTable) {
LOGE(@"Failed to initialize rule table.");
return nil;
}
_eventTable = [SNTDatabaseController eventTable];
if (! _eventTable) {
LOGE(@"Failed to initialize event table.");
return nil;
}
// Establish XPC listener for GUI agent connections
_notifierConnection =
[[SNTXPCConnection alloc] initServerWithName:[SNTXPCNotifierInterface serviceId]];
_notifierConnection.remoteInterface = [SNTXPCNotifierInterface notifierInterface];
[_notifierConnection resume];
// Establish XPC listener for santactl connections
_controlConnection =
[[SNTXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceId]];
_controlConnection.exportedInterface = [SNTXPCControlInterface controlInterface];
_controlConnection.exportedObject =
[[SNTDaemonControlController alloc] initWithDriverManager:_driverManager];
[_controlConnection resume];
// Get client mode and begin observing for updates
SNTConfigurator *configurator = [SNTConfigurator configurator];
santa_clientmode_t clientMode = [configurator clientMode];
[configurator addObserver:self
forKeyPath:@"clientMode"
options:NSKeyValueObservingOptionNew
context:NULL];
// Initialize the binary checker object
_execController = [[SNTExecutionController alloc] initWithDriverManager:_driverManager
ruleTable:_ruleTable
eventTable:_eventTable
operatingMode:clientMode
notifierConnection:_notifierConnection];
if (!_execController) return nil;
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqual:@"clientMode"]) {
self.execController.operatingMode = [change[NSKeyValueChangeNewKey] intValue];
}
}
- (int)run {
LOGI(@"Connected to driver, activating.");
dispatch_queue_t q = dispatch_queue_create("com.google.santad.driver_queue",
DISPATCH_QUEUE_CONCURRENT);
[self.driverManager listenWithBlock:^BOOL(santa_message_t message) {
@autoreleasepool {
switch (message.action) {
case ACTION_REQUEST_SHUTDOWN: {
LOGI(@"Driver requested a shutdown");
// Sleep before exiting to give driver chance to ready itself
sleep(10);
return NO;
}
case ACTION_REQUEST_CHECKBW: {
// Validate the binary aynchronously on a concurrent queue so we don't
// hold up other execution requests in the background.
dispatch_async(q, ^{
struct passwd *user = getpwuid(message.userId);
NSString *userName;
if (user) {
userName = @(user->pw_name);
}
[self.execController validateBinaryWithSHA1:@(message.sha1)
path:@(message.path)
userName:userName
pid:@(message.pid)
vnodeId:message.vnode_id];
});
return YES;
}
default: {
LOGE(@"Received request without an action");
return NO;
}
}
}
}];
return 0;
}
@end

View File

@@ -0,0 +1,26 @@
/// Copyright 2014 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 "SNTXPCControlInterface.h"
@class SNTDriverManager;
/// SNTDaemonControlController handles all of the RPCs from santactl
@interface SNTDaemonControlController : NSObject<SNTDaemonControlXPC>
@property SNTDriverManager *driverManager;
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager;
@end

View File

@@ -0,0 +1,90 @@
/// Copyright 2014 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 "SNTDaemonControlController.h"
#import "SNTConfigurator.h"
#import "SNTDatabaseController.h"
#import "SNTDriverManager.h"
#import "SNTEventTable.h"
#import "SNTLogging.h"
#import "SNTRule.h"
#import "SNTRuleTable.h"
@implementation SNTDaemonControlController
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager {
self = [super init];
if (self) {
_driverManager = driverManager;
}
return self;
}
#pragma mark Kernel ops
- (void)cacheCount:(void (^)(uint64_t))reply; {
uint64_t count = [self.driverManager cacheCount];
reply(count);
}
- (void)flushCache:(void (^)(BOOL))reply {
reply([self.driverManager flushCache]);
}
#pragma mark Database ops
- (void)databaseRuleCounts:(void (^)(uint64_t binary, uint64_t certificate))reply {
SNTRuleTable *rdb = [SNTDatabaseController ruleTable];
reply([rdb binaryRuleCount], [rdb certificateRuleCount]);
}
- (void)databaseRuleAddRule:(SNTRule *)rule withReply:(void (^)())reply {
[[SNTDatabaseController ruleTable] addRule:rule];
reply();
}
- (void)databaseRuleAddRules:(NSArray *)rules withReply:(void (^)())reply {
[[SNTDatabaseController ruleTable] addRules:rules];
reply();
}
- (void)databaseEventCount:(void (^)(uint64_t count))reply {
reply([[SNTDatabaseController eventTable] eventsPendingCount]);
}
- (void)databaseEventForSHA1:(NSString *)sha1 withReply:(void (^)(SNTStoredEvent *))reply {
reply([[SNTDatabaseController eventTable] latestEventForSHA1:sha1]);
}
- (void)databaseEventsPending:(void (^)(NSArray *events))reply {
reply([[SNTDatabaseController eventTable] pendingEvents]);
}
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids {
[[SNTDatabaseController eventTable] deleteEventsWithIndexes:ids];
}
#pragma mark Misc
- (void)clientMode:(void (^)(santa_clientmode_t))reply {
reply([[SNTConfigurator configurator] clientMode]);
}
- (void)setClientMode:(santa_clientmode_t)mode withReply:(void (^)())reply {
[[SNTConfigurator configurator] setClientMode:mode];
reply();
}
@end

View File

@@ -0,0 +1,32 @@
/// Copyright 2014 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.
// These imports are in the header rather than implementation to keep them in one place, saving
// classes that use this one from also having to import FMDB stuff.
#import <FMDB/FMDB.h>
@class SNTConfigTable;
@class SNTEventTable;
@class SNTRuleTable;
/// Provides methods to get an instance of one of the database table controllers with a
/// pre-configured database queue.
@interface SNTDatabaseController : NSObject
/// Returns an instance of the respective table class initialized with an appropriate database queue
/// Will initialize only once, regardless of calling thread.
+ (SNTEventTable *)eventTable;
+ (SNTRuleTable *)ruleTable;
@end

View File

@@ -0,0 +1,82 @@
/// Copyright 2014 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 "SNTDatabaseController.h"
#import "SNTEventTable.h"
#import "SNTLogging.h"
#import "SNTRuleTable.h"
@implementation SNTDatabaseController
static NSString * const kDatabasePath = @"/var/db/santa";
static NSString * const kRulesDatabaseName = @"rules.db";
static NSString * const kEventsDatabaseName = @"events.db";
+ (SNTEventTable *)eventTable {
static FMDatabaseQueue *eventDatabaseQueue = nil;
static dispatch_once_t eventDatabaseToken;
dispatch_once(&eventDatabaseToken, ^{
[self createDatabasePath];
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kEventsDatabaseName];
eventDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
#ifndef DEBUG
[eventDatabaseQueue inDatabase:^(FMDatabase *db) {
db.logsErrors = NO;
}];
#endif
});
return [[SNTEventTable alloc] initWithDatabaseQueue:eventDatabaseQueue];
}
+ (SNTRuleTable *)ruleTable {
static FMDatabaseQueue *ruleDatabaseQueue = nil;
static dispatch_once_t ruleDatabaseToken;
dispatch_once(&ruleDatabaseToken, ^{
[self createDatabasePath];
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kRulesDatabaseName];
ruleDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
#ifndef DEBUG
[ruleDatabaseQueue inDatabase:^(FMDatabase *db) {
db.logsErrors = NO;
}];
#endif
});
return [[SNTRuleTable alloc] initWithDatabaseQueue:ruleDatabaseQueue];
}
#pragma mark - Private
/// Create the folder that contains the databases
+ (void)createDatabasePath {
NSFileManager *fm = [NSFileManager defaultManager];
NSDictionary *attrs = @{ NSFileOwnerAccountName: @"root",
NSFileGroupOwnerAccountName: @"wheel",
NSFilePosixPermissions: @0755 };
if (![fm fileExistsAtPath:kDatabasePath]) {
[fm createDirectoryAtPath:kDatabasePath
withIntermediateDirectories:YES
attributes:attrs
error:nil];
} else {
[fm setAttributes:attrs ofItemAtPath:kDatabasePath error:nil];
}
}
@end

View File

@@ -0,0 +1,35 @@
/// Copyright 2014 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.
// These imports are in the header rather than implementation to keep them in one place, saving
// classes that use this one from also having to import FMDB stuff.
#import <FMDB/FMDB.h>
@interface SNTDatabaseTable : NSObject
/// Designated initializer.
- (instancetype)initWithDatabaseQueue:(FMDatabaseQueue *)db;
/// Subclasses should override this method to apply schema updates. The passed in version nubmer
/// is the current version of the table. The return value is the new version of the table. If
/// updating the table failed, return a negative number. If there was no update to apply, return 0.
- (int)initializeDatabase:(FMDatabase *)db fromVersion:(int)version;
/// Wrappers around the respective FMDatabaseQueue methods. If the object we initialized with was
/// a database queue, these just pass through. If the object we initialized with was an FMDatabase
/// we just call the block with the database, potentially wrapping in a transaction.
- (void)inDatabase:(void (^)(FMDatabase *db))block;
- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block;
@end

View File

@@ -0,0 +1,69 @@
/// Copyright 2014 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 "SNTDatabaseTable.h"
#import "SNTLogging.h"
@interface SNTDatabaseTable ()
@property FMDatabaseQueue *dbQ;
@end
@implementation SNTDatabaseTable
- (instancetype)initWithDatabaseQueue:(FMDatabaseQueue *)db {
if (!db) return nil;
self = [super init];
if (self) {
_dbQ = db;
[self updateTableSchema];
}
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (int)initializeDatabase:(FMDatabase *)db fromVersion:(int)version {
[self doesNotRecognizeSelector:_cmd];
return -1;
}
/// Called at the end of initialization to ensure the table in the
/// database exists and uses the latest schema.
- (void)updateTableSchema {
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
int currentVersion = [db userVersion];
int newVersion = [self initializeDatabase:db fromVersion:currentVersion];
if (newVersion < 1) return;
LOGD(@"Updated %@ from version %d to %d", [self className], currentVersion, newVersion);
[db setUserVersion:newVersion];
}];
}
- (void)inDatabase:(void (^)(FMDatabase *db))block {
[self.dbQ inDatabase:block];
}
- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
[self.dbQ inTransaction:block];
}
@end

View File

@@ -0,0 +1,36 @@
/// Copyright 2014 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.
#include "SNTKernelCommon.h"
@class SNTNotificationMessage;
/// Manages the connection between daemon and kernel.
@interface SNTDriverManager : NSObject
/// Handles requests from the kernel using the given block.
/// @note Loops indefinitely until either the callback block returns @c NO or there is an error
/// trying to read data from the data queue.
- (void)listenWithBlock:(BOOL (^)(santa_message_t message))callback;
/// Sends a response to a query back to the kernel.
- (kern_return_t)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)vnodeId;
/// Get the number of binaries in the kernel's cache.
- (uint64_t)cacheCount;
/// Flush the kernel's binary cache.
- (BOOL)flushCache;
@end

View File

@@ -0,0 +1,188 @@
/// Copyright 2014 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 "SNTDriverManager.h"
#include <IOKit/IODataQueueClient.h>
#include <mach/mach.h>
#include "SNTLogging.h"
#import "SNTNotificationMessage.h"
@interface SNTDriverManager ()
@property IODataQueueMemory *queueMemory;
@property io_connect_t connection;
@property mach_port_t receivePort;
@end
@implementation SNTDriverManager
#pragma mark init/dealloc
- (instancetype)init {
self = [super init];
if (self) {
kern_return_t kr;
io_service_t serviceObject;
CFDictionaryRef classToMatch;
if (!(classToMatch = IOServiceMatching(USERCLIENT_CLASS))) {
LOGD(@"Failed to create matching dictionary");
return nil;
}
// Locate driver. Wait for it if necessary.
do {
CFRetain(classToMatch); // this ref is released by IOServiceGetMatchingService
serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, classToMatch);
if (!serviceObject) {
sleep(10);
}
} while (!serviceObject);
CFRelease(classToMatch);
// This calls |initWithTask|, |attach| and |start| in |SantaDriverClient|
kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection);
IOObjectRelease(serviceObject);
if (kr != kIOReturnSuccess) {
LOGD(@"Failed to open Santa driver service");
return nil;
}
// Call |open| in |SantaDriverClient|
kr = IOConnectCallMethod(_connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
if (kr == kIOReturnExclusiveAccess) {
LOGD(@"A client is already connected");
return nil;
} else if (kr != kIOReturnSuccess) {
LOGD(@"An error occurred while opening the connection");
return nil;
}
}
return self;
}
- (void)dealloc {
IOServiceClose(_connection);
}
# pragma mark Incoming messages
- (void)listenWithBlock:(BOOL (^)(santa_message_t message))callback {
kern_return_t kr;
santa_message_t vdata;
UInt32 dataSize;
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
unsigned int msgType = 1;
// Allocate a mach port to receive notifactions from the IODataQueue
if (!(self.receivePort = IODataQueueAllocateNotificationPort())) {
LOGD(@"Failed to allocate notification port");
return;
}
// This will call registerNotificationPort() inside our user client class
kr = IOConnectSetNotificationPort(self.connection, msgType, self.receivePort, 0);
if (kr != kIOReturnSuccess) {
LOGD(@"Failed to register notification port: %d", kr);
mach_port_destroy(mach_task_self(), self.receivePort);
return;
}
// This will call clientMemoryForType() inside our user client class,
// which activates the Kauth listeners.
kr = IOConnectMapMemory(self.connection, kIODefaultMemoryType, mach_task_self(),
&address, &size, kIOMapAnywhere);
if (kr != kIOReturnSuccess) {
LOGD(@"Failed to map memory: %d", kr);
mach_port_destroy(mach_task_self(), self.receivePort);
return;
}
self.queueMemory = (IODataQueueMemory *)address;
BOOL breakOut = NO;
while (IODataQueueWaitForAvailableData(self.queueMemory,
self.receivePort) == kIOReturnSuccess) {
while (IODataQueueDataAvailable(self.queueMemory)) {
dataSize = sizeof(vdata);
kr = IODataQueueDequeue(self.queueMemory, &vdata, &dataSize);
if (kr == kIOReturnSuccess) {
if (! callback(vdata)) {
breakOut = YES;
break;
}
} else {
LOGD(@"Error receiving data: %d", kr);
breakOut = YES;
break;
}
}
if (breakOut) {
break;
}
}
IOConnectUnmapMemory(self.connection, kIODefaultMemoryType, mach_task_self(), address);
mach_port_destroy(mach_task_self(), self.receivePort);
}
#pragma mark Outgoing messages
- (kern_return_t)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)vnodeId {
switch (action) {
case ACTION_RESPOND_CHECKBW_ALLOW:
return IOConnectCallScalarMethod(self.connection,
kSantaUserClientAllowBinary,
&vnodeId,
1,
0,
0);
case ACTION_RESPOND_CHECKBW_DENY:
return IOConnectCallScalarMethod(self.connection,
kSantaUserClientDenyBinary,
&vnodeId,
1,
0,
0);
default:
return KERN_INVALID_ARGUMENT;
}
}
- (uint64_t)cacheCount {
uint32_t input_count = 1;
uint64_t cache_count = 0;
IOConnectCallScalarMethod(self.connection,
kSantaUserClientCacheCount,
0,
0,
&cache_count,
&input_count);
return cache_count;
}
- (BOOL)flushCache {
return IOConnectCallScalarMethod(
self.connection, kSantaUserClientClearCache, 0, 0, 0, 0) == KERN_SUCCESS;
}
@end

View File

@@ -0,0 +1,43 @@
/// Copyright 2014 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 "SNTDatabaseTable.h"
@class SNTNotificationMessage;
@class SNTStoredEvent;
/// Responsible for managing the event table in the Santa database.
@interface SNTEventTable : SNTDatabaseTable
/// Add event to the database
- (void)addStoredEvent:(SNTStoredEvent *)event;
/// Number of events in database.
- (int)eventsPendingCount;
/// Retrieves all events in the database
/// @return NSArray of SNTStoredEvent
- (NSArray *)pendingEvents;
/// Retrieve an event from the database.
/// @return a single SNTStoredEvent
- (SNTStoredEvent *)latestEventForSHA1:(NSString *)sha1;
/// Delete a single event from the database using its index.
- (void)deleteEventWithIndex:(NSNumber *)index;
/// Delete multiple events from the database with an array of indexes.
- (void)deleteEventsWithIndexes:(NSArray *)indexes;
@end

View File

@@ -0,0 +1,213 @@
/// Copyright 2014 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 "SNTEventTable.h"
#import "SNTCertificate.h"
#import "SNTLogging.h"
#import "SNTNotificationMessage.h"
#import "SNTStoredEvent.h"
@implementation SNTEventTable
- (int)initializeDatabase:(FMDatabase *)db fromVersion:(int)version {
int newVersion = 0;
if (version < 1) {
[db executeUpdate:@"CREATE TABLE 'events' ("
"'idx' INTEGER PRIMARY KEY AUTOINCREMENT,"
"'fileSHA1' TEXT NOT NULL,"
"'filePath' TEXT NOT NULL,"
"'fileBundleID' TEXT,"
"'fileBundleVersion' TEXT,"
"'fileBundleVersionString' TEXT,"
"'fileBundleName' TEXT,"
"'certSHA1' TEXT,"
"'certCN' TEXT,"
"'certOrg' TEXT,"
"'certOU' TEXT,"
"'certValidFromDate' REAL,"
"'certValidUntilDate' REAL,"
"'occurrenceDate' REAL,"
"'executingUser' TEXT,"
"'decision' INT,"
"'loggedInUsers' BLOB,"
"'currentSessions' BLOB"
@");"];
[db executeUpdate:@"CREATE INDEX event_filesha1 ON events (fileSHA1);"];
newVersion = 1;
}
return newVersion;
}
#pragma mark Loading / Storing
- (void)addStoredEvent:(SNTStoredEvent *)event {
if (!event.fileSHA1 ||
!event.filePath ||
!event.occurrenceDate ||
!event.executingUser ||
!event.decision) return;
NSMutableDictionary *parameters = [@{@"fileSHA1": event.fileSHA1,
@"filePath": event.filePath,
@"occurrenceDate": event.occurrenceDate,
@"executingUser": event.executingUser,
@"decision": @(event.decision)} mutableCopy];
if (event.certSHA1) parameters[@"certSHA1"] = event.certSHA1;
if (event.certCN) parameters[@"certCN"] = event.certCN;
if (event.certOrg) parameters[@"certOrg"] = event.certOrg;
if (event.certOU) parameters[@"certOU"] = event.certOU;
if (event.certValidFromDate) parameters[@"certValidFromDate"] = event.certValidFromDate;
if (event.certValidUntilDate) parameters[@"certValidUntilDate"] = event.certValidUntilDate;
if (event.fileBundleID) parameters[@"fileBundleID"] = event.fileBundleID;
if (event.fileBundleName) parameters[@"fileBundleName"] = event.fileBundleName;
if (event.fileBundleVersion) parameters[@"fileBundleVersion"] = event.fileBundleVersion;
if (event.fileBundleVersionString) {
parameters[@"fileBundleVersionString"] = event.fileBundleVersionString;
}
if (event.loggedInUsers) {
NSData *usersData = [NSKeyedArchiver archivedDataWithRootObject:event.loggedInUsers];
parameters[@"loggedInUsers"] = usersData;
}
if (event.currentSessions ) {
NSData *sessionsData = [NSKeyedArchiver archivedDataWithRootObject:event.currentSessions];
parameters[@"currentSessions"] = sessionsData;
}
NSString *paramString = [[parameters allKeys] componentsJoinedByString:@","];
NSString *paramStringColon = [paramString stringByReplacingOccurrencesOfString:@","
withString:@",:"];
paramStringColon = [@":" stringByAppendingString:paramStringColon];
NSString *sql = [NSString stringWithFormat:@"INSERT INTO 'events' (%@) VALUES (%@)",
paramString,
paramStringColon];
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
if (![db executeUpdate:sql withParameterDictionary:parameters]) {
LOGD(@"Failed to save event");
}
}];
}
- (SNTStoredEvent *)eventFromResultSet:(FMResultSet *)rs {
SNTStoredEvent *event = [[SNTStoredEvent alloc] init];
event.idx = @([rs intForColumn:@"idx"]);
event.fileSHA1 = [rs stringForColumn:@"fileSHA1"];
event.filePath = [rs stringForColumn:@"filePath"];
event.occurrenceDate = [rs dateForColumn:@"occurrenceDate"];
event.executingUser = [rs stringForColumn:@"executingUser"];
event.decision = [rs intForColumn:@"decision"];
event.certSHA1 = [rs stringForColumn:@"certSHA1"];
event.certCN = [rs stringForColumn:@"certCN"];
event.certOrg = [rs stringForColumn:@"certOrg"];
event.certOU = [rs stringForColumn:@"certOU"];
event.certValidFromDate = [rs dateForColumn:@"certValidFromDate"];
event.certValidUntilDate = [rs dateForColumn:@"certValidUntilDate"];
event.fileBundleID = [rs stringForColumn:@"fileBundleID"];
event.fileBundleName = [rs stringForColumn:@"fileBundleName"];
event.fileBundleVersion = [rs stringForColumn:@"fileBundleVersion"];
event.fileBundleVersionString = [rs stringForColumn:@"fileBundleVersionString"];
NSData *currentSessions = [rs dataForColumn:@"currentSessions"];
NSData *loggedInUsers = [rs dataForColumn:@"loggedInUsers"];
if (currentSessions) {
event.currentSessions = [NSKeyedUnarchiver unarchiveObjectWithData:currentSessions];
}
if (loggedInUsers) {
event.loggedInUsers = [NSKeyedUnarchiver unarchiveObjectWithData:loggedInUsers];
}
return event;
}
#pragma mark Querying/Retreiving
- (int)eventsPendingCount {
__block int eventsPending = 0;
[self inDatabase:^(FMDatabase *db) {
eventsPending = [db intForQuery:@"SELECT COUNT(*) FROM events"];
}];
return eventsPending;
}
- (BOOL)existingEventForBinary:(SNTNotificationMessage *)event {
__block BOOL result = NO;
NSString *qry = @"SELECT fileSHA1 FROM events WHERE fileSHA1=? LIMIT 1";
[self inDatabase:^(FMDatabase *db) {
result = [db boolForQuery:qry, event.SHA1];
}];
return result;
}
- (SNTStoredEvent *)latestEventForSHA1:(NSString *)sha1 {
__block SNTStoredEvent *storedEvent;
[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs = [db executeQuery:@"SELECT * FROM events WHERE fileSHA1=? "
@"ORDER BY occurrenceDate DESC LIMIT 1;", sha1];
if ([rs next]) {
storedEvent = [self eventFromResultSet:rs];
}
[rs close];
}];
return storedEvent;
}
- (NSArray *)pendingEvents {
NSMutableArray *pendingEvents = [[NSMutableArray alloc] init];
[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs = [db executeQuery:@"SELECT * FROM events"];
while ([rs next]) {
[pendingEvents addObject:[self eventFromResultSet:rs]];
}
[rs close];
}];
return pendingEvents;
}
#pragma mark Deleting
- (void)deleteEventWithIndex:(NSNumber *)index {
[self inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"DELETE FROM events WHERE idx=?", index];
}];
}
- (void)deleteEventsWithIndexes:(NSArray *)indexes {
for (NSNumber *index in indexes) {
[self deleteEventWithIndex:index];
}
}
@end

View File

@@ -0,0 +1,57 @@
/// Copyright 2014 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.
#include "SNTCommonEnums.h"
@class SNTCodesignChecker;
@class SNTDriverManager;
@class SNTEventTable;
@class SNTRuleTable;
@class SNTXPCConnection;
/// SNTExecutionController is responsible for everything that happens when a request to execute
/// a binary occurs:
/// + Making a decision about whether to allow or deny this binary based on any existing rules for
/// that specific binary, its signing certificate and the operating mode of santad.
/// + Sending the decision to the kernel as soon as possible
/// + (If denied or unknown) Storing details about the execution event to the database
/// for upload and spwaning santactl to quickly try and send that to the server.
/// + (If denied) Potentially sending a message to SantaGUI to notify the user
/// + Logging the event to the log file
///
@interface SNTExecutionController : NSObject
@property SNTDriverManager *driverManager;
@property SNTRuleTable *ruleTable;
@property SNTEventTable *eventTable;
@property SNTXPCConnection *notifierConnection;
@property santa_clientmode_t operatingMode;
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager
ruleTable:(SNTRuleTable *)ruleTable
eventTable:(SNTEventTable *)eventTable
operatingMode:(santa_clientmode_t)operating_mode
notifierConnection:(SNTXPCConnection *)notifierConn;
// Handles the logic of deciding whether to allow the binary to run or not, sends the response to
// the kernel, logs the event to the log and if necessary stores the event in the database and
// sends a notification to the GUI agent.
- (void)validateBinaryWithSHA1:(NSString *)sha1
path:(NSString *)path
userName:(NSString *)userName
pid:(NSNumber *)pid
vnodeId:(uint64_t)vnodeId;
@end

View File

@@ -0,0 +1,325 @@
/// Copyright 2014 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 "SNTExecutionController.h"
#include <utmpx.h>
#include "SNTLogging.h"
#import "SNTBinaryInfo.h"
#import "SNTCertificate.h"
#import "SNTCodesignChecker.h"
#import "SNTDriverManager.h"
#import "SNTDropRootPrivs.h"
#import "SNTEventTable.h"
#import "SNTNotificationMessage.h"
#import "SNTRule.h"
#import "SNTRuleTable.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
#import "SNTXPCNotifierInterface.h"
@implementation SNTExecutionController
#pragma mark Initializers
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager
ruleTable:(SNTRuleTable *)ruleTable
eventTable:(SNTEventTable *)eventTable
operatingMode:(santa_clientmode_t)operatingMode
notifierConnection:(SNTXPCConnection *)notifier {
self = [super init];
if (self) {
_driverManager = driverManager;
_ruleTable = ruleTable;
_eventTable = eventTable;
_operatingMode = operatingMode;
_notifierConnection = notifier;
LOGI(@"Log format: Decision (A|D), Reason (B|C), SHA-1, Path, Cert SHA-1, Cert CN");
}
return self;
}
#pragma mark Binary Validation
- (void)validateBinaryWithSHA1:(NSString *)sha1
path:(NSString *)path
userName:(NSString *)userName
pid:(NSNumber *)pid
vnodeId:(uint64_t)vnodeId {
// Step 1 - in scope?
if (![self fileIsInScope:path]) {
[self.driverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vnodeId];
LOGD(@"File out of scope: %@", path);
return;
}
// These will be filled in either in step 2, 3 or 4.
santa_action_t respondedAction = ACTION_UNSET;
SNTRule *rule;
// Step 2 - binary rule?
rule = [self.ruleTable binaryRuleForSHA1:sha1];
if (rule) {
respondedAction = [self actionForRuleState:rule.state];
[self.driverManager postToKernelAction:respondedAction forVnodeID:vnodeId];
}
SNTBinaryInfo *binInfo = [[SNTBinaryInfo alloc] initWithPath:path];
SNTCodesignChecker *csInfo = [[SNTCodesignChecker alloc] initWithBinaryPath:path];
// Step 3 - cert rule?
if (!rule) {
rule = [self.ruleTable certificateRuleForSHA1:csInfo.leafCertificate.SHA1];
if (rule) {
respondedAction = [self actionForRuleState:rule.state];
[self.driverManager postToKernelAction:respondedAction forVnodeID:vnodeId];
}
}
// Step 4 - default rule :-(
if (!rule) {
respondedAction = [self defaultDecision];
[self.driverManager postToKernelAction:respondedAction forVnodeID:vnodeId];
}
// Step 5 - log to database
if (respondedAction == ACTION_RESPOND_CHECKBW_DENY || !rule) {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.fileSHA1 = sha1;
se.filePath = path;
se.fileBundleID = [binInfo bundleIdentifier];
se.fileBundleName = [binInfo bundleName];
if ([binInfo bundleShortVersionString]) {
se.fileBundleVersionString = [binInfo bundleShortVersionString];
}
if ([binInfo bundleVersion]) {
se.fileBundleVersion = [binInfo bundleVersion];
}
se.certSHA1 = csInfo.leafCertificate.SHA1;
se.certCN = csInfo.leafCertificate.commonName;
se.certOrg = csInfo.leafCertificate.orgName;
se.certOU = csInfo.leafCertificate.orgUnit;
se.certValidFromDate = csInfo.leafCertificate.validFrom;
se.certValidUntilDate = csInfo.leafCertificate.validUntil;
se.executingUser = userName;
se.occurrenceDate = [[NSDate alloc] init];
se.decision = [self eventStateForDecision:respondedAction type:rule.type];
se.pid = pid;
NSArray *loggedInUsers, *currentSessions;
[self loggedInUsers:&loggedInUsers sessions:&currentSessions];
se.currentSessions = currentSessions;
se.loggedInUsers = loggedInUsers;
[self.eventTable addStoredEvent:se];
}
// Step 6 - log to log file
[self logDecisionForEventState:[self eventStateForDecision:respondedAction type:rule.type]
sha1:sha1
path:path
leafCert:csInfo.leafCertificate];
// Step 7 - alert user
if (respondedAction == ACTION_RESPOND_CHECKBW_DENY) {
// So the server has something to show the user straight away, initiate an event
// upload for the blocked binary rather than waiting for the next sync.
// The event upload is skipped if the full path is equal to that of /usr/sbin/santactl so that
/// on the off chance that santactl is not whitelisted, we don't get into an infinite loop.
if (![path isEqual:@"/usr/sbin/santactl"]) {
[self initiateEventUploadForSHA1:sha1];
}
SNTNotificationMessage *notMsg = [[SNTNotificationMessage alloc] init];
notMsg.path = path;
notMsg.SHA1 = sha1;
notMsg.customMessage = rule.customMsg;
notMsg.certificates = csInfo.certificates;
[[self.notifierConnection remoteObjectProxy] postBlockNotification:notMsg];
}
}
/**
Checks whether the file at @c path is in-scope for checking with Santa.
Files that are out of scope:
+ Non Mach-O files
+ Files in whitelisted directories.
@return @c YES if file is in scope, @c NO otherwise.
**/
- (BOOL)fileIsInScope:(NSString *)path {
// Determine if file is within a whitelisted directory.
if ([self pathIsInWhitelistedDir:path]) {
return NO;
}
// If file is not a Mach-O file, we're not interested.
// TODO(rah): Consider adding an option to check scripts
SNTBinaryInfo *binInfo = [[SNTBinaryInfo alloc] initWithPath:path];
if (![binInfo isMachO]) {
return NO;
}
return YES;
}
- (BOOL)pathIsInWhitelistedDir:(NSString *)path {
// TODO(rah): Implement this.
return NO;
}
- (santa_eventstate_t)eventStateForDecision:(santa_action_t)decision type:(santa_ruletype_t)type {
if (decision == ACTION_RESPOND_CHECKBW_ALLOW) {
if (type == RULETYPE_BINARY) {
return EVENTSTATE_ALLOW_BINARY;
} else if (type == RULETYPE_CERT) {
return EVENTSTATE_ALLOW_CERTIFICATE;
} else {
return EVENTSTATE_ALLOW_UNKNOWN;
}
} else if (decision == ACTION_RESPOND_CHECKBW_DENY) {
if (type == RULETYPE_BINARY) {
return EVENTSTATE_BLOCK_BINARY;
} else if (decision == RULETYPE_CERT) {
return EVENTSTATE_BLOCK_CERTIFICATE;
} else {
return EVENTSTATE_BLOCK_UNKNOWN;
}
} else {
return EVENTSTATE_UNKNOWN;
}
}
- (void)logDecisionForEventState:(santa_eventstate_t)eventState
sha1:(NSString *)sha1
path:(NSString *)path
leafCert:(SNTCertificate *)cert {
NSString *d, *r, *outLog;
switch (eventState) {
case EVENTSTATE_ALLOW_BINARY:
d = @"A"; r = @"B"; break;
case EVENTSTATE_ALLOW_CERTIFICATE:
d = @"A"; r = @"C"; break;
case EVENTSTATE_ALLOW_UNKNOWN:
d = @"A"; r = @"?"; break;
case EVENTSTATE_BLOCK_BINARY:
d = @"D"; r = @"B"; break;
case EVENTSTATE_BLOCK_CERTIFICATE:
d = @"D"; r = @"C"; break;
case EVENTSTATE_BLOCK_UNKNOWN:
d = @"D"; r = @"?"; break;
default:
d = @"?"; r = @"?"; break;
}
// Ensure there are no commas in the path name (as this will be confusing in the log)
NSString *printPath = [path stringByReplacingOccurrencesOfString:@"," withString:@"<comma>"];
if (cert && cert.SHA1 && cert.commonName) {
// Also ensure there are no commas in the cert's common name.
NSString *printCommonName = [cert.commonName stringByReplacingOccurrencesOfString:@","
withString:@"<comma>"];
outLog = [NSString stringWithFormat:@"%@,%@,%@,%@,%@,%@", d, r, sha1, printPath,
cert.SHA1, printCommonName];
} else {
outLog = [NSString stringWithFormat:@"%@,%@,%@,%@", d, r, sha1, printPath];
}
// Now make sure none of the log line has a newline in it.
LOGI(@"%@", [[outLog componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]
componentsJoinedByString:@" "]);
}
- (void)initiateEventUploadForSHA1:(NSString *)sha1 {
signal(SIGCHLD, SIG_IGN);
pid_t child = fork();
if (child == 0) {
fclose(stdout);
// Ensure we have no privileges
if (!DropRootPrivileges()) {
exit(1);
}
exit(execl("/usr/sbin/santactl", "/usr/sbin/santactl", "sync",
"singleevent", [sha1 UTF8String], NULL));
}
}
- (santa_action_t)defaultDecision {
switch (self.operatingMode) {
case CLIENTMODE_MONITOR:
return ACTION_RESPOND_CHECKBW_ALLOW;
case CLIENTMODE_LOCKDOWN:
return ACTION_RESPOND_CHECKBW_DENY;
default:
// This should never happen, panic and lockdown.
LOGE(@"Client mode is unset while enforcement is in effect. Blocking.");
return ACTION_RESPOND_CHECKBW_DENY;
}
}
- (santa_action_t)actionForRuleState:(santa_rulestate_t)state {
switch (state) {
case RULESTATE_WHITELIST:
return ACTION_RESPOND_CHECKBW_ALLOW;
case RULESTATE_BLACKLIST:
case RULESTATE_SILENT_BLACKLIST:
return ACTION_RESPOND_CHECKBW_DENY;
default:
return ACTION_ERROR;
}
}
- (void)loggedInUsers:(NSArray **)users sessions:(NSArray **)sessions {
struct utmpx *nxt;
NSMutableDictionary *loggedInUsers = [[NSMutableDictionary alloc] init];
NSMutableDictionary *loggedInHosts = [[NSMutableDictionary alloc] init];
while ((nxt = getutxent())) {
if (nxt->ut_type != USER_PROCESS) continue;
NSString *userName = [NSString stringWithUTF8String:nxt->ut_user];
NSString *sessionName;
if (strnlen(nxt->ut_host, 1) > 0) {
sessionName = [NSString stringWithFormat:@"%s@%s", nxt->ut_user, nxt->ut_host];
} else {
sessionName = [NSString stringWithFormat:@"%s@%s", nxt->ut_user, nxt->ut_line];
}
if (userName && ![userName isEqual:@""]) {
loggedInUsers[userName] = @"";
}
if (sessionName && ![sessionName isEqual:@":"]) {
loggedInHosts[sessionName] = @"";
}
}
endutxent();
*users = [loggedInUsers allKeys];
*sessions = [loggedInHosts allKeys];
}
@end

Some files were not shown because too many files have changed in this diff Show More