commit 07988686aecf4008c6dfd354f2bd7baa6f3cf372 Author: Russell Hancox Date: Thu Nov 20 16:23:13 2014 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9ea78595 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +Build +Santa.xcworkspace +Pods +Podfile.lock +Santa.xcodeproj/xcuserdata +Santa.xcodeproj/project.xcworkspace + diff --git a/Conf/com.google.santa.asl.conf b/Conf/com.google.santa.asl.conf new file mode 100644 index 00000000..2588dd20 --- /dev/null +++ b/Conf/com.google.santa.asl.conf @@ -0,0 +1,2 @@ +# Copy this file to /etc/asl to log all messages from santa-driver to the log file +? [S= Message santa-driver:] file /var/log/santa.log claim only format="[$((Time)(utc.3))] $Message" diff --git a/Conf/com.google.santad.plist b/Conf/com.google.santad.plist new file mode 100644 index 00000000..ea2e4ecf --- /dev/null +++ b/Conf/com.google.santad.plist @@ -0,0 +1,29 @@ + + + + + Label + com.google.santad + ProgramArguments + + /usr/libexec/santad + + MachServices + + SantaXPCNotifications + + SantaXPCControl + + + StandardOutPath + /var/log/santa.log + StandardErrorPath + /var/log/santa.log + RunAtLoad + + KeepAlive + + ProcessType + Interactive + + diff --git a/Conf/com.google.santagui.plist b/Conf/com.google.santagui.plist new file mode 100644 index 00000000..c0f26138 --- /dev/null +++ b/Conf/com.google.santagui.plist @@ -0,0 +1,18 @@ + + + + + Label + com.google.santagui + ProgramArguments + + /Applications/Santa.app/Contents/MacOS/Santa + + StandardOutPath + /var/log/santa.log + StandardErrorPath + /var/log/santa.log + RunAtLoad + + + diff --git a/Conf/com.google.santasync.plist b/Conf/com.google.santasync.plist new file mode 100644 index 00000000..3f11fcf2 --- /dev/null +++ b/Conf/com.google.santasync.plist @@ -0,0 +1,19 @@ + + + + + Label + com.google.santasync + ProgramArguments + + /usr/sbin/santactl + sync + + StandardErrorPath + /var/log/santa.log + ProcessType + Background + StartInterval + 600 + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/Podfile b/Podfile new file mode 100644 index 00000000..f5bae151 --- /dev/null +++ b/Podfile @@ -0,0 +1,12 @@ +platform :osx, "10.8" + +inhibit_all_warnings! + +target :santad do + pod 'FMDB' +end + +target :LogicTests do + pod 'OCMock' + pod 'FMDB' +end diff --git a/README.md b/README.md new file mode 100644 index 00000000..d3939670 --- /dev/null +++ b/README.md @@ -0,0 +1,157 @@ +Santa +===== + +Santa is a binary whitelisting/blacklisting system for Mac OS X. It consists of +a kernel extension that monitors for executions, a userland daemon that makes +execution decisions based on the contents of a SQLite database, a GUI agent that +notifies the user in case of a block decision and a command-line utility for +managing the system and synchronizing the database with a server. + +Santa is not yet a 1.0. We're writing more tests, fixing bugs, working on TODOs +and finishing up a security audit. + +Santa is named because it keeps track of binaries that are naughty and nice. + +Santa is a project of Google's Macintosh Operations Team. + +Features +======== + +* Multiple modes: MONITOR and LOCKDOWN. In MONITOR mode all binaries except +those marked as blacklisted will be allowed to run, whilst being logged and +recorded in the database. In LOCKDOWN mode, only whitelisted binaries are +allowed to run. + +* Codesign listing: Binaries can be whitelisted/blacklisted by their signing +certificate, so you can trust/block all binaries by a given publisher. The +binary will only be whitelisted by certificate if its signature validates +correctly. However, a decision for a binary will override a decision for a +certificate; i.e. you can whitelist a certificate while blacklisting a binary +signed by that certificate or vice-versa. + +* In-kernel caching: whitelisted binaries are cached in the kernel so the +processing required to make a request is only done if the binary +isn't already cached. + +* Userland components validate each other: each of the userland components (the +daemon, the GUI agent and the command-line utility) communicate with each other +using XPC and check that their signing certificates are identical before any +communication is accepted. + +* Event logging: all executions processed by the userland agent are logged and +all unknown or denied binaries are also stored in the database for upload to a +server. + +* Kext uses only KPIs: the kernel extension only uses provided kernel +programming interfaces to do its job. This means that the kext code should +continue to work across OS versions. + +Known Issues +============ + +Santa is not yet a 1.0 and we have some known issues to be aware of: + +* Potential race-condition: we currently have a single TODO in the kext code to +investigate a potential race condition where a binary is executed and then very +quickly modified between the kext getting the SHA-1 and the decision being made. + +* Kext communication security: the kext will only accept a connection from a +single client at a time and said client must be running as root. We haven't yet +found a good way to ensure the kext only accepts connections from a valid client, +short of hardcoding the SHA-1 in the kext. This shouldn't present a huge problem +as the daemon is loaded on boot-up by launchd, so any later attempts to connect +will be blocked. + +* Database protection: the SQLite database is installed with permissions so that +only the root user can read/write it. We're considering approaches to secure +this further. + +* Sync client: the command-line client includes a command to synchronize with a +management server, including the uploading of events that have occurred on the +machine and to download new rules. We're still very heavily working on this +server (which is AppEngine-based and will be open-sourced in the future), so the +sync client code is unfinished. It does show the 'API' that we're expecting to +use so if you'd like to write your own management server, feel free to look at +how the client currently works (and suggest changes!) + +* Scripts: Santa is currently written to ignore any execution that isn't a +binary. This is because after weighing the administration cost vs the benefit, +we found it wasn't worthwhile. Additionally, a number of applications make use +of temporary generated scripts, which we can't possibly whitelist and not doing +so would cause problems. We're happy to revisit this (or at least make it an +option) if it would be useful to others. + +* Documentation: There currently isn't any. + +* Tests: There aren't enough of them. + +Building +======== + +```sh +git clone https://github.com/google/santa +cd santa + +# Build a debug build. This will install any necessary CocoaPods, create the +# workspace and build, outputting the full log only if an error occurred. +# If CocoaPods is not installed, you'll be prompted to install it. +# +# For other build/install/run options, run rake without any arguments +rake build:debug +``` + +Note: the Xcode project is setup to use any installed "Mac Developer" certificate +and for security-reasons parts of Santa will not operate properly if not signed. + +Kext Signing +============ + +10.9 requires a special Developer ID certificate to sign kernel extensions and +if the kext is not signed with one of these special certificates a warning will +be shown when loading the kext for the first time. In 10.10 this is a hard error +and the kext will not load at all unless the machine is booted with a debug +boot-arg. If you want to test with the boot arg run the following command and +reboot: + +```sh +sudo nvram boot-args="kext-dev-mode=1" +``` + +There are two possible solutions for this: + +1) Use a pre-built, pre-signed version of the kext that we supply. Each time +changes are made to the kext code we will update the pre-built version that you +can make use of. This doesn't prevent you from making changes to the non-kext +parts of Santa and distributing those. If you make changes to the kext and make +a pull request, we can merge them in and distribute a new version of the +pre-signed kext. + +2) Apply for your own [kext signing certificate](https://developer.apple.com/contact/kext/). + +Contributing +============ + +Patches to this project are very much welcome. + +Before sending a patch or pull request, we ask you to fill out _one_ of the +Contributor License Agreements: + +* [Google Individual Contributor License Agreement, v1.1](https://developers.google.com/open-source/cla/individual) +* [Google Software Grant and Corporate Contributor License Agreement, v1.1](https://developers.google.com/open-source/cla/corporate) + +Also, if your change is large it would be a good idea to open an [Issue](https://github.com/google/santa/issues/new) +with a short description of your proposed change so we can quickly review it. +This is optional but it might save everyone time in the long run. + +Before making a pull request it's usually a good idea to run the tests, which +you can do with the following commands: + +```sh +rake tests:logic +rake tests:kernel # only necessary if you're changing the kext code +``` + +Disclaimer +========== + +This is **not** an official Google product. diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..fde1a496 --- /dev/null +++ b/Rakefile @@ -0,0 +1,210 @@ +require 'timeout' + +WORKSPACE = 'Santa.xcworkspace' +DEFAULT_SCHEME = 'All' +OUTPUT_PATH = 'build' +PLISTS = ['Source/SantaGUI/Resources/Santa-Info.plist', + 'Source/santad/Resources/santad-Info.plist', + 'Source/santa-driver/Resources/santa-driver-Info.plist', + 'Source/santactl/Resources/santactl-Info.plist'] +XCODE_DEFAULTS = "-workspace #{WORKSPACE} -scheme #{DEFAULT_SCHEME} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets" + +task :default do + system("rake -sT") +end + +def run_and_output_on_fail(cmd) + output=`#{cmd} 2>&1` + if not $?.success? + raise output + end +end + +def run_and_output_with_color(cmd) + output=`#{cmd} 2>&1` + + has_output = false + output.scan(/((Test Suite|Test Case|Executed).*)$/) do |match| + has_output = true + out = match[0] + if out.include?("passed") + puts "\e[32m#{out}\e[0m" + elsif out.include?("failed") + puts "\e[31m#{out}\e[0m" + else + puts out + end + end + + if not has_output + raise output + end +end + +task :init do + unless File.exists?(WORKSPACE) and File.exists?('Pods') + puts "Workspace is missing, running 'pod install'" + system "pod install" or raise "CocoaPods is not installed. Install with 'sudo gem install cocoapods'" + end +end + +task :remove_existing do + system 'sudo rm -rf /santa-driver.kext' + system 'sudo rm -rf /Applications/Santa.app' + system 'sudo rm /usr/libexec/santad' + system 'sudo rm /usr/sbin/santactl' +end + +desc "Clean" +task :clean => :init do + puts "Cleaning" + run_and_output_on_fail("xcodebuild #{XCODE_DEFAULTS} clean") + FileUtils.rm_rf(OUTPUT_PATH) +end + +# Build +namespace :build do + desc "Build: Debug" + task :debug do + Rake::Task['build:build'].invoke("Debug") + end + + desc "Build: Release" + task :release do + Rake::Task['build:build'].invoke("Release") + end + + task :build, [:configuration] => :init do |t, args| + config = args[:configuration] + puts "Building with configuration: #{config}" + run_and_output_on_fail("xcodebuild #{XCODE_DEFAULTS} -configuration #{config} build") + end +end + + +# Install +namespace :install do + desc "Install: Debug" + task :debug do + Rake::Task['install:install'].invoke("Debug") + end + + desc "Install: Release" + task :release do + Rake::Task['install:install'].invoke("Release") + end + + task :install, [:configuration] do |t, args| + config = args[:configuration] + system 'sudo cp conf/com.google.santad.plist /Library/LaunchDaemons' + system 'sudo cp conf/com.google.santasync.plist /Library/LaunchDaemons' + system 'sudo cp conf/com.google.santagui.plist /Library/LaunchAgents' + system 'sudo cp conf/com.google.santa.asl.conf /etc/asl' + Rake::Task['build:build'].invoke(config) + puts "Installing with configuration: #{config}" + Rake::Task['remove_existing'].invoke() + system "sudo cp -r #{OUTPUT_PATH}/Products/#{config}/santa-driver.kext /" + system "sudo cp -r #{OUTPUT_PATH}/Products/#{config}/Santa.app /Applications" + system "sudo cp #{OUTPUT_PATH}/Products/#{config}/santad /usr/libexec" + system "sudo cp #{OUTPUT_PATH}/Products/#{config}/santactl /usr/sbin" + end +end + +# Tests +namespace :tests do + desc "Tests: Logic" + task :logic => [:init] do + puts "Running logic tests" + run_and_output_with_color("xcodebuild #{XCODE_DEFAULTS} test") + end + + desc "Tests: Kernel" + task :kernel do + Rake::Task['unload'].invoke() + Rake::Task['install:debug'].invoke() + Rake::Task['load_kext'].invoke + timeout = 30 + puts "Running kernel tests with a #{timeout} second timeout" + begin + Timeout::timeout(timeout) { + system "sudo #{OUTPUT_PATH}/Build/Products/Debug/KernelTests" + } + rescue Timeout::Error + puts "ERROR: tests ran for longer than #{timeout} seconds and were killed." + end + Rake::Task['unload_kext'].execute + end +end + +# Load/Unload +task :unload_daemon do + puts "Unloading daemon" + system "sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null" +end + +task :unload_kext do + puts "Unloading kernel extension" + system "sudo kextunload /santa-driver.kext 2>/dev/null" +end + +task :unload_gui do + puts "Unloading GUI agent" + system "sudo killall Santa 2>/dev/null" +end + +desc "Unload" +task :unload => [:unload_daemon, :unload_kext, :unload_gui] + +task :load_daemon do + puts "Loading daemon" + system "sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist" +end + +task :load_kext do + puts "Loading kernel extension" + system "sudo kextload /santa-driver.kext" +end + +task :load_gui do + puts "Loading GUI agent" + system "open /Applications/Santa.app" +end + +desc "Load" +task :load => [:load_kext, :load_daemon, :load_gui] + +namespace :reload do + desc "Reload: Debug" + task :debug do + Rake::Task['unload'].invoke() + Rake::Task['install:debug'].invoke() + Rake::Task['load'].invoke() + end + + desc "Reload: Release" + task :release do + Rake::Task['unload'].invoke() + Rake::Task['install:release'].invoke() + Rake::Task['load'].invoke() + end +end + +# Versioning +desc "Update version, version should be of the form rake version[\\d{1,4}.\\d{1,2}(?:.\\d{1,2})?]" +task :version, :version do |t, args| + response = args[:version] + + unless response =~ /^\d{1,4}\.\d{1,2}(?:\.\d{1,2})?$/ + raise "Version number must be of form: xxxx.xx[.xx]. E.g: rake version[1.0.2], rake version[1.7]" + end + + system "sed -i -e 's/MODULE_VERSION = .*;/MODULE_VERSION = #{response};/g' Santa.xcodeproj/project.pbxproj" + + PLISTS.each do |plist| + system "defaults write $PWD/#{plist} CFBundleVersion #{response}" + system "defaults write $PWD/#{plist} CFBundleShortVersionString #{response}" + system "plutil -convert xml1 $PWD/#{plist}" + end + + puts "Updated version to #{response}" +end diff --git a/Santa.xcodeproj/project.pbxproj b/Santa.xcodeproj/project.pbxproj new file mode 100644 index 00000000..8c5b997b --- /dev/null +++ b/Santa.xcodeproj/project.pbxproj @@ -0,0 +1,1704 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXAggregateTarget section */ + 0D91BCDC174E8AE600131A7D /* All */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 0D91BCDD174E8AE600131A7D /* Build configuration list for PBXAggregateTarget "All" */; + buildPhases = ( + ); + dependencies = ( + 0D91BCE1174E8AED00131A7D /* PBXTargetDependency */, + 0D9A7F5A1759393600035EB5 /* PBXTargetDependency */, + 0D35BDB818FD859300921A21 /* PBXTargetDependency */, + 0D385DF5180DE73A00418BC6 /* PBXTargetDependency */, + 0D91BD5F1A01571D00DF4A0E /* PBXTargetDependency */, + 0D91BD611A01571D00DF4A0E /* PBXTargetDependency */, + ); + name = All; + productName = All; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 0D0016A3192BCD3C005E7FCD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D9A7F3E1759330500035EB5 /* Foundation.framework */; }; + 0D0016A6192BCD3C005E7FCD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D0016A5192BCD3C005E7FCD /* main.m */; }; + 0D0016AE192BCD8C005E7FCD /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D3AFBF718FB4C870087BCEE /* IOKit.framework */; }; + 0D0A1EC3191998C900B8450F /* SNTCommandSyncRuleDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D0A1EC2191998C900B8450F /* SNTCommandSyncRuleDownload.m */; }; + 0D0A1EC6191AB9B000B8450F /* SNTCommandSyncPostflight.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D0A1EC5191AB9B000B8450F /* SNTCommandSyncPostflight.m */; }; + 0D10BE861A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */; }; + 0D10BE871A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */; }; + 0D10BE891A0AAF6700C0C944 /* SNTDropRootPrivs.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */; }; + 0D10BE8B1A0AB23300C0C944 /* SNTDERDecoderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D10BE8A1A0AB23300C0C944 /* SNTDERDecoderTest.m */; }; + 0D10BE8C1A0AB3FD00C0C944 /* SNTDERDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D7FFD4A1A017D4B00F34435 /* SNTDERDecoder.m */; }; + 0D1AF477187C7A2C00D3298D /* SNTCertificate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D1AF476187C7A2C00D3298D /* SNTCertificate.m */; }; + 0D1AF478187C7A2C00D3298D /* SNTCertificate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D1AF476187C7A2C00D3298D /* SNTCertificate.m */; }; + 0D1B477019A53419008CADD3 /* SNTAboutWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D1B476E19A53419008CADD3 /* SNTAboutWindowController.m */; }; + 0D1B477119A53419008CADD3 /* AboutWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0D1B476F19A53419008CADD3 /* AboutWindow.xib */; }; + 0D260DAE18B68E12002A0B55 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D260DAD18B68E12002A0B55 /* XCTest.framework */; }; + 0D260DC118B69078002A0B55 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D260DC018B69078002A0B55 /* Security.framework */; }; + 0D28D53819D9F5910015C5EB /* SNTConfigurator.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D42D2B719D2042900955F08 /* SNTConfigurator.m */; }; + 0D31DF4718D254B3002B300D /* SNTCodesignChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D59C0E317710E6000748EBF /* SNTCodesignChecker.m */; }; + 0D35BD9F18FD71CE00921A21 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D9A7F3E1759330500035EB5 /* Foundation.framework */; }; + 0D35BDA218FD71CE00921A21 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDA118FD71CE00921A21 /* main.m */; }; + 0D35BDAC18FD7CFD00921A21 /* SNTCommandController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDAB18FD7CFD00921A21 /* SNTCommandController.m */; }; + 0D35BDB518FD84F600921A21 /* SNTCommandSync.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDB418FD84F600921A21 /* SNTCommandSync.m */; }; + 0D35BDBD18FDA23600921A21 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D3AFBF718FB4C870087BCEE /* IOKit.framework */; }; + 0D35BDC018FDA5C800921A21 /* SNTCertificate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D1AF476187C7A2C00D3298D /* SNTCertificate.m */; }; + 0D35BDC218FDA5D100921A21 /* SNTCodesignChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D59C0E317710E6000748EBF /* SNTCodesignChecker.m */; }; + 0D35BDC418FDA5D100921A21 /* SNTXPCConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */; }; + 0D377C2A17A071B7008453DB /* SNTEventTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D377C2917A071B7008453DB /* SNTEventTable.m */; }; + 0D37C10F18F6029A0069BC61 /* SNTDatabaseTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D37C10E18F6029A0069BC61 /* SNTDatabaseTable.m */; }; + 0D385DB8180DE4A900418BC6 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D385DB7180DE4A900418BC6 /* Cocoa.framework */; }; + 0D385DC4180DE4A900418BC6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D385DC3180DE4A900418BC6 /* main.m */; }; + 0D385DD0180DE4A900418BC6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D385DCF180DE4A900418BC6 /* Images.xcassets */; }; + 0D385DF0180DE51600418BC6 /* MessageWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0D385DE9180DE51600418BC6 /* MessageWindow.xib */; }; + 0D385DF1180DE51600418BC6 /* SNTAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D385DEB180DE51600418BC6 /* SNTAppDelegate.m */; }; + 0D385DF2180DE51600418BC6 /* SNTMessageWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D385DED180DE51600418BC6 /* SNTMessageWindowController.m */; }; + 0D385DF3180DE51600418BC6 /* SNTNotificationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D385DEF180DE51600418BC6 /* SNTNotificationManager.m */; }; + 0D3AFBE718FB32CB0087BCEE /* SNTXPCConnectionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D3AFBE618FB32CB0087BCEE /* SNTXPCConnectionTest.m */; }; + 0D3AFBEB18FB48E70087BCEE /* SNTDatabaseTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D37C10E18F6029A0069BC61 /* SNTDatabaseTable.m */; }; + 0D3AFBEC18FB48E70087BCEE /* SNTEventTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D377C2917A071B7008453DB /* SNTEventTable.m */; }; + 0D3AFBEE18FB4C6C0087BCEE /* SNTApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DB8ACC0185662DC00FEF9C7 /* SNTApplication.m */; }; + 0D3AFBEF18FB4C6C0087BCEE /* SNTExecutionController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */; }; + 0D3AFBF018FB4C6C0087BCEE /* SNTDriverManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D7D01861774F93A005DBAB4 /* SNTDriverManager.m */; }; + 0D3AFBF618FB4C7E0087BCEE /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D385DB7180DE4A900418BC6 /* Cocoa.framework */; }; + 0D3AFBF818FB4C870087BCEE /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D3AFBF718FB4C870087BCEE /* IOKit.framework */; }; + 0D416401191974F1006A356A /* SNTCommandSyncStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D416400191974F1006A356A /* SNTCommandSyncStatus.m */; }; + 0D41640519197AD7006A356A /* SNTCommandSyncEventUpload.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D41640419197AD7006A356A /* SNTCommandSyncEventUpload.m */; }; + 0D42D2B519D1D98A00955F08 /* SNTSystemInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D42D2B419D1D98A00955F08 /* SNTSystemInfo.m */; }; + 0D42D2B819D2042900955F08 /* SNTConfigurator.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D42D2B719D2042900955F08 /* SNTConfigurator.m */; }; + 0D42D2B919D2042900955F08 /* SNTConfigurator.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D42D2B719D2042900955F08 /* SNTConfigurator.m */; }; + 0D4644C5182AF81700098690 /* SantaDecisionManager.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0D4644C3182AF81700098690 /* SantaDecisionManager.cc */; }; + 0D4644C6182AF81700098690 /* SantaDecisionManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D4644C4182AF81700098690 /* SantaDecisionManager.h */; }; + 0D4A5007176A4602004F63BF /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D4A5006176A4602004F63BF /* Security.framework */; }; + 0D54E0B11976F8D3000BB59F /* SNTBinaryInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD6041190ACCB8006B445C /* SNTBinaryInfo.m */; }; + 0D59C0E417710E6000748EBF /* SNTCodesignChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D59C0E317710E6000748EBF /* SNTCodesignChecker.m */; }; + 0D63DD5C1906FCB400D346C4 /* SNTDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */; }; + 0D63DD5E1906FCB400D346C4 /* SNTDatabaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */; }; + 0D668E8118D1121700E29A8B /* SNTMessageWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D668E8018D1121700E29A8B /* SNTMessageWindow.m */; }; + 0D6F12D819EC8822006B218E /* SecurityInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DCD5F771909C659006B445C /* SecurityInterface.framework */; }; + 0D6F12DA19EDE51E006B218E /* tubitak.crt in Resources */ = {isa = PBXBuildFile; fileRef = 0D6F12D919EDE411006B218E /* tubitak.crt */; }; + 0D6FDC8318C68D7E0044685C /* GIAG2.crt in Resources */ = {isa = PBXBuildFile; fileRef = 0D6FDC8218C68D7E0044685C /* GIAG2.crt */; }; + 0D6FDC8518C68E500044685C /* GIAG2.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0D6FDC8418C68E500044685C /* GIAG2.pem */; }; + 0D6FDC8718C6913D0044685C /* apple.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0D6FDC8618C6913D0044685C /* apple.pem */; }; + 0D6FDC8C18C69AF90044685C /* SNTCertificate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D1AF476187C7A2C00D3298D /* SNTCertificate.m */; }; + 0D6FDC8F18C7F0200044685C /* SNTNotificationMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DBE65F018BEA3CC00AC994C /* SNTNotificationMessage.m */; }; + 0D6FDC9618C93A020044685C /* SNTXPCConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */; }; + 0D6FDC9718C93A020044685C /* SNTXPCConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */; }; + 0D7A7AF3174FCF4C00B77646 /* SantaMessage.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0D7A7AF1174FCF4C00B77646 /* SantaMessage.cc */; }; + 0D7A7AF4174FCF4C00B77646 /* SantaMessage.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D7A7AF2174FCF4C00B77646 /* SantaMessage.h */; }; + 0D7D01871774F93A005DBAB4 /* SNTDriverManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D7D01861774F93A005DBAB4 /* SNTDriverManager.m */; }; + 0D7FFD4B1A017D4B00F34435 /* SNTDERDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D7FFD4A1A017D4B00F34435 /* SNTDERDecoder.m */; }; + 0D827E6519DF392E006EC811 /* SNTConfigurator.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D42D2B719D2042900955F08 /* SNTConfigurator.m */; }; + 0D827E6719DF3C74006EC811 /* SNTCommandStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D827E6619DF3C74006EC811 /* SNTCommandStatus.m */; }; + 0D8C200C180F359A00CE2BF8 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D8C200B180F359A00CE2BF8 /* Security.framework */; }; + 0D8E18CD19107B56000F89B8 /* SNTDaemonControlController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D8E18CC19107B56000F89B8 /* SNTDaemonControlController.m */; }; + 0D9A7F331759144800035EB5 /* SantaDriver.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0D9A7F311759144800035EB5 /* SantaDriver.cc */; }; + 0D9A7F341759144800035EB5 /* SantaDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D9A7F321759144800035EB5 /* SantaDriver.h */; }; + 0D9A7F371759148E00035EB5 /* SantaDriverClient.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0D9A7F351759148E00035EB5 /* SantaDriverClient.cc */; }; + 0D9A7F381759148E00035EB5 /* SantaDriverClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 0D9A7F361759148E00035EB5 /* SantaDriverClient.h */; }; + 0D9A7F3F1759330500035EB5 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D9A7F3E1759330500035EB5 /* Foundation.framework */; }; + 0D9A7F421759330500035EB5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D9A7F411759330500035EB5 /* main.m */; }; + 0DA73C9F1934F8100056D7C4 /* SNTLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA73C9E1934F8100056D7C4 /* SNTLogging.m */; }; + 0DA73CA11934F8100056D7C4 /* SNTLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA73C9E1934F8100056D7C4 /* SNTLogging.m */; }; + 0DA73CA21934F88D0056D7C4 /* SNTLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA73C9E1934F8100056D7C4 /* SNTLogging.m */; }; + 0DB8ACC1185662DC00FEF9C7 /* SNTApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DB8ACC0185662DC00FEF9C7 /* SNTApplication.m */; }; + 0DBE65F118BEA3CC00AC994C /* SNTNotificationMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DBE65F018BEA3CC00AC994C /* SNTNotificationMessage.m */; }; + 0DC5D86D191AED220078A5C0 /* SNTRuleTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC5D86C191AED220078A5C0 /* SNTRuleTable.m */; }; + 0DC5D86E191AED220078A5C0 /* SNTRuleTable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC5D86C191AED220078A5C0 /* SNTRuleTable.m */; }; + 0DC5D871192160180078A5C0 /* SNTCommandSyncLogUpload.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DC5D870192160180078A5C0 /* SNTCommandSyncLogUpload.m */; }; + 0DCA552718C95928002A7DAE /* SNTXPCConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */; }; + 0DCD5FBF1909D64A006B445C /* SNTCommandBinaryInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD5FBE1909D64A006B445C /* SNTCommandBinaryInfo.m */; }; + 0DCD6042190ACCB8006B445C /* SNTBinaryInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD6041190ACCB8006B445C /* SNTBinaryInfo.m */; }; + 0DCD6043190ACCB8006B445C /* SNTBinaryInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD6041190ACCB8006B445C /* SNTBinaryInfo.m */; }; + 0DCD6044190ACCB8006B445C /* SNTBinaryInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD6041190ACCB8006B445C /* SNTBinaryInfo.m */; }; + 0DCD604B19105433006B445C /* SNTStoredEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD604A19105433006B445C /* SNTStoredEvent.m */; }; + 0DCD604D19105433006B445C /* SNTStoredEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD604A19105433006B445C /* SNTStoredEvent.m */; }; + 0DCD604F19115A06006B445C /* SNTXPCNotifierInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD604E19115A06006B445C /* SNTXPCNotifierInterface.m */; }; + 0DCD605119115A06006B445C /* SNTXPCNotifierInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD604E19115A06006B445C /* SNTXPCNotifierInterface.m */; }; + 0DCD605519115D17006B445C /* SNTXPCControlInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD605419115D17006B445C /* SNTXPCControlInterface.m */; }; + 0DCD605619115D17006B445C /* SNTXPCControlInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD605419115D17006B445C /* SNTXPCControlInterface.m */; }; + 0DCD605719115E54006B445C /* SNTDaemonControlController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D8E18CC19107B56000F89B8 /* SNTDaemonControlController.m */; }; + 0DCD605819115E57006B445C /* SNTXPCControlInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD605419115D17006B445C /* SNTXPCControlInterface.m */; }; + 0DCD605919115E5A006B445C /* SNTXPCNotifierInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD604E19115A06006B445C /* SNTXPCNotifierInterface.m */; }; + 0DCD605C19117A90006B445C /* SNTCommandSyncPreflight.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD605B19117A90006B445C /* SNTCommandSyncPreflight.m */; }; + 0DCD6062191188B1006B445C /* SNTAuthenticatingURLSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD6061191188B1006B445C /* SNTAuthenticatingURLSession.m */; }; + 0DD0D487194F5187005F27EB /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D3AFBF718FB4C870087BCEE /* IOKit.framework */; }; + 0DD0D48B194F6193005F27EB /* SNTCertificateTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD0D48A194F6193005F27EB /* SNTCertificateTest.m */; }; + 0DD0D48D194F6D5B005F27EB /* SNTCodesignCheckerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD0D48C194F6D5B005F27EB /* SNTCodesignCheckerTest.m */; }; + 0DD0D48F194F78F8005F27EB /* SNTBinaryInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD0D48E194F78F8005F27EB /* SNTBinaryInfoTest.m */; }; + 0DD0D491194F9947005F27EB /* SNTExecutionControllerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DD0D490194F9947005F27EB /* SNTExecutionControllerTest.m */; }; + 0DD0D492194F9BEF005F27EB /* SNTLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DA73C9E1934F8100056D7C4 /* SNTLogging.m */; }; + 0DD65D98184D2F0A00822DA7 /* SNTCodesignChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D59C0E317710E6000748EBF /* SNTCodesignChecker.m */; }; + 0DE4C8A118FEF28200466D04 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D8C200B180F359A00CE2BF8 /* Security.framework */; }; + 0DE4C8A618FF3B1700466D04 /* SNTCommandFlushCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE4C8A518FF3B1700466D04 /* SNTCommandFlushCache.m */; }; + 0DE50F681912716A007B2B0C /* SNTRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE50F671912716A007B2B0C /* SNTRule.m */; }; + 0DE50F691912B0CD007B2B0C /* SNTRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE50F671912716A007B2B0C /* SNTRule.m */; }; + 0DE50F6C19130358007B2B0C /* SNTStoredEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD604A19105433006B445C /* SNTStoredEvent.m */; }; + 0DE50F6D191303E3007B2B0C /* SNTNotificationMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DBE65F018BEA3CC00AC994C /* SNTNotificationMessage.m */; }; + 0DE50F6E191304E0007B2B0C /* SNTRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE50F671912716A007B2B0C /* SNTRule.m */; }; + 0DE6788D1784A8C2007A9E52 /* SNTExecutionController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */; }; + 8BFD9B39112F4D16B3D0EFFB /* libPods-LogicTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 752301D17AA44BDE8B6D0541 /* libPods-LogicTests.a */; }; + E86AE075D7F24FB88FB627C5 /* libPods-santad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A84545E322F475FA0B505D5 /* libPods-santad.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 0D35BDB718FD859300921A21 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0D91BCA8174E8A6500131A7D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0D35BD9D18FD71CE00921A21; + remoteInfo = santactl; + }; + 0D385DF4180DE73A00418BC6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0D91BCA8174E8A6500131A7D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0D385DB5180DE4A900418BC6; + remoteInfo = SantaNotifier; + }; + 0D91BCE0174E8AED00131A7D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0D91BCA8174E8A6500131A7D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0D91BCB3174E8A7E00131A7D; + remoteInfo = "santa-driver"; + }; + 0D91BD5E1A01571D00DF4A0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0D91BCA8174E8A6500131A7D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0D260DAB18B68E12002A0B55; + remoteInfo = LogicTests; + }; + 0D91BD601A01571D00DF4A0E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0D91BCA8174E8A6500131A7D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0D0016A1192BCD3C005E7FCD; + remoteInfo = KernelTests; + }; + 0D9A7F591759393600035EB5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0D91BCA8174E8A6500131A7D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0D9A7F3C1759330400035EB5; + remoteInfo = santad; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 0D9A7F3B1759330400035EB5 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0A84545E322F475FA0B505D5 /* libPods-santad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santad.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D0016A2192BCD3C005E7FCD /* KernelTests */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = KernelTests; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D0016A5192BCD3C005E7FCD /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 0D0A1EC1191998C900B8450F /* SNTCommandSyncRuleDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncRuleDownload.h; sourceTree = ""; }; + 0D0A1EC2191998C900B8450F /* SNTCommandSyncRuleDownload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncRuleDownload.m; sourceTree = ""; }; + 0D0A1EC4191AB9B000B8450F /* SNTCommandSyncPostflight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncPostflight.h; sourceTree = ""; }; + 0D0A1EC5191AB9B000B8450F /* SNTCommandSyncPostflight.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncPostflight.m; sourceTree = ""; }; + 0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTDropRootPrivs.m; sourceTree = ""; }; + 0D10BE881A0AAC2100C0C944 /* SNTDropRootPrivs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTDropRootPrivs.h; sourceTree = ""; }; + 0D10BE8A1A0AB23300C0C944 /* SNTDERDecoderTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTDERDecoderTest.m; sourceTree = ""; }; + 0D1AF475187C7A2C00D3298D /* SNTCertificate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCertificate.h; sourceTree = ""; }; + 0D1AF476187C7A2C00D3298D /* SNTCertificate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCertificate.m; sourceTree = ""; }; + 0D1B476D19A53419008CADD3 /* SNTAboutWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTAboutWindowController.h; sourceTree = ""; }; + 0D1B476E19A53419008CADD3 /* SNTAboutWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTAboutWindowController.m; sourceTree = ""; }; + 0D1B476F19A53419008CADD3 /* AboutWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AboutWindow.xib; sourceTree = ""; }; + 0D260DAC18B68E12002A0B55 /* LogicTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LogicTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D260DAD18B68E12002A0B55 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 0D260DB118B68E12002A0B55 /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = ""; }; + 0D260DB718B68E12002A0B55 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = ""; }; + 0D260DC018B69078002A0B55 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; }; + 0D28E5E119269B3600280F87 /* SNTLogging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTLogging.h; sourceTree = ""; }; + 0D28E5E31926AFE400280F87 /* SNTKernelCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTKernelCommon.h; sourceTree = ""; }; + 0D28E5E41926B55600280F87 /* santactl-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "santactl-Info.plist"; sourceTree = ""; }; + 0D35BD9E18FD71CE00921A21 /* santactl */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = santactl; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D35BDA118FD71CE00921A21 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 0D35BDA418FD71CE00921A21 /* santactl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "santactl-Prefix.pch"; sourceTree = ""; }; + 0D35BDAA18FD7CFD00921A21 /* SNTCommandController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandController.h; sourceTree = ""; }; + 0D35BDAB18FD7CFD00921A21 /* SNTCommandController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandController.m; sourceTree = ""; }; + 0D35BDB418FD84F600921A21 /* SNTCommandSync.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSync.m; sourceTree = ""; }; + 0D377C2417A06DDE008453DB /* SNTRuleTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTRuleTable.h; sourceTree = ""; }; + 0D377C2817A071B7008453DB /* SNTEventTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTEventTable.h; sourceTree = ""; }; + 0D377C2917A071B7008453DB /* SNTEventTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTEventTable.m; sourceTree = ""; }; + 0D37C10D18F6029A0069BC61 /* SNTDatabaseTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTDatabaseTable.h; sourceTree = ""; }; + 0D37C10E18F6029A0069BC61 /* SNTDatabaseTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTDatabaseTable.m; sourceTree = ""; }; + 0D385DB6180DE4A900418BC6 /* Santa.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Santa.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D385DB7180DE4A900418BC6 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 0D385DBF180DE4A900418BC6 /* Santa-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Santa-Info.plist"; sourceTree = ""; }; + 0D385DC3180DE4A900418BC6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 0D385DC5180DE4A900418BC6 /* Santa-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Santa-Prefix.pch"; sourceTree = ""; }; + 0D385DCF180DE4A900418BC6 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + 0D385DE9180DE51600418BC6 /* MessageWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MessageWindow.xib; sourceTree = ""; }; + 0D385DEA180DE51600418BC6 /* SNTAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTAppDelegate.h; sourceTree = ""; }; + 0D385DEB180DE51600418BC6 /* SNTAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTAppDelegate.m; sourceTree = ""; }; + 0D385DEC180DE51600418BC6 /* SNTMessageWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTMessageWindowController.h; sourceTree = ""; }; + 0D385DED180DE51600418BC6 /* SNTMessageWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTMessageWindowController.m; sourceTree = ""; }; + 0D385DEE180DE51600418BC6 /* SNTNotificationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTNotificationManager.h; sourceTree = ""; }; + 0D385DEF180DE51600418BC6 /* SNTNotificationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTNotificationManager.m; sourceTree = ""; }; + 0D3AFBE618FB32CB0087BCEE /* SNTXPCConnectionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTXPCConnectionTest.m; sourceTree = ""; }; + 0D3AFBF718FB4C870087BCEE /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; + 0D4163FF191974F1006A356A /* SNTCommandSyncStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncStatus.h; sourceTree = ""; }; + 0D416400191974F1006A356A /* SNTCommandSyncStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncStatus.m; sourceTree = ""; }; + 0D41640319197AD7006A356A /* SNTCommandSyncEventUpload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncEventUpload.h; sourceTree = ""; }; + 0D41640419197AD7006A356A /* SNTCommandSyncEventUpload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncEventUpload.m; sourceTree = ""; }; + 0D42D2B319D1D98A00955F08 /* SNTSystemInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTSystemInfo.h; sourceTree = ""; }; + 0D42D2B419D1D98A00955F08 /* SNTSystemInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTSystemInfo.m; sourceTree = ""; }; + 0D42D2B619D2042900955F08 /* SNTConfigurator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTConfigurator.h; sourceTree = ""; }; + 0D42D2B719D2042900955F08 /* SNTConfigurator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTConfigurator.m; sourceTree = ""; }; + 0D4644C3182AF81700098690 /* SantaDecisionManager.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SantaDecisionManager.cc; sourceTree = ""; }; + 0D4644C4182AF81700098690 /* SantaDecisionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SantaDecisionManager.h; sourceTree = ""; }; + 0D4A5006176A4602004F63BF /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = ""; }; + 0D59C0E217710E6000748EBF /* SNTCodesignChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCodesignChecker.h; sourceTree = ""; }; + 0D59C0E317710E6000748EBF /* SNTCodesignChecker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCodesignChecker.m; sourceTree = ""; }; + 0D63DD5A1906FCB400D346C4 /* SNTDatabaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTDatabaseController.h; sourceTree = ""; }; + 0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTDatabaseController.m; sourceTree = ""; }; + 0D668E7F18D1121700E29A8B /* SNTMessageWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTMessageWindow.h; sourceTree = ""; }; + 0D668E8018D1121700E29A8B /* SNTMessageWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTMessageWindow.m; sourceTree = ""; }; + 0D6F12D919EDE411006B218E /* tubitak.crt */ = {isa = PBXFileReference; lastKnownFileType = file; path = tubitak.crt; sourceTree = ""; }; + 0D6FDC8218C68D7E0044685C /* GIAG2.crt */ = {isa = PBXFileReference; lastKnownFileType = file; path = GIAG2.crt; sourceTree = ""; }; + 0D6FDC8418C68E500044685C /* GIAG2.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GIAG2.pem; sourceTree = ""; }; + 0D6FDC8618C6913D0044685C /* apple.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = apple.pem; sourceTree = ""; }; + 0D6FDC9418C93A020044685C /* SNTXPCConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTXPCConnection.h; sourceTree = ""; }; + 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTXPCConnection.m; sourceTree = ""; }; + 0D7A7AF1174FCF4C00B77646 /* SantaMessage.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SantaMessage.cc; sourceTree = ""; }; + 0D7A7AF2174FCF4C00B77646 /* SantaMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SantaMessage.h; sourceTree = ""; }; + 0D7D01851774F93A005DBAB4 /* SNTDriverManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTDriverManager.h; sourceTree = ""; }; + 0D7D01861774F93A005DBAB4 /* SNTDriverManager.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = SNTDriverManager.m; sourceTree = ""; }; + 0D7FFD491A017D4B00F34435 /* SNTDERDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTDERDecoder.h; sourceTree = ""; }; + 0D7FFD4A1A017D4B00F34435 /* SNTDERDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTDERDecoder.m; sourceTree = ""; }; + 0D827E6619DF3C74006EC811 /* SNTCommandStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SNTCommandStatus.m; path = status/SNTCommandStatus.m; sourceTree = ""; }; + 0D8C200B180F359A00CE2BF8 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 0D8E18CB19107B56000F89B8 /* SNTDaemonControlController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTDaemonControlController.h; sourceTree = ""; }; + 0D8E18CC19107B56000F89B8 /* SNTDaemonControlController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTDaemonControlController.m; sourceTree = ""; }; + 0D91BCB4174E8A7E00131A7D /* santa-driver.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "santa-driver.kext"; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D91BCB8174E8A7E00131A7D /* Kernel.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Kernel.framework; path = System/Library/Frameworks/Kernel.framework; sourceTree = SDKROOT; }; + 0D91BCBB174E8A7E00131A7D /* santa-driver-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "santa-driver-Info.plist"; sourceTree = ""; }; + 0D91BCE4174E8B5E00131A7D /* SNTCommonEnums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommonEnums.h; sourceTree = ""; }; + 0D9A7F311759144800035EB5 /* SantaDriver.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = SantaDriver.cc; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 0D9A7F321759144800035EB5 /* SantaDriver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SantaDriver.h; sourceTree = ""; }; + 0D9A7F351759148E00035EB5 /* SantaDriverClient.cc */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.cpp.cpp; lineEnding = 0; path = SantaDriverClient.cc; sourceTree = ""; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.cpp; }; + 0D9A7F361759148E00035EB5 /* SantaDriverClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SantaDriverClient.h; sourceTree = ""; }; + 0D9A7F3D1759330500035EB5 /* santad */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = santad; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D9A7F3E1759330500035EB5 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 0D9A7F411759330500035EB5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = main.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 0DA73C9E1934F8100056D7C4 /* SNTLogging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTLogging.m; sourceTree = ""; }; + 0DB2B92318085753001C01D9 /* santad-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "santad-Prefix.pch"; sourceTree = ""; }; + 0DB8ACBF185662DC00FEF9C7 /* SNTApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTApplication.h; sourceTree = ""; }; + 0DB8ACC0185662DC00FEF9C7 /* SNTApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = SNTApplication.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 0DB8ACE41858D73000FEF9C7 /* santad-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "santad-Info.plist"; sourceTree = ""; }; + 0DBE65EF18BEA3CC00AC994C /* SNTNotificationMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTNotificationMessage.h; sourceTree = ""; }; + 0DBE65F018BEA3CC00AC994C /* SNTNotificationMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTNotificationMessage.m; sourceTree = ""; }; + 0DC5D86C191AED220078A5C0 /* SNTRuleTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTRuleTable.m; sourceTree = ""; }; + 0DC5D86F192160180078A5C0 /* SNTCommandSyncLogUpload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncLogUpload.h; sourceTree = ""; }; + 0DC5D870192160180078A5C0 /* SNTCommandSyncLogUpload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncLogUpload.m; sourceTree = ""; }; + 0DC8C9E3180CC3BC00FCFB29 /* SNTXPCNotifierInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTXPCNotifierInterface.h; sourceTree = ""; }; + 0DCD5F771909C659006B445C /* SecurityInterface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SecurityInterface.framework; path = System/Library/Frameworks/SecurityInterface.framework; sourceTree = SDKROOT; }; + 0DCD5FBE1909D64A006B445C /* SNTCommandBinaryInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandBinaryInfo.m; sourceTree = ""; }; + 0DCD6040190ACCB8006B445C /* SNTBinaryInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTBinaryInfo.h; sourceTree = ""; }; + 0DCD6041190ACCB8006B445C /* SNTBinaryInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTBinaryInfo.m; sourceTree = ""; }; + 0DCD604919105433006B445C /* SNTStoredEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTStoredEvent.h; sourceTree = ""; }; + 0DCD604A19105433006B445C /* SNTStoredEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTStoredEvent.m; sourceTree = ""; }; + 0DCD604E19115A06006B445C /* SNTXPCNotifierInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTXPCNotifierInterface.m; sourceTree = ""; }; + 0DCD605319115D17006B445C /* SNTXPCControlInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTXPCControlInterface.h; sourceTree = ""; }; + 0DCD605419115D17006B445C /* SNTXPCControlInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTXPCControlInterface.m; sourceTree = ""; }; + 0DCD605A19117A90006B445C /* SNTCommandSyncPreflight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncPreflight.h; sourceTree = ""; }; + 0DCD605B19117A90006B445C /* SNTCommandSyncPreflight.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncPreflight.m; sourceTree = ""; }; + 0DCD6060191188B1006B445C /* SNTAuthenticatingURLSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTAuthenticatingURLSession.h; sourceTree = ""; }; + 0DCD6061191188B1006B445C /* SNTAuthenticatingURLSession.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTAuthenticatingURLSession.m; sourceTree = ""; }; + 0DD0D48A194F6193005F27EB /* SNTCertificateTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCertificateTest.m; sourceTree = ""; }; + 0DD0D48C194F6D5B005F27EB /* SNTCodesignCheckerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCodesignCheckerTest.m; sourceTree = ""; }; + 0DD0D48E194F78F8005F27EB /* SNTBinaryInfoTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTBinaryInfoTest.m; sourceTree = ""; }; + 0DD0D490194F9947005F27EB /* SNTExecutionControllerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTExecutionControllerTest.m; sourceTree = ""; }; + 0DE4C8A518FF3B1700466D04 /* SNTCommandFlushCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandFlushCache.m; sourceTree = ""; }; + 0DE50F6619127169007B2B0C /* SNTRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTRule.h; sourceTree = ""; }; + 0DE50F671912716A007B2B0C /* SNTRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTRule.m; sourceTree = ""; }; + 0DE6788B1784A8C2007A9E52 /* SNTExecutionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTExecutionController.h; sourceTree = ""; }; + 0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = SNTExecutionController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 13A4FE400F3857C0F5831498 /* Pods-LogicTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LogicTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LogicTests/Pods-LogicTests.debug.xcconfig"; sourceTree = ""; }; + 627BB4EC9917DC20E89D718C /* Pods-santad.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-santad.debug.xcconfig"; path = "Pods/Target Support Files/Pods-santad/Pods-santad.debug.xcconfig"; sourceTree = ""; }; + 752301D17AA44BDE8B6D0541 /* libPods-LogicTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LogicTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 8003CA1D3E46447BCEA56440 /* Pods-santad.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-santad.release.xcconfig"; path = "Pods/Target Support Files/Pods-santad/Pods-santad.release.xcconfig"; sourceTree = ""; }; + BE74E23CF5A553E5F02462B9 /* Pods-LogicTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LogicTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-LogicTests/Pods-LogicTests.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0D00169F192BCD3C005E7FCD /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D0016AE192BCD8C005E7FCD /* IOKit.framework in Frameworks */, + 0D0016A3192BCD3C005E7FCD /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0D260DA918B68E12002A0B55 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D3AFBF618FB4C7E0087BCEE /* Cocoa.framework in Frameworks */, + 0D3AFBF818FB4C870087BCEE /* IOKit.framework in Frameworks */, + 0D260DC118B69078002A0B55 /* Security.framework in Frameworks */, + 0D260DAE18B68E12002A0B55 /* XCTest.framework in Frameworks */, + 8BFD9B39112F4D16B3D0EFFB /* libPods-LogicTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0D35BD9B18FD71CE00921A21 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0DE4C8A118FEF28200466D04 /* Security.framework in Frameworks */, + 0D35BDBD18FDA23600921A21 /* IOKit.framework in Frameworks */, + 0D35BD9F18FD71CE00921A21 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0D385DB3180DE4A900418BC6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D6F12D819EC8822006B218E /* SecurityInterface.framework in Frameworks */, + 0D8C200C180F359A00CE2BF8 /* Security.framework in Frameworks */, + 0D385DB8180DE4A900418BC6 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0D9A7F3A1759330400035EB5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0DD0D487194F5187005F27EB /* IOKit.framework in Frameworks */, + 0D4A5007176A4602004F63BF /* Security.framework in Frameworks */, + 0D9A7F3F1759330500035EB5 /* Foundation.framework in Frameworks */, + E86AE075D7F24FB88FB627C5 /* libPods-santad.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0D0016A4192BCD3C005E7FCD /* KernelTests */ = { + isa = PBXGroup; + children = ( + 0D0016A5192BCD3C005E7FCD /* main.m */, + ); + path = KernelTests; + sourceTree = ""; + }; + 0D260DAF18B68E12002A0B55 /* LogicTests */ = { + isa = PBXGroup; + children = ( + 0D260DB018B68E12002A0B55 /* Resources */, + 0DD0D48E194F78F8005F27EB /* SNTBinaryInfoTest.m */, + 0DD0D48A194F6193005F27EB /* SNTCertificateTest.m */, + 0DD0D48C194F6D5B005F27EB /* SNTCodesignCheckerTest.m */, + 0D10BE8A1A0AB23300C0C944 /* SNTDERDecoderTest.m */, + 0DD0D490194F9947005F27EB /* SNTExecutionControllerTest.m */, + 0D3AFBE618FB32CB0087BCEE /* SNTXPCConnectionTest.m */, + ); + path = LogicTests; + sourceTree = ""; + }; + 0D260DB018B68E12002A0B55 /* Resources */ = { + isa = PBXGroup; + children = ( + 0D6FDC8618C6913D0044685C /* apple.pem */, + 0D6FDC8218C68D7E0044685C /* GIAG2.crt */, + 0D6F12D919EDE411006B218E /* tubitak.crt */, + 0D260DB118B68E12002A0B55 /* Tests-Info.plist */, + 0D260DB718B68E12002A0B55 /* Tests-Prefix.pch */, + 0D6FDC8418C68E500044685C /* GIAG2.pem */, + ); + path = Resources; + sourceTree = ""; + }; + 0D35BDA018FD71CE00921A21 /* santactl */ = { + isa = PBXGroup; + children = ( + 0D35BDA118FD71CE00921A21 /* main.m */, + 0D35BDAA18FD7CFD00921A21 /* SNTCommandController.h */, + 0D35BDAB18FD7CFD00921A21 /* SNTCommandController.m */, + 0DCD5FBC1909D4FD006B445C /* binaryinfo */, + 0DE4C8A318FF3AFA00466D04 /* flushcache */, + 0D827E6819DF4F3F006EC811 /* status */, + 0D35BDB618FD84FC00921A21 /* sync */, + 0D35BDA318FD71CE00921A21 /* Resources */, + ); + name = santactl; + path = Source/santactl; + sourceTree = ""; + }; + 0D35BDA318FD71CE00921A21 /* Resources */ = { + isa = PBXGroup; + children = ( + 0D35BDA418FD71CE00921A21 /* santactl-Prefix.pch */, + 0D28E5E41926B55600280F87 /* santactl-Info.plist */, + ); + path = Resources; + sourceTree = ""; + }; + 0D35BDB618FD84FC00921A21 /* sync */ = { + isa = PBXGroup; + children = ( + 0DCD6060191188B1006B445C /* SNTAuthenticatingURLSession.h */, + 0DCD6061191188B1006B445C /* SNTAuthenticatingURLSession.m */, + 0D35BDB418FD84F600921A21 /* SNTCommandSync.m */, + 0D41640319197AD7006A356A /* SNTCommandSyncEventUpload.h */, + 0D41640419197AD7006A356A /* SNTCommandSyncEventUpload.m */, + 0DC5D86F192160180078A5C0 /* SNTCommandSyncLogUpload.h */, + 0DC5D870192160180078A5C0 /* SNTCommandSyncLogUpload.m */, + 0D0A1EC4191AB9B000B8450F /* SNTCommandSyncPostflight.h */, + 0D0A1EC5191AB9B000B8450F /* SNTCommandSyncPostflight.m */, + 0DCD605A19117A90006B445C /* SNTCommandSyncPreflight.h */, + 0DCD605B19117A90006B445C /* SNTCommandSyncPreflight.m */, + 0D0A1EC1191998C900B8450F /* SNTCommandSyncRuleDownload.h */, + 0D0A1EC2191998C900B8450F /* SNTCommandSyncRuleDownload.m */, + 0D4163FF191974F1006A356A /* SNTCommandSyncStatus.h */, + 0D416400191974F1006A356A /* SNTCommandSyncStatus.m */, + 0D7FFD491A017D4B00F34435 /* SNTDERDecoder.h */, + 0D7FFD4A1A017D4B00F34435 /* SNTDERDecoder.m */, + ); + path = sync; + sourceTree = ""; + }; + 0D385DBD180DE4A900418BC6 /* SantaGUI */ = { + isa = PBXGroup; + children = ( + 0D385DC3180DE4A900418BC6 /* main.m */, + 0D1B476D19A53419008CADD3 /* SNTAboutWindowController.h */, + 0D1B476E19A53419008CADD3 /* SNTAboutWindowController.m */, + 0D385DEA180DE51600418BC6 /* SNTAppDelegate.h */, + 0D385DEB180DE51600418BC6 /* SNTAppDelegate.m */, + 0D668E7F18D1121700E29A8B /* SNTMessageWindow.h */, + 0D668E8018D1121700E29A8B /* SNTMessageWindow.m */, + 0D385DEC180DE51600418BC6 /* SNTMessageWindowController.h */, + 0D385DED180DE51600418BC6 /* SNTMessageWindowController.m */, + 0D385DEE180DE51600418BC6 /* SNTNotificationManager.h */, + 0D385DEF180DE51600418BC6 /* SNTNotificationManager.m */, + 0D3AF83018F87CE20087BCEE /* Resources */, + ); + name = SantaGUI; + path = Source/SantaGUI; + sourceTree = ""; + }; + 0D3AF83018F87CE20087BCEE /* Resources */ = { + isa = PBXGroup; + children = ( + 0D385DCF180DE4A900418BC6 /* Images.xcassets */, + 0D385DBF180DE4A900418BC6 /* Santa-Info.plist */, + 0D385DC5180DE4A900418BC6 /* Santa-Prefix.pch */, + 0D1B476F19A53419008CADD3 /* AboutWindow.xib */, + 0D385DE9180DE51600418BC6 /* MessageWindow.xib */, + ); + path = Resources; + sourceTree = ""; + }; + 0D3AF83118F87CEF0087BCEE /* Resources */ = { + isa = PBXGroup; + children = ( + 0DB8ACE41858D73000FEF9C7 /* santad-Info.plist */, + 0DB2B92318085753001C01D9 /* santad-Prefix.pch */, + ); + path = Resources; + sourceTree = ""; + }; + 0D789F9F1940F26D0036F7C4 /* Tests */ = { + isa = PBXGroup; + children = ( + 0D0016A4192BCD3C005E7FCD /* KernelTests */, + 0D260DAF18B68E12002A0B55 /* LogicTests */, + ); + path = Tests; + sourceTree = ""; + }; + 0D827E6819DF4F3F006EC811 /* status */ = { + isa = PBXGroup; + children = ( + 0D827E6619DF3C74006EC811 /* SNTCommandStatus.m */, + ); + name = status; + sourceTree = ""; + }; + 0D91BCA7174E8A6500131A7D = { + isa = PBXGroup; + children = ( + 0D91BCD5174E8AAB00131A7D /* common */, + 0D91BCB9174E8A7E00131A7D /* santa-driver */, + 0D9A7F401759330500035EB5 /* santad */, + 0D35BDA018FD71CE00921A21 /* santactl */, + 0D385DBD180DE4A900418BC6 /* SantaGUI */, + 0D789F9F1940F26D0036F7C4 /* Tests */, + 0D91BCB6174E8A7E00131A7D /* Frameworks */, + 0D91BCB5174E8A7E00131A7D /* Products */, + 277C48D61A6FDE3B7B817FE7 /* Pods */, + ); + sourceTree = ""; + }; + 0D91BCB5174E8A7E00131A7D /* Products */ = { + isa = PBXGroup; + children = ( + 0D91BCB4174E8A7E00131A7D /* santa-driver.kext */, + 0D9A7F3D1759330500035EB5 /* santad */, + 0D385DB6180DE4A900418BC6 /* Santa.app */, + 0D260DAC18B68E12002A0B55 /* LogicTests.xctest */, + 0D35BD9E18FD71CE00921A21 /* santactl */, + 0D0016A2192BCD3C005E7FCD /* KernelTests */, + ); + name = Products; + sourceTree = ""; + }; + 0D91BCB6174E8A7E00131A7D /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0DCD5F771909C659006B445C /* SecurityInterface.framework */, + 0D3AFBF718FB4C870087BCEE /* IOKit.framework */, + 0D260DC018B69078002A0B55 /* Security.framework */, + 0D8C200B180F359A00CE2BF8 /* Security.framework */, + 0D4A5006176A4602004F63BF /* Security.framework */, + 0D91BCB8174E8A7E00131A7D /* Kernel.framework */, + 0D9A7F3E1759330500035EB5 /* Foundation.framework */, + 0D385DB7180DE4A900418BC6 /* Cocoa.framework */, + 0D260DAD18B68E12002A0B55 /* XCTest.framework */, + 752301D17AA44BDE8B6D0541 /* libPods-LogicTests.a */, + 0A84545E322F475FA0B505D5 /* libPods-santad.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 0D91BCB9174E8A7E00131A7D /* santa-driver */ = { + isa = PBXGroup; + children = ( + 0D4644C4182AF81700098690 /* SantaDecisionManager.h */, + 0D4644C3182AF81700098690 /* SantaDecisionManager.cc */, + 0D9A7F321759144800035EB5 /* SantaDriver.h */, + 0D9A7F311759144800035EB5 /* SantaDriver.cc */, + 0D9A7F361759148E00035EB5 /* SantaDriverClient.h */, + 0D9A7F351759148E00035EB5 /* SantaDriverClient.cc */, + 0D7A7AF2174FCF4C00B77646 /* SantaMessage.h */, + 0D7A7AF1174FCF4C00B77646 /* SantaMessage.cc */, + 0DA36C1F199EA46600A129D6 /* Resources */, + ); + name = "santa-driver"; + path = "Source/santa-driver"; + sourceTree = ""; + }; + 0D91BCD5174E8AAB00131A7D /* common */ = { + isa = PBXGroup; + children = ( + 0DCD6040190ACCB8006B445C /* SNTBinaryInfo.h */, + 0DCD6041190ACCB8006B445C /* SNTBinaryInfo.m */, + 0D1AF475187C7A2C00D3298D /* SNTCertificate.h */, + 0D1AF476187C7A2C00D3298D /* SNTCertificate.m */, + 0D59C0E217710E6000748EBF /* SNTCodesignChecker.h */, + 0D59C0E317710E6000748EBF /* SNTCodesignChecker.m */, + 0D91BCE4174E8B5E00131A7D /* SNTCommonEnums.h */, + 0D42D2B619D2042900955F08 /* SNTConfigurator.h */, + 0D42D2B719D2042900955F08 /* SNTConfigurator.m */, + 0D10BE881A0AAC2100C0C944 /* SNTDropRootPrivs.h */, + 0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */, + 0D28E5E31926AFE400280F87 /* SNTKernelCommon.h */, + 0D28E5E119269B3600280F87 /* SNTLogging.h */, + 0DA73C9E1934F8100056D7C4 /* SNTLogging.m */, + 0DBE65EF18BEA3CC00AC994C /* SNTNotificationMessage.h */, + 0DBE65F018BEA3CC00AC994C /* SNTNotificationMessage.m */, + 0DE50F6619127169007B2B0C /* SNTRule.h */, + 0DE50F671912716A007B2B0C /* SNTRule.m */, + 0DCD604919105433006B445C /* SNTStoredEvent.h */, + 0DCD604A19105433006B445C /* SNTStoredEvent.m */, + 0D42D2B319D1D98A00955F08 /* SNTSystemInfo.h */, + 0D42D2B419D1D98A00955F08 /* SNTSystemInfo.m */, + 0D6FDC9418C93A020044685C /* SNTXPCConnection.h */, + 0D6FDC9518C93A020044685C /* SNTXPCConnection.m */, + 0DCD605319115D17006B445C /* SNTXPCControlInterface.h */, + 0DCD605419115D17006B445C /* SNTXPCControlInterface.m */, + 0DC8C9E3180CC3BC00FCFB29 /* SNTXPCNotifierInterface.h */, + 0DCD604E19115A06006B445C /* SNTXPCNotifierInterface.m */, + ); + name = common; + path = Source/common; + sourceTree = ""; + }; + 0D9A7F401759330500035EB5 /* santad */ = { + isa = PBXGroup; + children = ( + 0DA73CA519363C9F0056D7C4 /* DataLayer */, + 0D3AF83118F87CEF0087BCEE /* Resources */, + 0DB8ACBF185662DC00FEF9C7 /* SNTApplication.h */, + 0DB8ACC0185662DC00FEF9C7 /* SNTApplication.m */, + 0D8E18CB19107B56000F89B8 /* SNTDaemonControlController.h */, + 0D8E18CC19107B56000F89B8 /* SNTDaemonControlController.m */, + 0D63DD5A1906FCB400D346C4 /* SNTDatabaseController.h */, + 0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */, + 0D7D01851774F93A005DBAB4 /* SNTDriverManager.h */, + 0D7D01861774F93A005DBAB4 /* SNTDriverManager.m */, + 0DE6788B1784A8C2007A9E52 /* SNTExecutionController.h */, + 0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */, + 0D9A7F411759330500035EB5 /* main.m */, + ); + name = santad; + path = Source/santad; + sourceTree = ""; + }; + 0DA36C1F199EA46600A129D6 /* Resources */ = { + isa = PBXGroup; + children = ( + 0D91BCBB174E8A7E00131A7D /* santa-driver-Info.plist */, + ); + path = Resources; + sourceTree = ""; + }; + 0DA73CA519363C9F0056D7C4 /* DataLayer */ = { + isa = PBXGroup; + children = ( + 0D37C10D18F6029A0069BC61 /* SNTDatabaseTable.h */, + 0D37C10E18F6029A0069BC61 /* SNTDatabaseTable.m */, + 0D377C2817A071B7008453DB /* SNTEventTable.h */, + 0D377C2917A071B7008453DB /* SNTEventTable.m */, + 0D377C2417A06DDE008453DB /* SNTRuleTable.h */, + 0DC5D86C191AED220078A5C0 /* SNTRuleTable.m */, + ); + name = DataLayer; + sourceTree = ""; + }; + 0DCD5FBC1909D4FD006B445C /* binaryinfo */ = { + isa = PBXGroup; + children = ( + 0DCD5FBE1909D64A006B445C /* SNTCommandBinaryInfo.m */, + ); + path = binaryinfo; + sourceTree = ""; + }; + 0DE4C8A318FF3AFA00466D04 /* flushcache */ = { + isa = PBXGroup; + children = ( + 0DE4C8A518FF3B1700466D04 /* SNTCommandFlushCache.m */, + ); + path = flushcache; + sourceTree = ""; + }; + 277C48D61A6FDE3B7B817FE7 /* Pods */ = { + isa = PBXGroup; + children = ( + 13A4FE400F3857C0F5831498 /* Pods-LogicTests.debug.xcconfig */, + BE74E23CF5A553E5F02462B9 /* Pods-LogicTests.release.xcconfig */, + 627BB4EC9917DC20E89D718C /* Pods-santad.debug.xcconfig */, + 8003CA1D3E46447BCEA56440 /* Pods-santad.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 0D91BCB0174E8A7E00131A7D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D7A7AF4174FCF4C00B77646 /* SantaMessage.h in Headers */, + 0D4644C6182AF81700098690 /* SantaDecisionManager.h in Headers */, + 0D9A7F341759144800035EB5 /* SantaDriver.h in Headers */, + 0D9A7F381759148E00035EB5 /* SantaDriverClient.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 0D0016A1192BCD3C005E7FCD /* KernelTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0D0016AB192BCD3D005E7FCD /* Build configuration list for PBXNativeTarget "KernelTests" */; + buildPhases = ( + 0D00169E192BCD3C005E7FCD /* Sources */, + 0D00169F192BCD3C005E7FCD /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = KernelTests; + productName = KernelTests; + productReference = 0D0016A2192BCD3C005E7FCD /* KernelTests */; + productType = "com.apple.product-type.tool"; + }; + 0D260DAB18B68E12002A0B55 /* LogicTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0D260DBC18B68E12002A0B55 /* Build configuration list for PBXNativeTarget "LogicTests" */; + buildPhases = ( + AE05898CB3CE4507B2F43B91 /* Check Pods Manifest.lock */, + 0D673DAD18FC9017009C5B06 /* Delete existing coverage files */, + 0D260DA818B68E12002A0B55 /* Sources */, + 0D260DA918B68E12002A0B55 /* Frameworks */, + 0D260DAA18B68E12002A0B55 /* Resources */, + 85CE5DF0D54C438A8933A631 /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = LogicTests; + productName = SantaTests; + productReference = 0D260DAC18B68E12002A0B55 /* LogicTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 0D35BD9D18FD71CE00921A21 /* santactl */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0D35BDA918FD71CE00921A21 /* Build configuration list for PBXNativeTarget "santactl" */; + buildPhases = ( + 0D35BD9A18FD71CE00921A21 /* Sources */, + 0D35BD9B18FD71CE00921A21 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = santactl; + productName = santactl; + productReference = 0D35BD9E18FD71CE00921A21 /* santactl */; + productType = "com.apple.product-type.tool"; + }; + 0D385DB5180DE4A900418BC6 /* Santa */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0D385DE3180DE4A900418BC6 /* Build configuration list for PBXNativeTarget "Santa" */; + buildPhases = ( + 0D385DB2180DE4A900418BC6 /* Sources */, + 0D385DB3180DE4A900418BC6 /* Frameworks */, + 0D385DB4180DE4A900418BC6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Santa; + productName = SantaNotifier; + productReference = 0D385DB6180DE4A900418BC6 /* Santa.app */; + productType = "com.apple.product-type.application"; + }; + 0D91BCB3174E8A7E00131A7D /* santa-driver */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0D91BCC3174E8A7E00131A7D /* Build configuration list for PBXNativeTarget "santa-driver" */; + buildPhases = ( + 0D91BCAE174E8A7E00131A7D /* Sources */, + 0D91BCB0174E8A7E00131A7D /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "santa-driver"; + productName = "santa-driver"; + productReference = 0D91BCB4174E8A7E00131A7D /* santa-driver.kext */; + productType = "com.apple.product-type.kernel-extension"; + }; + 0D9A7F3C1759330400035EB5 /* santad */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0D9A7F471759330500035EB5 /* Build configuration list for PBXNativeTarget "santad" */; + buildPhases = ( + 34C9C9E8C5454BBE980DF8A9 /* Check Pods Manifest.lock */, + 0D9A7F391759330400035EB5 /* Sources */, + 0D9A7F3A1759330400035EB5 /* Frameworks */, + 0D9A7F3B1759330400035EB5 /* CopyFiles */, + 3CDBFA3554E7465D93EAA5C8 /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = santad; + productName = santad; + productReference = 0D9A7F3D1759330500035EB5 /* santad */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0D91BCA8174E8A6500131A7D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0510; + TargetAttributes = { + 0D0016A1192BCD3C005E7FCD = { + DevelopmentTeam = 48U5E5R4XN; + }; + 0D260DAB18B68E12002A0B55 = { + DevelopmentTeam = 48U5E5R4XN; + TestTargetID = 0D385DB5180DE4A900418BC6; + }; + 0D35BD9D18FD71CE00921A21 = { + DevelopmentTeam = 48U5E5R4XN; + }; + 0D385DB5180DE4A900418BC6 = { + DevelopmentTeam = 48U5E5R4XN; + }; + 0D91BCB3174E8A7E00131A7D = { + DevelopmentTeam = 48U5E5R4XN; + }; + 0D9A7F3C1759330400035EB5 = { + DevelopmentTeam = 48U5E5R4XN; + }; + }; + }; + buildConfigurationList = 0D91BCAB174E8A6500131A7D /* Build configuration list for PBXProject "Santa" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 0D91BCA7174E8A6500131A7D; + productRefGroup = 0D91BCB5174E8A7E00131A7D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 0D91BCDC174E8AE600131A7D /* All */, + 0D91BCB3174E8A7E00131A7D /* santa-driver */, + 0D9A7F3C1759330400035EB5 /* santad */, + 0D35BD9D18FD71CE00921A21 /* santactl */, + 0D385DB5180DE4A900418BC6 /* Santa */, + 0D260DAB18B68E12002A0B55 /* LogicTests */, + 0D0016A1192BCD3C005E7FCD /* KernelTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 0D260DAA18B68E12002A0B55 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D6FDC8518C68E500044685C /* GIAG2.pem in Resources */, + 0D6FDC8318C68D7E0044685C /* GIAG2.crt in Resources */, + 0D6F12DA19EDE51E006B218E /* tubitak.crt in Resources */, + 0D6FDC8718C6913D0044685C /* apple.pem in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0D385DB4180DE4A900418BC6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D385DF0180DE51600418BC6 /* MessageWindow.xib in Resources */, + 0D385DD0180DE4A900418BC6 /* Images.xcassets in Resources */, + 0D1B477119A53419008CADD3 /* AboutWindow.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0D673DAD18FC9017009C5B06 /* Delete existing coverage files */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Delete existing coverage files"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Delete existing gcda files to prevent the build log being filled with hundreds of lines\n# of \"profiling:invalid arc tag\".\n# TODO(rah): Remove when Xcode fixes this.\nfind . -name \"*.gcda\" -print0 | xargs -0 rm"; + showEnvVarsInLog = 0; + }; + 34C9C9E8C5454BBE980DF8A9 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 3CDBFA3554E7465D93EAA5C8 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-santad/Pods-santad-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 85CE5DF0D54C438A8933A631 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-LogicTests/Pods-LogicTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + AE05898CB3CE4507B2F43B91 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 0D00169E192BCD3C005E7FCD /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D0016A6192BCD3C005E7FCD /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0D260DA818B68E12002A0B55 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D63DD5E1906FCB400D346C4 /* SNTDatabaseController.m in Sources */, + 0D6FDC8F18C7F0200044685C /* SNTNotificationMessage.m in Sources */, + 0D3AFBF018FB4C6C0087BCEE /* SNTDriverManager.m in Sources */, + 0DCD6044190ACCB8006B445C /* SNTBinaryInfo.m in Sources */, + 0D6FDC9718C93A020044685C /* SNTXPCConnection.m in Sources */, + 0D3AFBEB18FB48E70087BCEE /* SNTDatabaseTable.m in Sources */, + 0DD0D491194F9947005F27EB /* SNTExecutionControllerTest.m in Sources */, + 0D3AFBEF18FB4C6C0087BCEE /* SNTExecutionController.m in Sources */, + 0D3AFBEC18FB48E70087BCEE /* SNTEventTable.m in Sources */, + 0DCD604D19105433006B445C /* SNTStoredEvent.m in Sources */, + 0DCD605819115E57006B445C /* SNTXPCControlInterface.m in Sources */, + 0D10BE891A0AAF6700C0C944 /* SNTDropRootPrivs.m in Sources */, + 0D10BE8B1A0AB23300C0C944 /* SNTDERDecoderTest.m in Sources */, + 0DD0D48B194F6193005F27EB /* SNTCertificateTest.m in Sources */, + 0D28D53819D9F5910015C5EB /* SNTConfigurator.m in Sources */, + 0D3AFBE718FB32CB0087BCEE /* SNTXPCConnectionTest.m in Sources */, + 0DCD605719115E54006B445C /* SNTDaemonControlController.m in Sources */, + 0D3AFBEE18FB4C6C0087BCEE /* SNTApplication.m in Sources */, + 0DD0D48F194F78F8005F27EB /* SNTBinaryInfoTest.m in Sources */, + 0DC5D86E191AED220078A5C0 /* SNTRuleTable.m in Sources */, + 0D31DF4718D254B3002B300D /* SNTCodesignChecker.m in Sources */, + 0DD0D492194F9BEF005F27EB /* SNTLogging.m in Sources */, + 0DD0D48D194F6D5B005F27EB /* SNTCodesignCheckerTest.m in Sources */, + 0DCD605919115E5A006B445C /* SNTXPCNotifierInterface.m in Sources */, + 0DE50F691912B0CD007B2B0C /* SNTRule.m in Sources */, + 0D6FDC8C18C69AF90044685C /* SNTCertificate.m in Sources */, + 0D10BE8C1A0AB3FD00C0C944 /* SNTDERDecoder.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0D35BD9A18FD71CE00921A21 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0DA73CA21934F88D0056D7C4 /* SNTLogging.m in Sources */, + 0D35BDC218FDA5D100921A21 /* SNTCodesignChecker.m in Sources */, + 0D35BDB518FD84F600921A21 /* SNTCommandSync.m in Sources */, + 0DCD5FBF1909D64A006B445C /* SNTCommandBinaryInfo.m in Sources */, + 0DCD6062191188B1006B445C /* SNTAuthenticatingURLSession.m in Sources */, + 0DCD605619115D17006B445C /* SNTXPCControlInterface.m in Sources */, + 0DE50F6C19130358007B2B0C /* SNTStoredEvent.m in Sources */, + 0D35BDC418FDA5D100921A21 /* SNTXPCConnection.m in Sources */, + 0DCD605C19117A90006B445C /* SNTCommandSyncPreflight.m in Sources */, + 0D41640519197AD7006A356A /* SNTCommandSyncEventUpload.m in Sources */, + 0D42D2B919D2042900955F08 /* SNTConfigurator.m in Sources */, + 0D10BE871A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */, + 0DE4C8A618FF3B1700466D04 /* SNTCommandFlushCache.m in Sources */, + 0D416401191974F1006A356A /* SNTCommandSyncStatus.m in Sources */, + 0DC5D871192160180078A5C0 /* SNTCommandSyncLogUpload.m in Sources */, + 0D35BDA218FD71CE00921A21 /* main.m in Sources */, + 0DCD6043190ACCB8006B445C /* SNTBinaryInfo.m in Sources */, + 0DE50F6E191304E0007B2B0C /* SNTRule.m in Sources */, + 0D0A1EC3191998C900B8450F /* SNTCommandSyncRuleDownload.m in Sources */, + 0D35BDC018FDA5C800921A21 /* SNTCertificate.m in Sources */, + 0D42D2B519D1D98A00955F08 /* SNTSystemInfo.m in Sources */, + 0D827E6719DF3C74006EC811 /* SNTCommandStatus.m in Sources */, + 0D0A1EC6191AB9B000B8450F /* SNTCommandSyncPostflight.m in Sources */, + 0D7FFD4B1A017D4B00F34435 /* SNTDERDecoder.m in Sources */, + 0D35BDAC18FD7CFD00921A21 /* SNTCommandController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0D385DB2180DE4A900418BC6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D54E0B11976F8D3000BB59F /* SNTBinaryInfo.m in Sources */, + 0DCA552718C95928002A7DAE /* SNTXPCConnection.m in Sources */, + 0D385DF1180DE51600418BC6 /* SNTAppDelegate.m in Sources */, + 0DCD605119115A06006B445C /* SNTXPCNotifierInterface.m in Sources */, + 0D827E6519DF392E006EC811 /* SNTConfigurator.m in Sources */, + 0D385DF2180DE51600418BC6 /* SNTMessageWindowController.m in Sources */, + 0D385DF3180DE51600418BC6 /* SNTNotificationManager.m in Sources */, + 0D1AF478187C7A2C00D3298D /* SNTCertificate.m in Sources */, + 0DD65D98184D2F0A00822DA7 /* SNTCodesignChecker.m in Sources */, + 0D385DC4180DE4A900418BC6 /* main.m in Sources */, + 0D1B477019A53419008CADD3 /* SNTAboutWindowController.m in Sources */, + 0D668E8118D1121700E29A8B /* SNTMessageWindow.m in Sources */, + 0DA73CA11934F8100056D7C4 /* SNTLogging.m in Sources */, + 0DE50F6D191303E3007B2B0C /* SNTNotificationMessage.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0D91BCAE174E8A7E00131A7D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D7A7AF3174FCF4C00B77646 /* SantaMessage.cc in Sources */, + 0D9A7F331759144800035EB5 /* SantaDriver.cc in Sources */, + 0D9A7F371759148E00035EB5 /* SantaDriverClient.cc in Sources */, + 0D4644C5182AF81700098690 /* SantaDecisionManager.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 0D9A7F391759330400035EB5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0DE6788D1784A8C2007A9E52 /* SNTExecutionController.m in Sources */, + 0D10BE861A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */, + 0D63DD5C1906FCB400D346C4 /* SNTDatabaseController.m in Sources */, + 0DCD604B19105433006B445C /* SNTStoredEvent.m in Sources */, + 0DB8ACC1185662DC00FEF9C7 /* SNTApplication.m in Sources */, + 0D9A7F421759330500035EB5 /* main.m in Sources */, + 0D1AF477187C7A2C00D3298D /* SNTCertificate.m in Sources */, + 0DA73C9F1934F8100056D7C4 /* SNTLogging.m in Sources */, + 0DCD6042190ACCB8006B445C /* SNTBinaryInfo.m in Sources */, + 0DC5D86D191AED220078A5C0 /* SNTRuleTable.m in Sources */, + 0D7D01871774F93A005DBAB4 /* SNTDriverManager.m in Sources */, + 0D8E18CD19107B56000F89B8 /* SNTDaemonControlController.m in Sources */, + 0D6FDC9618C93A020044685C /* SNTXPCConnection.m in Sources */, + 0D377C2A17A071B7008453DB /* SNTEventTable.m in Sources */, + 0DE50F681912716A007B2B0C /* SNTRule.m in Sources */, + 0D37C10F18F6029A0069BC61 /* SNTDatabaseTable.m in Sources */, + 0DBE65F118BEA3CC00AC994C /* SNTNotificationMessage.m in Sources */, + 0D59C0E417710E6000748EBF /* SNTCodesignChecker.m in Sources */, + 0D42D2B819D2042900955F08 /* SNTConfigurator.m in Sources */, + 0DCD605519115D17006B445C /* SNTXPCControlInterface.m in Sources */, + 0DCD604F19115A06006B445C /* SNTXPCNotifierInterface.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 0D35BDB818FD859300921A21 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0D35BD9D18FD71CE00921A21 /* santactl */; + targetProxy = 0D35BDB718FD859300921A21 /* PBXContainerItemProxy */; + }; + 0D385DF5180DE73A00418BC6 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0D385DB5180DE4A900418BC6 /* Santa */; + targetProxy = 0D385DF4180DE73A00418BC6 /* PBXContainerItemProxy */; + }; + 0D91BCE1174E8AED00131A7D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0D91BCB3174E8A7E00131A7D /* santa-driver */; + targetProxy = 0D91BCE0174E8AED00131A7D /* PBXContainerItemProxy */; + }; + 0D91BD5F1A01571D00DF4A0E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0D260DAB18B68E12002A0B55 /* LogicTests */; + targetProxy = 0D91BD5E1A01571D00DF4A0E /* PBXContainerItemProxy */; + }; + 0D91BD611A01571D00DF4A0E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0D0016A1192BCD3C005E7FCD /* KernelTests */; + targetProxy = 0D91BD601A01571D00DF4A0E /* PBXContainerItemProxy */; + }; + 0D9A7F5A1759393600035EB5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0D9A7F3C1759330400035EB5 /* santad */; + targetProxy = 0D9A7F591759393600035EB5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 0D0016AC192BCD3D005E7FCD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + }; + name = Debug; + }; + 0D0016AD192BCD3D005E7FCD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + }; + name = Release; + }; + 0D260DBA18B68E12002A0B55 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 13A4FE400F3857C0F5831498 /* Pods-LogicTests.debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Santa.app/Contents/MacOS/Santa"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + "$(LOCAL_LIBRARY_DIR)/Frameworks", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_GENERATE_TEST_COVERAGE_FILES = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Tests/LogicTests/Resources/Tests-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + ); + INFOPLIST_FILE = "Tests/LogicTests/Resources/Tests-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = xctest; + }; + name = Debug; + }; + 0D260DBB18B68E12002A0B55 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BE74E23CF5A553E5F02462B9 /* Pods-LogicTests.release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Santa.app/Contents/MacOS/Santa"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + "$(LOCAL_LIBRARY_DIR)/Frameworks", + ); + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_GENERATE_TEST_COVERAGE_FILES = YES; + GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Tests/LogicTests/Resources/Tests-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + ); + INFOPLIST_FILE = "Tests/LogicTests/Resources/Tests-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = xctest; + }; + name = Release; + }; + 0D35BDA718FD71CE00921A21 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CREATE_INFOPLIST_SECTION_IN_BINARY = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Source/santactl/Resources/santactl-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "Source/santactl/Resources/santactl-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + }; + name = Debug; + }; + 0D35BDA818FD71CE00921A21 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + CREATE_INFOPLIST_SECTION_IN_BINARY = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Source/santactl/Resources/santactl-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "Source/santactl/Resources/santactl-Info.plist"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + }; + name = Release; + }; + 0D385DE4180DE4A900418BC6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Source/SantaGUI/Resources/Santa-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "Source/SantaGUI/Resources/Santa-Info.plist"; + PRODUCT_NAME = "${TARGET_NAME}"; + PROVISIONING_PROFILE = ""; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 0D385DE5180DE4A900418BC6 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Source/SantaGUI/Resources/Santa-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "Source/SantaGUI/Resources/Santa-Info.plist"; + PRODUCT_NAME = "${TARGET_NAME}"; + PROVISIONING_PROFILE = ""; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + 0D91BCAC174E8A6500131A7D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + PROVISIONING_PROFILE = ""; + RUN_CLANG_STATIC_ANALYZER = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 0D91BCAD174E8A6500131A7D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + MACOSX_DEPLOYMENT_TARGET = 10.8; + PROVISIONING_PROFILE = ""; + RUN_CLANG_STATIC_ANALYZER = YES; + SDKROOT = macosx; + }; + name = Release; + }; + 0D91BCC4174E8A7E00131A7D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1"; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = NO; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = "Source/santa-driver/Resources/santa-driver-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MODULE_NAME = "com.google.santa-driver"; + MODULE_VERSION = 0.7; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + WARNING_CFLAGS = "-Wno-deprecated-register"; + WRAPPER_EXTENSION = kext; + }; + name = Debug; + }; + 0D91BCC5174E8A7E00131A7D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREPROCESSOR_DEFINITIONS = ""; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ""; + INFOPLIST_FILE = "Source/santa-driver/Resources/santa-driver-Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MODULE_NAME = "com.google.santa-driver"; + MODULE_VERSION = 0.7; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + WARNING_CFLAGS = "-Wno-deprecated-register"; + WRAPPER_EXTENSION = kext; + }; + name = Release; + }; + 0D91BCDE174E8AE600131A7D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 0D91BCDF174E8AE600131A7D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 0D9A7F481759330500035EB5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 627BB4EC9917DC20E89D718C /* Pods-santad.debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CREATE_INFOPLIST_SECTION_IN_BINARY = YES; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Source/santad/Resources/santad-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "Source/santad/Resources/santad-Info.plist"; + INSTALL_PATH = /usr/sbin; + LIBRARY_SEARCH_PATHS = "$(inherited)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 0D9A7F491759330500035EB5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8003CA1D3E46447BCEA56440 /* Pods-santad.release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + CREATE_INFOPLIST_SECTION_IN_BINARY = YES; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "Source/santad/Resources/santad-Prefix.pch"; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = "Source/santad/Resources/santad-Info.plist"; + INSTALL_PATH = /usr/sbin; + LIBRARY_SEARCH_PATHS = "$(inherited)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0D0016AB192BCD3D005E7FCD /* Build configuration list for PBXNativeTarget "KernelTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D0016AC192BCD3D005E7FCD /* Debug */, + 0D0016AD192BCD3D005E7FCD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0D260DBC18B68E12002A0B55 /* Build configuration list for PBXNativeTarget "LogicTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D260DBA18B68E12002A0B55 /* Debug */, + 0D260DBB18B68E12002A0B55 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0D35BDA918FD71CE00921A21 /* Build configuration list for PBXNativeTarget "santactl" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D35BDA718FD71CE00921A21 /* Debug */, + 0D35BDA818FD71CE00921A21 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0D385DE3180DE4A900418BC6 /* Build configuration list for PBXNativeTarget "Santa" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D385DE4180DE4A900418BC6 /* Debug */, + 0D385DE5180DE4A900418BC6 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0D91BCAB174E8A6500131A7D /* Build configuration list for PBXProject "Santa" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D91BCAC174E8A6500131A7D /* Debug */, + 0D91BCAD174E8A6500131A7D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0D91BCC3174E8A7E00131A7D /* Build configuration list for PBXNativeTarget "santa-driver" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D91BCC4174E8A7E00131A7D /* Debug */, + 0D91BCC5174E8A7E00131A7D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0D91BCDD174E8AE600131A7D /* Build configuration list for PBXAggregateTarget "All" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D91BCDE174E8AE600131A7D /* Debug */, + 0D91BCDF174E8AE600131A7D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0D9A7F471759330500035EB5 /* Build configuration list for PBXNativeTarget "santad" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D9A7F481759330500035EB5 /* Debug */, + 0D9A7F491759330500035EB5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0D91BCA8174E8A6500131A7D /* Project object */; +} diff --git a/Santa.xcodeproj/xcshareddata/xcschemes/All.xcscheme b/Santa.xcodeproj/xcshareddata/xcschemes/All.xcscheme new file mode 100644 index 00000000..c384678f --- /dev/null +++ b/Santa.xcodeproj/xcshareddata/xcschemes/All.xcscheme @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Santa.xcodeproj/xcshareddata/xcschemes/KernelTests.xcscheme b/Santa.xcodeproj/xcshareddata/xcschemes/KernelTests.xcscheme new file mode 100644 index 00000000..6feff62c --- /dev/null +++ b/Santa.xcodeproj/xcshareddata/xcschemes/KernelTests.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Santa.xcodeproj/xcshareddata/xcschemes/Santa.xcscheme b/Santa.xcodeproj/xcshareddata/xcschemes/Santa.xcscheme new file mode 100644 index 00000000..f9d7e5ce --- /dev/null +++ b/Santa.xcodeproj/xcshareddata/xcschemes/Santa.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Santa.xcodeproj/xcshareddata/xcschemes/santa-driver.xcscheme b/Santa.xcodeproj/xcshareddata/xcschemes/santa-driver.xcscheme new file mode 100644 index 00000000..eb9a8067 --- /dev/null +++ b/Santa.xcodeproj/xcshareddata/xcschemes/santa-driver.xcscheme @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Santa.xcodeproj/xcshareddata/xcschemes/santactl.xcscheme b/Santa.xcodeproj/xcshareddata/xcschemes/santactl.xcscheme new file mode 100644 index 00000000..aa59001c --- /dev/null +++ b/Santa.xcodeproj/xcshareddata/xcschemes/santactl.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Santa.xcodeproj/xcshareddata/xcschemes/santad.xcscheme b/Santa.xcodeproj/xcshareddata/xcschemes/santad.xcscheme new file mode 100644 index 00000000..4c7b8a68 --- /dev/null +++ b/Santa.xcodeproj/xcshareddata/xcschemes/santad.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/SantaGUI/Resources/AboutWindow.xib b/Source/SantaGUI/Resources/AboutWindow.xib new file mode 100644 index 00000000..c7d2b819 --- /dev/null +++ b/Source/SantaGUI/Resources/AboutWindow.xib @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Santa is a binary whitelisting system for Mac OS X. + +There are no user-configurable settings. + + + + + + + + + + + + + + + + + + + + diff --git a/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/Contents.json b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..f35bf7bf --- /dev/null +++ b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png new file mode 100644 index 00000000..3a1046cc Binary files /dev/null and b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png differ diff --git a/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-16.png b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-16.png new file mode 100644 index 00000000..dbd8be5b Binary files /dev/null and b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-16.png differ diff --git a/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-32.png b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-32.png new file mode 100644 index 00000000..7d9e6b5f Binary files /dev/null and b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-32.png differ diff --git a/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-512.png b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-512.png new file mode 100644 index 00000000..c44da928 Binary files /dev/null and b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-512.png differ diff --git a/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-64.png b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-64.png new file mode 100644 index 00000000..e430c558 Binary files /dev/null and b/Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-64.png differ diff --git a/Source/SantaGUI/Resources/MessageWindow.xib b/Source/SantaGUI/Resources/MessageWindow.xib new file mode 100644 index 00000000..ca5292bc --- /dev/null +++ b/Source/SantaGUI/Resources/MessageWindow.xib @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The following application has been blocked from executing because its trustworthiness cannot be determined. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Not code-signed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/SantaGUI/Resources/Santa-Info.plist b/Source/SantaGUI/Resources/Santa-Info.plist new file mode 100644 index 00000000..44df15b5 --- /dev/null +++ b/Source/SantaGUI/Resources/Santa-Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.google.${PRODUCT_NAME:rfc1034identifier}GUI + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.7 + CFBundleVersion + 0.7 + LSMinimumSystemVersion + ${MACOSX_DEPLOYMENT_TARGET} + LSUIElement + + NSHumanReadableCopyright + Google, Inc. + NSPrincipalClass + NSApplication + + diff --git a/Source/SantaGUI/Resources/Santa-Prefix.pch b/Source/SantaGUI/Resources/Santa-Prefix.pch new file mode 100644 index 00000000..4a87cf73 --- /dev/null +++ b/Source/SantaGUI/Resources/Santa-Prefix.pch @@ -0,0 +1,3 @@ +#ifdef __OBJC__ + #import +#endif diff --git a/Source/SantaGUI/SNTAboutWindowController.h b/Source/SantaGUI/SNTAboutWindowController.h new file mode 100644 index 00000000..de56a6d6 --- /dev/null +++ b/Source/SantaGUI/SNTAboutWindowController.h @@ -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 diff --git a/Source/SantaGUI/SNTAboutWindowController.m b/Source/SantaGUI/SNTAboutWindowController.m new file mode 100644 index 00000000..00f3a787 --- /dev/null +++ b/Source/SantaGUI/SNTAboutWindowController.m @@ -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 diff --git a/Source/SantaGUI/SNTAppDelegate.h b/Source/SantaGUI/SNTAppDelegate.h new file mode 100644 index 00000000..73953f3f --- /dev/null +++ b/Source/SantaGUI/SNTAppDelegate.h @@ -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 +@end diff --git a/Source/SantaGUI/SNTAppDelegate.m b/Source/SantaGUI/SNTAppDelegate.m new file mode 100644 index 00000000..6cf45f67 --- /dev/null +++ b/Source/SantaGUI/SNTAppDelegate.m @@ -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 diff --git a/Source/SantaGUI/SNTMessageWindow.h b/Source/SantaGUI/SNTMessageWindow.h new file mode 100644 index 00000000..94e115e6 --- /dev/null +++ b/Source/SantaGUI/SNTMessageWindow.h @@ -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 diff --git a/Source/SantaGUI/SNTMessageWindow.m b/Source/SantaGUI/SNTMessageWindow.m new file mode 100644 index 00000000..c1ef694c --- /dev/null +++ b/Source/SantaGUI/SNTMessageWindow.m @@ -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 diff --git a/Source/SantaGUI/SNTMessageWindowController.h b/Source/SantaGUI/SNTMessageWindowController.h new file mode 100644 index 00000000..ec9b66b6 --- /dev/null +++ b/Source/SantaGUI/SNTMessageWindowController.h @@ -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 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 diff --git a/Source/SantaGUI/SNTMessageWindowController.m b/Source/SantaGUI/SNTMessageWindowController.m new file mode 100644 index 00000000..f4448186 --- /dev/null +++ b/Source/SantaGUI/SNTMessageWindowController.m @@ -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 + +#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 = @""; + NSString *htmlFooter = @""; + 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 diff --git a/Source/SantaGUI/SNTNotificationManager.h b/Source/SantaGUI/SNTNotificationManager.h new file mode 100644 index 00000000..3572532a --- /dev/null +++ b/Source/SantaGUI/SNTNotificationManager.h @@ -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 + + +@end diff --git a/Source/SantaGUI/SNTNotificationManager.m b/Source/SantaGUI/SNTNotificationManager.m new file mode 100644 index 00000000..e173de62 --- /dev/null +++ b/Source/SantaGUI/SNTNotificationManager.m @@ -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 diff --git a/Source/SantaGUI/main.m b/Source/SantaGUI/main.m new file mode 100644 index 00000000..ef4d0507 --- /dev/null +++ b/Source/SantaGUI/main.m @@ -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]; + } +} diff --git a/Source/common/SNTBinaryInfo.h b/Source/common/SNTBinaryInfo.h new file mode 100644 index 00000000..17e68504 --- /dev/null +++ b/Source/common/SNTBinaryInfo.h @@ -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 diff --git a/Source/common/SNTBinaryInfo.m b/Source/common/SNTBinaryInfo.m new file mode 100644 index 00000000..022703e5 --- /dev/null +++ b/Source/common/SNTBinaryInfo.m @@ -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 + +#include +#include + +@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 diff --git a/Source/common/SNTCertificate.h b/Source/common/SNTCertificate.h new file mode 100644 index 00000000..063991f9 --- /dev/null +++ b/Source/common/SNTCertificate.h @@ -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 + +/// 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 diff --git a/Source/common/SNTCertificate.m b/Source/common/SNTCertificate.m new file mode 100644 index 00000000..c697f929 --- /dev/null +++ b/Source/common/SNTCertificate.m @@ -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 +#import + +@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 diff --git a/Source/common/SNTCodesignChecker.h b/Source/common/SNTCodesignChecker.h new file mode 100644 index 00000000..5344f09c --- /dev/null +++ b/Source/common/SNTCodesignChecker.h @@ -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 diff --git a/Source/common/SNTCodesignChecker.m b/Source/common/SNTCodesignChecker.m new file mode 100644 index 00000000..ed28b787 --- /dev/null +++ b/Source/common/SNTCodesignChecker.m @@ -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 + +#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 diff --git a/Source/common/SNTCodesignChecker.m.old b/Source/common/SNTCodesignChecker.m.old new file mode 100644 index 00000000..704f8233 --- /dev/null +++ b/Source/common/SNTCodesignChecker.m.old @@ -0,0 +1,213 @@ +#import "SNTCodesignChecker.h" + +#import + +#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 diff --git a/Source/common/SNTCommonEnums.h b/Source/common/SNTCommonEnums.h new file mode 100644 index 00000000..9fb21084 --- /dev/null +++ b/Source/common/SNTCommonEnums.h @@ -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 diff --git a/Source/common/SNTConfigurator.h b/Source/common/SNTConfigurator.h new file mode 100644 index 00000000..4991aeeb --- /dev/null +++ b/Source/common/SNTConfigurator.h @@ -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 diff --git a/Source/common/SNTConfigurator.m b/Source/common/SNTConfigurator.m new file mode 100644 index 00000000..d5d4d424 --- /dev/null +++ b/Source/common/SNTConfigurator.m @@ -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 diff --git a/Source/common/SNTDropRootPrivs.h b/Source/common/SNTDropRootPrivs.h new file mode 100644 index 00000000..914a01c1 --- /dev/null +++ b/Source/common/SNTDropRootPrivs.h @@ -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(); \ No newline at end of file diff --git a/Source/common/SNTDropRootPrivs.m b/Source/common/SNTDropRootPrivs.m new file mode 100644 index 00000000..e727adda --- /dev/null +++ b/Source/common/SNTDropRootPrivs.m @@ -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; +} \ No newline at end of file diff --git a/Source/common/SNTKernelCommon.h b/Source/common/SNTKernelCommon.h new file mode 100644 index 00000000..d32d5160 --- /dev/null +++ b/Source/common/SNTKernelCommon.h @@ -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 diff --git a/Source/common/SNTLogging.h b/Source/common/SNTLogging.h new file mode 100644 index 00000000..6d02c99a --- /dev/null +++ b/Source/common/SNTLogging.h @@ -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 diff --git a/Source/common/SNTLogging.m b/Source/common/SNTLogging.m new file mode 100644 index 00000000..4fe9613c --- /dev/null +++ b/Source/common/SNTLogging.m @@ -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]); + } +} \ No newline at end of file diff --git a/Source/common/SNTNotificationMessage.h b/Source/common/SNTNotificationMessage.h new file mode 100644 index 00000000..643e3de5 --- /dev/null +++ b/Source/common/SNTNotificationMessage.h @@ -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 + +/// 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 diff --git a/Source/common/SNTNotificationMessage.m b/Source/common/SNTNotificationMessage.m new file mode 100644 index 00000000..3f19d2a6 --- /dev/null +++ b/Source/common/SNTNotificationMessage.m @@ -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 diff --git a/Source/common/SNTRule.h b/Source/common/SNTRule.h new file mode 100644 index 00000000..a4f49fa3 --- /dev/null +++ b/Source/common/SNTRule.h @@ -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 + +/// 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 \ No newline at end of file diff --git a/Source/common/SNTRule.m b/Source/common/SNTRule.m new file mode 100644 index 00000000..b0fcb48a --- /dev/null +++ b/Source/common/SNTRule.m @@ -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 \ No newline at end of file diff --git a/Source/common/SNTStoredEvent.h b/Source/common/SNTStoredEvent.h new file mode 100644 index 00000000..4ad83652 --- /dev/null +++ b/Source/common/SNTStoredEvent.h @@ -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 + +@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 diff --git a/Source/common/SNTStoredEvent.m b/Source/common/SNTStoredEvent.m new file mode 100644 index 00000000..3c340755 --- /dev/null +++ b/Source/common/SNTStoredEvent.m @@ -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 diff --git a/Source/common/SNTSystemInfo.h b/Source/common/SNTSystemInfo.h new file mode 100644 index 00000000..3413d132 --- /dev/null +++ b/Source/common/SNTSystemInfo.h @@ -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 diff --git a/Source/common/SNTSystemInfo.m b/Source/common/SNTSystemInfo.m new file mode 100644 index 00000000..8b60022c --- /dev/null +++ b/Source/common/SNTSystemInfo.m @@ -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 diff --git a/Source/common/SNTXPCConnection.h b/Source/common/SNTXPCConnection.h new file mode 100644 index 00000000..f7944768 --- /dev/null +++ b/Source/common/SNTXPCConnection.h @@ -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 + +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 diff --git a/Source/common/SNTXPCConnection.m b/Source/common/SNTXPCConnection.m new file mode 100644 index 00000000..677340e7 --- /dev/null +++ b/Source/common/SNTXPCConnection.m @@ -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 diff --git a/Source/common/SNTXPCControlInterface.h b/Source/common/SNTXPCControlInterface.h new file mode 100644 index 00000000..9ec2ff22 --- /dev/null +++ b/Source/common/SNTXPCControlInterface.h @@ -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 diff --git a/Source/common/SNTXPCControlInterface.m b/Source/common/SNTXPCControlInterface.m new file mode 100644 index 00000000..f3a8a482 --- /dev/null +++ b/Source/common/SNTXPCControlInterface.m @@ -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 diff --git a/Source/common/SNTXPCNotifierInterface.h b/Source/common/SNTXPCNotifierInterface.h new file mode 100644 index 00000000..03d83c72 --- /dev/null +++ b/Source/common/SNTXPCNotifierInterface.h @@ -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 diff --git a/Source/common/SNTXPCNotifierInterface.m b/Source/common/SNTXPCNotifierInterface.m new file mode 100644 index 00000000..095c4822 --- /dev/null +++ b/Source/common/SNTXPCNotifierInterface.m @@ -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 diff --git a/Source/santa-driver/Resources/santa-driver-Info.plist b/Source/santa-driver/Resources/santa-driver-Info.plist new file mode 100644 index 00000000..e7c74970 --- /dev/null +++ b/Source/santa-driver/Resources/santa-driver-Info.plist @@ -0,0 +1,53 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIconFile + + CFBundleIdentifier + com.google.santa-driver + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + KEXT + CFBundleShortVersionString + 0.7 + CFBundleSignature + ???? + CFBundleVersion + 0.7 + IOKitPersonalities + + SantaDriver + + CFBundleIdentifier + com.google.santa-driver + IOClass + com_google_SantaDriver + IOProviderClass + IOResources + IOResourceMatch + IOKit + IOUserClientClass + com_google_SantaDriverClient + + + OSBundleLibraries + + com.apple.kpi.bsd + 9.0.0 + com.apple.kpi.iokit + 9.0.0 + com.apple.kpi.libkern + 9.0.0 + com.apple.kpi.mach + 9.0.0 + + + diff --git a/Source/santa-driver/SantaDecisionManager.cc b/Source/santa-driver/SantaDecisionManager.cc new file mode 100644 index 00000000..dd517701 --- /dev/null +++ b/Source/santa-driver/SantaDecisionManager.cc @@ -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(this)); + if (!process_listener_) return kIOReturnInternalError; + LOGD("Process listener started."); + + vnode_listener_ = kauth_listen_scope(KAUTH_SCOPE_VNODE, + vnode_scope_callback, + reinterpret_cast(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(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(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(idata)); + vfs_context_t vfs_context = reinterpret_cast(arg0); + vnode_t vnode = reinterpret_cast(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(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; +} diff --git a/Source/santa-driver/SantaDecisionManager.h b/Source/santa-driver/SantaDecisionManager.h new file mode 100644 index 00000000..444ea57e --- /dev/null +++ b/Source/santa-driver/SantaDecisionManager.h @@ -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 +#include +#include +#include +#include +#include +#include + +#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 diff --git a/Source/santa-driver/SantaDriver.cc b/Source/santa-driver/SantaDriver.cc new file mode 100644 index 00000000..bb2909f7 --- /dev/null +++ b/Source/santa-driver/SantaDriver.cc @@ -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 diff --git a/Source/santa-driver/SantaDriver.h b/Source/santa-driver/SantaDriver.h new file mode 100644 index 00000000..127a6bf3 --- /dev/null +++ b/Source/santa-driver/SantaDriver.h @@ -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 +#include + +#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 diff --git a/Source/santa-driver/SantaDriverClient.cc b/Source/santa-driver/SantaDriverClient.cc new file mode 100644 index 00000000..df5227f8 --- /dev/null +++ b/Source/santa-driver/SantaDriverClient.cc @@ -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(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(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(&SantaDriverClient::static_open), + 0, // input scalar + 0, // input struct + 0, // output scalar + 0 // output struct + }, + { + reinterpret_cast( + &SantaDriverClient::static_close), + 0, + 0, + 0, + 0 + }, + { + reinterpret_cast( + &SantaDriverClient::static_allow_binary), + 1, + 0, + 0, + 0 + }, + { + reinterpret_cast( + &SantaDriverClient::static_deny_binary), + 1, + 0, + 0, + 0 + }, + { + reinterpret_cast( + &SantaDriverClient::static_clear_cache), + 0, + 0, + 0, + 0 + }, + { + reinterpret_cast( + &SantaDriverClient::static_cache_count), + 0, + 0, + 1, + 0 + } + }; + + if (selector < static_cast(kSantaUserClientNMethods)) { + dispatch = &(sMethods[selector]); + if (!target) target = this; + } else { + return kIOReturnBadArgument; + } + + return super::externalMethod(selector, + arguments, + dispatch, + target, + reference); +} + +#undef super diff --git a/Source/santa-driver/SantaDriverClient.h b/Source/santa-driver/SantaDriverClient.h new file mode 100644 index 00000000..73103173 --- /dev/null +++ b/Source/santa-driver/SantaDriverClient.h @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#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 diff --git a/Source/santa-driver/SantaMessage.cc b/Source/santa-driver/SantaMessage.cc new file mode 100644 index 00000000..40b89378 --- /dev/null +++ b/Source/santa-driver/SantaMessage.cc @@ -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; +} diff --git a/Source/santa-driver/SantaMessage.h b/Source/santa-driver/SantaMessage.h new file mode 100644 index 00000000..7617fdf1 --- /dev/null +++ b/Source/santa-driver/SantaMessage.h @@ -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 + +#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 diff --git a/Source/santactl/Resources/santactl-Info.plist b/Source/santactl/Resources/santactl-Info.plist new file mode 100644 index 00000000..57834582 --- /dev/null +++ b/Source/santactl/Resources/santactl-Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleIdentifier + com.google.santactl + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundleShortVersionString + 0.7 + CFBundleSignature + ???? + CFBundleVersion + 0.7 + CSFlags + kill + + diff --git a/Source/santactl/Resources/santactl-Prefix.pch b/Source/santactl/Resources/santactl-Prefix.pch new file mode 100644 index 00000000..b951e765 --- /dev/null +++ b/Source/santactl/Resources/santactl-Prefix.pch @@ -0,0 +1,3 @@ +#ifdef __OBJC__ + #import +#endif diff --git a/Source/santactl/SNTCommandController.h b/Source/santactl/SNTCommandController.h new file mode 100644 index 00000000..6b12256d --- /dev/null +++ b/Source/santactl/SNTCommandController.h @@ -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 +/// 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)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]; } diff --git a/Source/santactl/SNTCommandController.m b/Source/santactl/SNTCommandController.m new file mode 100644 index 00000000..a6579797 --- /dev/null +++ b/Source/santactl/SNTCommandController.m @@ -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)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 cmd = registeredCommands[cmdName]; + [helpText appendFormat:@"\t%*s - %@\n", longestCommandName, + [cmdName UTF8String], [cmd shortHelpText]]; + } + + return helpText; +} + ++ (NSString *)helpForCommandWithName:(NSString *)commandName { + Class 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 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 diff --git a/Source/santactl/binaryinfo/SNTCommandBinaryInfo.m b/Source/santactl/binaryinfo/SNTCommandBinaryInfo.m new file mode 100644 index 00000000..960930dd --- /dev/null +++ b/Source/santactl/binaryinfo/SNTCommandBinaryInfo.m @@ -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 +@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 diff --git a/Source/santactl/flushcache/SNTCommandFlushCache.m b/Source/santactl/flushcache/SNTCommandFlushCache.m new file mode 100644 index 00000000..9be5dd31 --- /dev/null +++ b/Source/santactl/flushcache/SNTCommandFlushCache.m @@ -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 +@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 diff --git a/Source/santactl/main.m b/Source/santactl/main.m new file mode 100644 index 00000000..2c956352 --- /dev/null +++ b/Source/santactl/main.m @@ -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]; + } +} diff --git a/Source/santactl/status/SNTCommandStatus.m b/Source/santactl/status/SNTCommandStatus.m new file mode 100644 index 00000000..314f56b4 --- /dev/null +++ b/Source/santactl/status/SNTCommandStatus.m @@ -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 + +#import "SNTBinaryInfo.h" +#import "SNTKernelCommon.h" +#import "SNTLogging.h" +#import "SNTXPCConnection.h" +#import "SNTXPCControlInterface.h" + +@interface SNTCommandStatus : NSObject +@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 diff --git a/Source/santactl/sync/SNTAuthenticatingURLSession.h b/Source/santactl/sync/SNTAuthenticatingURLSession.h new file mode 100644 index 00000000..4eb7fc82 --- /dev/null +++ b/Source/santactl/sync/SNTAuthenticatingURLSession.h @@ -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 + +/// 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 diff --git a/Source/santactl/sync/SNTAuthenticatingURLSession.m b/Source/santactl/sync/SNTAuthenticatingURLSession.m new file mode 100644 index 00000000..67f3c289 --- /dev/null +++ b/Source/santactl/sync/SNTAuthenticatingURLSession.m @@ -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 diff --git a/Source/santactl/sync/SNTCommandSync.m b/Source/santactl/sync/SNTCommandSync.m new file mode 100644 index 00000000..a598028c --- /dev/null +++ b/Source/santactl/sync/SNTCommandSync.m @@ -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 +@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 diff --git a/Source/santactl/sync/SNTCommandSyncEventUpload.h b/Source/santactl/sync/SNTCommandSyncEventUpload.h new file mode 100644 index 00000000..92739097 --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncEventUpload.h @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncEventUpload.m b/Source/santactl/sync/SNTCommandSyncEventUpload.m new file mode 100644 index 00000000..b49ab6ad --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncEventUpload.m @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncLogUpload.h b/Source/santactl/sync/SNTCommandSyncLogUpload.h new file mode 100644 index 00000000..ce20e669 --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncLogUpload.h @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncLogUpload.m b/Source/santactl/sync/SNTCommandSyncLogUpload.m new file mode 100644 index 00000000..64dc2f95 --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncLogUpload.m @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncPostflight.h b/Source/santactl/sync/SNTCommandSyncPostflight.h new file mode 100644 index 00000000..974d263a --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncPostflight.h @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncPostflight.m b/Source/santactl/sync/SNTCommandSyncPostflight.m new file mode 100644 index 00000000..d82a8f85 --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncPostflight.m @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncPreflight.h b/Source/santactl/sync/SNTCommandSyncPreflight.h new file mode 100644 index 00000000..31f6e64a --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncPreflight.h @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncPreflight.m b/Source/santactl/sync/SNTCommandSyncPreflight.m new file mode 100644 index 00000000..8f656772 --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncPreflight.m @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncRuleDownload.h b/Source/santactl/sync/SNTCommandSyncRuleDownload.h new file mode 100644 index 00000000..4b8e9130 --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncRuleDownload.h @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncRuleDownload.m b/Source/santactl/sync/SNTCommandSyncRuleDownload.m new file mode 100644 index 00000000..26d0a5df --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncRuleDownload.m @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncStatus.h b/Source/santactl/sync/SNTCommandSyncStatus.h new file mode 100644 index 00000000..17370fc9 --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncStatus.h @@ -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 diff --git a/Source/santactl/sync/SNTCommandSyncStatus.m b/Source/santactl/sync/SNTCommandSyncStatus.m new file mode 100644 index 00000000..30aeae24 --- /dev/null +++ b/Source/santactl/sync/SNTCommandSyncStatus.m @@ -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 diff --git a/Source/santactl/sync/SNTDERDecoder.h b/Source/santactl/sync/SNTDERDecoder.h new file mode 100644 index 00000000..deba81df --- /dev/null +++ b/Source/santactl/sync/SNTDERDecoder.h @@ -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 diff --git a/Source/santactl/sync/SNTDERDecoder.m b/Source/santactl/sync/SNTDERDecoder.m new file mode 100644 index 00000000..0682cac1 --- /dev/null +++ b/Source/santactl/sync/SNTDERDecoder.m @@ -0,0 +1,201 @@ +#import "SNTDERDecoder.h" + +#import +#import + +@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 diff --git a/Source/santad/Resources/santad-Info.plist b/Source/santad/Resources/santad-Info.plist new file mode 100644 index 00000000..a12b312e --- /dev/null +++ b/Source/santad/Resources/santad-Info.plist @@ -0,0 +1,18 @@ + + + + + CFBundleIdentifier + com.google.santad + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundleShortVersionString + 0.7 + CFBundleSignature + ???? + CFBundleVersion + 0.7 + + diff --git a/Source/santad/Resources/santad-Prefix.pch b/Source/santad/Resources/santad-Prefix.pch new file mode 100644 index 00000000..472179bf --- /dev/null +++ b/Source/santad/Resources/santad-Prefix.pch @@ -0,0 +1,3 @@ +#ifdef __OBJC__ +#import +#endif \ No newline at end of file diff --git a/Source/santad/SNTApplication.h b/Source/santad/SNTApplication.h new file mode 100644 index 00000000..c4bc3ca0 --- /dev/null +++ b/Source/santad/SNTApplication.h @@ -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 diff --git a/Source/santad/SNTApplication.m b/Source/santad/SNTApplication.m new file mode 100644 index 00000000..54adb0f5 --- /dev/null +++ b/Source/santad/SNTApplication.m @@ -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 +#include + +#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 diff --git a/Source/santad/SNTDaemonControlController.h b/Source/santad/SNTDaemonControlController.h new file mode 100644 index 00000000..447d73c1 --- /dev/null +++ b/Source/santad/SNTDaemonControlController.h @@ -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 + +@property SNTDriverManager *driverManager; + +- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager; + +@end diff --git a/Source/santad/SNTDaemonControlController.m b/Source/santad/SNTDaemonControlController.m new file mode 100644 index 00000000..e957fadd --- /dev/null +++ b/Source/santad/SNTDaemonControlController.m @@ -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 diff --git a/Source/santad/SNTDatabaseController.h b/Source/santad/SNTDatabaseController.h new file mode 100644 index 00000000..288b2fd9 --- /dev/null +++ b/Source/santad/SNTDatabaseController.h @@ -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 + +@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 diff --git a/Source/santad/SNTDatabaseController.m b/Source/santad/SNTDatabaseController.m new file mode 100644 index 00000000..938d3281 --- /dev/null +++ b/Source/santad/SNTDatabaseController.m @@ -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 diff --git a/Source/santad/SNTDatabaseTable.h b/Source/santad/SNTDatabaseTable.h new file mode 100644 index 00000000..71fb5f88 --- /dev/null +++ b/Source/santad/SNTDatabaseTable.h @@ -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 + +@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 diff --git a/Source/santad/SNTDatabaseTable.m b/Source/santad/SNTDatabaseTable.m new file mode 100644 index 00000000..cb6a6e9d --- /dev/null +++ b/Source/santad/SNTDatabaseTable.m @@ -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 diff --git a/Source/santad/SNTDriverManager.h b/Source/santad/SNTDriverManager.h new file mode 100644 index 00000000..13fa8901 --- /dev/null +++ b/Source/santad/SNTDriverManager.h @@ -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 \ No newline at end of file diff --git a/Source/santad/SNTDriverManager.m b/Source/santad/SNTDriverManager.m new file mode 100644 index 00000000..60f5bcbe --- /dev/null +++ b/Source/santad/SNTDriverManager.m @@ -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 +#include + +#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 diff --git a/Source/santad/SNTEventTable.h b/Source/santad/SNTEventTable.h new file mode 100644 index 00000000..c24a193f --- /dev/null +++ b/Source/santad/SNTEventTable.h @@ -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 diff --git a/Source/santad/SNTEventTable.m b/Source/santad/SNTEventTable.m new file mode 100644 index 00000000..f38e7008 --- /dev/null +++ b/Source/santad/SNTEventTable.m @@ -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 diff --git a/Source/santad/SNTExecutionController.h b/Source/santad/SNTExecutionController.h new file mode 100644 index 00000000..7a4b4e2b --- /dev/null +++ b/Source/santad/SNTExecutionController.h @@ -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 diff --git a/Source/santad/SNTExecutionController.m b/Source/santad/SNTExecutionController.m new file mode 100644 index 00000000..d28460e2 --- /dev/null +++ b/Source/santad/SNTExecutionController.m @@ -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 + +#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:¤tSessions]; + 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:@""]; + + if (cert && cert.SHA1 && cert.commonName) { + // Also ensure there are no commas in the cert's common name. + NSString *printCommonName = [cert.commonName stringByReplacingOccurrencesOfString:@"," + withString:@""]; + 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 diff --git a/Source/santad/SNTRuleTable.h b/Source/santad/SNTRuleTable.h new file mode 100644 index 00000000..758e2cb3 --- /dev/null +++ b/Source/santad/SNTRuleTable.h @@ -0,0 +1,46 @@ +/// 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" + +#include "SNTCommonEnums.h" + +@class SNTRule; +@class SNTNotificationMessage; + +/// Responsible for managing the cache tables in the Santa database (certificates & binaries) +@interface SNTRuleTable : SNTDatabaseTable + +/// @return Number of rules in the database +- (long)ruleCount; + +/// @return Number of binary rules in the database +- (long)binaryRuleCount; + +/// @return Number of certificate rules in the database +- (long)certificateRuleCount; + +/// @return Rule for binary with given SHA-1 +- (SNTRule *)binaryRuleForSHA1:(NSString *)SHA1; + +/// @return Rule for certificate with given SHA-1 +- (SNTRule *)certificateRuleForSHA1:(NSString *)SHA1; + +/// Add a single rule to the database +- (void)addRule:(SNTRule *)rule; + +/// Add an array of rules to the database +- (void)addRules:(NSArray *)rules; + +@end diff --git a/Source/santad/SNTRuleTable.m b/Source/santad/SNTRuleTable.m new file mode 100644 index 00000000..3d5fc131 --- /dev/null +++ b/Source/santad/SNTRuleTable.m @@ -0,0 +1,146 @@ +/// 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 "SNTRuleTable.h" + +#import "SNTCertificate.h" +#import "SNTCodesignChecker.h" +#import "SNTNotificationMessage.h" +#import "SNTRule.h" + +@implementation SNTRuleTable + +- (int)initializeDatabase:(FMDatabase *)db fromVersion:(int)version { + int newVersion = 0; + + if (version < 1) { + [db executeUpdate:@"CREATE TABLE 'rules' (" + @"'sha1' TEXT NOT NULL, " + @"'state' INTEGER NOT NULL, " + @"'type' INTEGER NOT NULL, " + @"'customMsg' TEXT" + @")"]; + + [db executeUpdate:@"CREATE VIEW binrules AS SELECT * FROM rules WHERE type=1"]; + [db executeUpdate:@"CREATE VIEW certrules AS SELECT * FROM rules WHERE type=2"]; + + [db executeUpdate:@"CREATE UNIQUE INDEX rulesunique ON rules (sha1, type)"]; + + // Insert the codesigning certs for the running santad and launchd into the initial database. + // This helps prevent accidentally denying critical system components while the database + // is empty. This 'initial database' will then be cleared on the first successful sync. + NSString *santadSHA = [[[[SNTCodesignChecker alloc] initWithSelf] leafCertificate] SHA1]; + NSString *launchdSHA = [[[[SNTCodesignChecker alloc] initWithPID:1] leafCertificate] SHA1]; + [db executeUpdate:@"INSERT INTO rules (sha1, state, type) VALUES (?, ?, ?)", + santadSHA, @(RULESTATE_WHITELIST), @(RULETYPE_CERT)]; + [db executeUpdate:@"INSERT INTO rules (sha1, state, type) VALUES (?, ?, ?)", + launchdSHA, @(RULESTATE_WHITELIST), @(RULETYPE_CERT)]; + + newVersion = 1; + } + + return newVersion; +} + +#pragma mark Entry Counts + +- (long)ruleCount { + __block long count = 0; + [self inDatabase:^(FMDatabase *db) { + count = [db longForQuery:@"SELECT COUNT(*) FROM rules"]; + }]; + return count; +} + +- (long)binaryRuleCount { + __block long count = 0; + [self inDatabase:^(FMDatabase *db) { + count = [db longForQuery:@"SELECT COUNT(*) FROM binrules"]; + }]; + return count; +} + +- (long)certificateRuleCount { + __block long count = 0; + [self inDatabase:^(FMDatabase *db) { + count = [db longForQuery:@"SELECT COUNT(*) FROM certrules"]; + }]; + return count; +} + +- (SNTRule *)ruleFromResultSet:(FMResultSet *)rs { + SNTRule *rule = [[SNTRule alloc] init]; + + rule.SHA1 = [rs stringForColumn:@"sha1"]; + rule.type = [rs intForColumn:@"type"]; + rule.state = [rs intForColumn:@"state"]; + rule.customMsg = [rs stringForColumn:@"customMsg"]; + + return rule; +} + +- (SNTRule *)certificateRuleForSHA1:(NSString *)SHA1 { + __block SNTRule *rule; + + [self inDatabase:^(FMDatabase *db) { + FMResultSet *rs = [db executeQuery:@"SELECT * FROM certrules WHERE sha1=? LIMIT 1", SHA1]; + if ([rs next]) { + rule = [self ruleFromResultSet:rs]; + } + [rs close]; + }]; + + return rule; +} + +- (SNTRule *)binaryRuleForSHA1:(NSString *)SHA1 { + __block SNTRule *rule; + + [self inDatabase:^(FMDatabase *db) { + FMResultSet *rs = [db executeQuery:@"SELECT * FROM binrules WHERE sha1=? LIMIT 1", SHA1]; + if ([rs next]) { + rule = [self ruleFromResultSet:rs]; + } + [rs close]; + }]; + + return rule; +} + +#pragma mark Adding + +- (void)addRule:(SNTRule *)rule { + if (!rule.SHA1 || [rule.SHA1 length] == 0) return; + if (rule.state == RULESTATE_UNKNOWN) return; + if (rule.type == RULETYPE_UNKNOWN) return; + + [self inTransaction:^(FMDatabase *db, BOOL *rollback) { + if (rule.state == RULESTATE_REMOVE) { + [db executeUpdate:@"DELETE FROM rules WHERE SHA1=? AND type=?", + rule.SHA1, @(rule.type)]; + } else { + [db executeUpdate:@"INSERT OR REPLACE INTO rules (sha1, state, type, customMsg) " + @"VALUES (?, ?, ?, ?);", rule.SHA1, rule.state, rule.type, rule.customMsg]; + } + }]; +} + +- (void)addRules:(NSArray *)rules { + for (SNTRule *rule in rules) { + if (![rule isKindOfClass:[SNTRule class]]) return; + [self addRule:rule]; + } +} + +@end diff --git a/Source/santad/main.m b/Source/santad/main.m new file mode 100644 index 00000000..dba0439e --- /dev/null +++ b/Source/santad/main.m @@ -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 "SNTLogging.h" + +#import "SNTApplication.h" + +int main(int argc, const char *argv[]) { + @autoreleasepool { + // Do not buffer stdout + setbuf(stdout, NULL); + + NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; + + if ([[[NSProcessInfo processInfo] arguments] containsObject:@"-v"]) { + printf("%s\n", [infoDict[@"CFBundleVersion"] UTF8String]); + return 0; + } + + LOGI(@"Started, version %@", infoDict[@"CFBundleVersion"]); + + SNTApplication *s = [[SNTApplication alloc] init]; + [s performSelectorInBackground:@selector(run) withObject:nil]; + + [[NSRunLoop mainRunLoop] run]; + } + + return 0; +} \ No newline at end of file diff --git a/Tests/KernelTests/main.m b/Tests/KernelTests/main.m new file mode 100644 index 00000000..4f021611 --- /dev/null +++ b/Tests/KernelTests/main.m @@ -0,0 +1,421 @@ +/// 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 +#import +#import + +#include +#include +#include + +#include "SNTKernelCommon.h" + +/// +/// Kernel Extension Tests +/// +/// Build and launch as root while the kernel extension is loaded and nothing is already connected. +/// + +#define TSTART(testName) \ + printf(" %-50s ", testName); +#define TPASS() \ + printf("\x1b[32mPASS\x1b[0m\n"); +#define TFAIL() \ + printf("\x1b[31mFAIL\x1b[0m\n"); \ + exit(1); +#define TFAILINFO(fmt, ...) \ + printf("\x1b[31mFAIL\x1b[0m\n -> " fmt "\n\nTest failed.\n\n", ##__VA_ARGS__); \ + exit(1); + +@interface SantaKernelTests : NSObject +@property io_connect_t connection; +@property int timesSeenLs; +@property int timesSeenCat; +@property int timesSeenCp; +- (void)runTests; +@end + +@implementation SantaKernelTests + +#pragma mark - Test Helpers + +/// Return an initialized NSTask for |path| with stdout, stdin and stderr directed to /dev/null +- (NSTask *)taskWithPath:(NSString *)path { + NSTask *t = [[NSTask alloc] init]; + t.launchPath = path; + t.standardInput = nil; + t.standardOutput = nil; + t.standardError = nil; + + return t; +} + +#pragma mark - Driver Helpers + +/// Call in-kernel function: |kSantaUserClientReceive| passing the |action| and |sha1| via a +/// |santa_message_t| struct. +- (void)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)vnodeid { + if (action == ACTION_RESPOND_CHECKBW_ALLOW) { + IOConnectCallScalarMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid, 1, 0, 0); + } else if (action == ACTION_RESPOND_CHECKBW_DENY) { + IOConnectCallScalarMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid, 1, 0, 0); + } +} + +/// Call in-kernel function: |kSantaUserClientClearCache| +- (void)flushCache { + IOConnectCallScalarMethod(self.connection, kSantaUserClientClearCache, 0, 0, 0, 0); +} + +#pragma mark - Connection Tests + +/// Tests the process of locating, attaching and opening the driver. Also verifies that the +/// driver correctly refuses non-privileged connections. +- (void)connectionTests { + kern_return_t kr; + io_service_t serviceObject; + CFDictionaryRef classToMatch; + + TSTART("Creates matching service dictionary"); + if (!(classToMatch = IOServiceMatching(USERCLIENT_CLASS))) { + TFAIL(); + } + TPASS(); + + TSTART("Locates Santa driver"); + serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, classToMatch); + if (!serviceObject) { + TFAILINFO("Is santa-driver.kext loaded?"); + } + TPASS(); + + TSTART("Driver refuses non-privileged connections"); + (void)setegid(-2); + (void)seteuid(-2); + kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection); + if (kr != kIOReturnBadArgument) { + TFAIL(); + } + (void)setegid(0); + (void)seteuid(0); + TPASS(); + + TSTART("Attaches to and starts Santa service"); + kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_connection); + IOObjectRelease(serviceObject); + if (kr != kIOReturnSuccess) { + TFAILINFO("KR: %d", kr); + } + TPASS(); + + TSTART("Calls 'open' method on driver"); + kr = IOConnectCallMethod(self.connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0); + + if (kr == kIOReturnExclusiveAccess) { + TFAILINFO("A client is already connected to the driver.\n" + "Please kill the existing client and re-run the test."); + } else if (kr != kIOReturnSuccess) { + TFAILINFO("KR: %d", kr); + } + TPASS(); +} + +#pragma mark - Listener + +/// Tests the process of allocating & registering a notification port and mapping shared memory. +/// From then on, monitors the IODataQueue and responds for files specifically used in other tests. +/// For everything else, allows execution normally to avoid deadlocking the system. +- (void)beginListening { + kern_return_t kr; + santa_message_t vdata; + UInt32 dataSize; + IODataQueueMemory *queueMemory; + mach_port_t receivePort; + + mach_vm_address_t address = 0; + mach_vm_size_t size = 0; + unsigned int msgType = 1; + + TSTART("Allocates a notification port"); + if (!(receivePort = IODataQueueAllocateNotificationPort())) { + TFAIL(); + } + TPASS(); + + TSTART("Registers the notification port"); + kr = IOConnectSetNotificationPort(self.connection, msgType, receivePort, 0); + if (kr != kIOReturnSuccess) { + mach_port_destroy(mach_task_self(), receivePort); + TFAILINFO("KR: %d", kr); + return; + } + TPASS(); + + TSTART("Maps shared memory"); + kr = IOConnectMapMemory(self.connection, kIODefaultMemoryType, mach_task_self(), + &address, &size, kIOMapAnywhere); + if (kr != kIOReturnSuccess) { + mach_port_destroy(mach_task_self(), receivePort); + TFAILINFO("KR: %d", kr); + } + TPASS(); + + // Fetch the SHA-1 of /bin/ps, as we'll be using that for the cache invalidation test. + unsigned char sha1[CC_SHA1_DIGEST_LENGTH]; + NSData *psData = [NSData dataWithContentsOfFile:@"/bin/ps" + options:NSDataReadingMappedIfSafe + error:nil]; + CC_SHA1([psData bytes], (unsigned int)[psData length], sha1); + char buf[CC_SHA1_DIGEST_LENGTH * 2 + 1]; + for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) { + snprintf(buf + (2*i), 4, "%02x", (unsigned char)sha1[i]); + } + buf[CC_SHA1_DIGEST_LENGTH * 2] = '\0'; + NSString *psSHA = [NSString stringWithUTF8String:buf]; + + /// Begin listening for events + queueMemory = (IODataQueueMemory *)address; + while (IODataQueueWaitForAvailableData(queueMemory, receivePort) == kIOReturnSuccess) { + while (IODataQueueDataAvailable(queueMemory)) { + dataSize = sizeof(vdata); + kr = IODataQueueDequeue(queueMemory, &vdata, &dataSize); + if (kr == kIOReturnSuccess) { + if ([psSHA isEqual:@(vdata.sha1)]) { + [self postToKernelAction:ACTION_RESPOND_CHECKBW_DENY forVnodeID:vdata.vnode_id]; + } else if (strncmp("/bin/mv", vdata.path, strlen("/bin/mv")) == 0) { + [self postToKernelAction:ACTION_RESPOND_CHECKBW_DENY forVnodeID:vdata.vnode_id]; + } else if (strncmp("/bin/ls", vdata.path, strlen("/bin/ls")) == 0) { + [self postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vdata.vnode_id]; + self.timesSeenLs++; + } else if (strncmp("/bin/cp", vdata.path, strlen("/bin/cp")) == 0) { + [self postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vdata.vnode_id]; + self.timesSeenCp++; + } else if (strncmp("/bin/cat", vdata.path, strlen("/bin/cat")) == 0) { + [self postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vdata.vnode_id]; + self.timesSeenCat++; + } else if (strncmp("/bin/ln", vdata.path, strlen("/bin/ln")) == 0) { + [self postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vdata.vnode_id]; + + NSString *shatest1 = @(vdata.sha1); + TSTART("Sends valid, lowercase SHA-1 hash"); + if ([shatest1 length] != 40 || ![shatest1 isEqual:[shatest1 lowercaseString]]) { + TFAILINFO("Received bad SHA-1: '%s'", vdata.sha1); + } + TPASS(); + } else { + // Allow everything not related to our testing. + [self postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vdata.vnode_id]; + } + } else { + TFAILINFO("Error receiving data: %d", kr); + } + } + } + + IOConnectUnmapMemory(self.connection, kIODefaultMemoryType, mach_task_self(), address); + mach_port_destroy(mach_task_self(), receivePort); +} + +#pragma mark - Functional Tests + +/// Tests that the kernel sends a valid SHA-1 with an execution request +- (void)receivesSHA1Tests { + NSTask *ln = [self taskWithPath:@"/bin/ln"]; + [ln launch]; + [ln waitUntilExit]; +} + +/// Tests that blocking works correctly +- (void)receiveAndBlockTests { + TSTART("Blocks denied binaries"); + + NSTask *ps = [self taskWithPath:@"/bin/ps"]; + + @try { + [ps launch]; + [ps waitUntilExit]; + TFAIL(); + } + @catch (NSException *exception) { + TPASS(); + } +} + +/// Tests that an allowed binary is cached +- (void)receiveAndCacheTests { + TSTART("Permits & caches allowed binaries"); + + self.timesSeenLs = 0; + + NSTask *ls = [self taskWithPath:@"/bin/ls"]; + [ls launch]; + [ls waitUntilExit]; + + if (self.timesSeenLs != 1) { + TFAILINFO("Didn't record first run of ls"); + } + + ls = [self taskWithPath:@"/bin/ls"]; + [ls launch]; + [ls waitUntilExit]; + + if (self.timesSeenLs > 1) { + TFAILINFO("Received request for ls a second time"); + } + + TPASS(); +} + +/// Tests that a write to a cached vnode will invalidate the cached response for that file +- (void)invalidatesCacheTests { + TSTART("Invalidates cache correctly"); + + // Copy the ls binary to a new file + NSFileManager *fm = [NSFileManager defaultManager]; + if (![fm copyItemAtPath:@"/bin/pwd" toPath:@"santakerneltests_tmp" error:nil]) { + TFAILINFO("Failed to create temp file"); + } + + // Launch the new file to put it in the cache + NSTask *pwd = [self taskWithPath:@"santakerneltests_tmp"]; + [pwd launch]; + [pwd waitUntilExit]; + + // Exit if this fails with a useful message. + if ([pwd terminationStatus] != 0) { + TFAILINFO("First launch of test binary failed"); + } + + // Now replace the contents of the test file (which is cached) with the contents of /bin/ps, + // which is 'blacklisted' by SHA-1 during the tests. + FILE *infile = fopen("/bin/ps", "r"); + FILE *outfile = fopen("santakerneltests_tmp", "w"); + int ch; + while ((ch = fgetc(infile)) != EOF) { + fputc(ch, outfile); + } + fclose(infile); + fclose(outfile); + + // Now try running the temp file again. If it succeeds, the test failed. + NSTask *ps = [self taskWithPath:@"santakerneltests_tmp"]; + + @try { + [ps launch]; + [ps waitUntilExit]; + TFAIL(); + } @catch (NSException *exception) { + TPASS(); + } @finally { + [fm removeItemAtPath:@"santakerneltests_tmp" error:nil]; + } +} + +/// Tests the clear cache function works correctly +- (void)clearCacheTests { + TSTART("Can clear cache"); + + self.timesSeenCat = 0; + + NSTask *cat = [self taskWithPath:@"/bin/cat"]; + [cat launch]; + [cat waitUntilExit]; + + if (self.timesSeenCat != 1) { + TFAILINFO("Didn't record first run of cat"); + } + + [self flushCache]; + + cat = [self taskWithPath:@"/bin/cat"]; + [cat launch]; + [cat waitUntilExit]; + + if (self.timesSeenCat != 2) { + TFAIL(); + } + + TPASS(); +} + +/// Tests that the kernel still denies blocked binaries even if launched while traced +- (void)blocksDeniedTracedBinaries { + TSTART("Denies blocked processes running while traced"); + + pid_t pid = fork(); + if (pid < 0) { + TFAILINFO("Failed to fork"); + } else if (pid > 0) { + int status; + waitpid(pid, &status, 0); + if (WIFEXITED(status) && WEXITSTATUS(status) == EACCES) { + TPASS(); + } else if (WIFSTOPPED(status)) { + TFAILINFO("Process was executed and is waiting for debugger"); + } else { + TFAILINFO("Process did not exit with EACCESS as expected"); + } + } else if (pid == 0) { + fclose(stdout); + fclose(stderr); + ptrace(PT_TRACE_ME, 0, 0, 0); + execl("/bin/mv", "mv", NULL); + _exit(errno); + } +} + +#pragma mark - Main + +- (void)runTests { + printf("\nSanta Kernel Tests\n==================\n"); + printf("-> Connection tests:\n"); + + // Test that connection can be established + [self connectionTests]; + + // Open driver and begin listening for events. Run this on background thread + // so we can continue running tests. + [self performSelectorInBackground:@selector(beginListening) withObject:nil]; + + // Wait for driver to finish getting ready + sleep(1.0); + printf("\n-> Functional tests:\033[m\n"); + + [self receivesSHA1Tests]; + [self receiveAndBlockTests]; + [self receiveAndCacheTests]; + [self invalidatesCacheTests]; + [self clearCacheTests]; + [self blocksDeniedTracedBinaries]; + + printf("\nAll tests passed.\n\n"); +} + +@end + +int main(int argc, const char *argv[]) { + @autoreleasepool { + setbuf(stdout, NULL); + + if (getuid() != 0) { + printf("Please run as root\n"); + exit(1); + } + + SantaKernelTests *skt = [[SantaKernelTests alloc] init]; + [skt runTests]; + } + return 0; +} diff --git a/Tests/LogicTests/Resources/GIAG2.crt b/Tests/LogicTests/Resources/GIAG2.crt new file mode 100644 index 00000000..d24272bd Binary files /dev/null and b/Tests/LogicTests/Resources/GIAG2.crt differ diff --git a/Tests/LogicTests/Resources/GIAG2.pem b/Tests/LogicTests/Resources/GIAG2.pem new file mode 100644 index 00000000..6de79222 --- /dev/null +++ b/Tests/LogicTests/Resources/GIAG2.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG +EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy +bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP +VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv +h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE +ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ +EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC +DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7 +qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD +VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g +K4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI +KwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n +ZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB +BQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY +/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/ +zG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza +HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto +WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6 +yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx +-----END CERTIFICATE----- diff --git a/Tests/LogicTests/Resources/Tests-Info.plist b/Tests/LogicTests/Resources/Tests-Info.plist new file mode 100644 index 00000000..22e2a02b --- /dev/null +++ b/Tests/LogicTests/Resources/Tests-Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.google.${PRODUCT_NAME:rfc1034identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/Tests/LogicTests/Resources/Tests-Prefix.pch b/Tests/LogicTests/Resources/Tests-Prefix.pch new file mode 100644 index 00000000..4a87cf73 --- /dev/null +++ b/Tests/LogicTests/Resources/Tests-Prefix.pch @@ -0,0 +1,3 @@ +#ifdef __OBJC__ + #import +#endif diff --git a/Tests/LogicTests/Resources/apple.pem b/Tests/LogicTests/Resources/apple.pem new file mode 100644 index 00000000..87119fc4 --- /dev/null +++ b/Tests/LogicTests/Resources/apple.pem @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF+DCCBOCgAwIBAgIQXvpnDpnkq4jg8gszhnt4TTANBgkqhkiG9w0BAQUFADCB +vjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug +YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE4MDYGA1UEAxMv +VmVyaVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBTR0MgQ0Ew +HhcNMTMxMTE0MDAwMDAwWhcNMTUxMTE0MjM1OTU5WjCCAQoxEzARBgsrBgEEAYI3 +PAIBAxMCVVMxGzAZBgsrBgEEAYI3PAIBAhMKQ2FsaWZvcm5pYTEdMBsGA1UEDxMU +UHJpdmF0ZSBPcmdhbml6YXRpb24xETAPBgNVBAUTCEMwODA2NTkyMQswCQYDVQQG +EwJVUzEOMAwGA1UEERQFOTUwMTQxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNV +BAcUCUN1cGVydGlubzEYMBYGA1UECRQPMSBJbmZpbml0ZSBMb29wMRMwEQYDVQQK +FApBcHBsZSBJbmMuMRcwFQYDVQQLFA5JU0cgZm9yIEFrYW1haTEWMBQGA1UEAxQN +d3d3LmFwcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMRK +AzgN5IfMI0ioi1hYtFu5x6CGTH3/wpdlACJhfUtcAk7k1GaaMQKy/6f4scGI3tmj +K6qJjvEHf3cp6sliOPBrCLoby8/4r1T1+yBxanpe8arpChtj5KwD+owXQYwN7jqO +IB4nQGQin+D3aJiiqJfBc4XxhAEh+u29/RM+ux3l+QDMTzOm2hecl/z+wqtmRNeq +wCp1MHL+ejGaf60/SJUQM0QlQjqUcQBNbE73Lx+8UpZzsxjbEwOnstmSLkAjWQTE +FBVwPGqLF6ttR6ytACnUi2w6bFJsDv71nuIO56/SpzB/ZivddtZv4iYDg8ALBnFF +3AZI5jhbSid+vDzz5XECAwEAAaOCAaEwggGdMBgGA1UdEQQRMA+CDXd3dy5hcHBs +ZS5jb20wCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBaAwKAYDVR0lBCEwHwYIKwYB +BQUHAwEGCCsGAQUFBwMCBglghkgBhvhCBAEwRAYDVR0gBD0wOzA5BgtghkgBhvhF +AQcXBjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vY3Bz +MB0GA1UdDgQWBBT/EdiWLzrKfY9ayp23GIwElp5xiTAfBgNVHSMEGDAWgBROQ8gd +du83U3pP8lhvlPM44tW93zA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vRVZJbnRs +LWNybC52ZXJpc2lnbi5jb20vRVZJbnRsMjAwNi5jcmwwdgYIKwYBBQUHAQEEajBo +MCsGCCsGAQUFBzABhh9odHRwOi8vRVZJbnRsLW9jc3AudmVyaXNpZ24uY29tMDkG +CCsGAQUFBzAChi1odHRwOi8vRVZJbnRsLWFpYS52ZXJpc2lnbi5jb20vRVZJbnRs +MjAwNi5jZXIwDQYJKoZIhvcNAQEFBQADggEBAI4W8R0KFLvKzi20sd3cSaWHLqNb +K6AgFSShRtrrPIu2yNXzI+VjHZTuPhViqSGxfZJYI6c4e60zz5BkfuB0RbrJjQF9 +iFjA/+bfS6+dIU95D9+zwp6u/NhSZAoqUH8LU5+GLGdCy2f9wU1Gyp47jgY9sDdF +tgkrVk1AnKU7Okd+n6ZOeUCTXgJgVm6UWFt5FXr80VMdEWv2U1JYxwNzITR+tJ// +u5BBaPG2p/6E3d9lmer8Ffq97CZ8poAeXsQXtuwNdwMCrbUGZKalaS71G/sSMrAR +xVVZakUhwnXXswczh701AK+TzB3dQNhFWooX/sn4+MiIkg+WPaxkd8AsCIM= +-----END CERTIFICATE----- diff --git a/Tests/LogicTests/Resources/tubitak.crt b/Tests/LogicTests/Resources/tubitak.crt new file mode 100644 index 00000000..49d447b3 Binary files /dev/null and b/Tests/LogicTests/Resources/tubitak.crt differ diff --git a/Tests/LogicTests/SNTBinaryInfoTest.m b/Tests/LogicTests/SNTBinaryInfoTest.m new file mode 100644 index 00000000..cea19a9b --- /dev/null +++ b/Tests/LogicTests/SNTBinaryInfoTest.m @@ -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 + +#import "SNTBinaryInfo.h" + +@interface SNTBinaryInfoTest : XCTestCase +@end + +@implementation SNTBinaryInfoTest + +- (void)testSHA1 { + SNTBinaryInfo *sut = [[SNTBinaryInfo alloc] initWithPath:@"/sbin/launchd"]; + + XCTAssertNotNil(sut.SHA1); + XCTAssertEqual(sut.SHA1.length, 40); +} + +- (void)testExecutable { + SNTBinaryInfo *sut = [[SNTBinaryInfo alloc] initWithPath:@"/sbin/launchd"]; + + XCTAssertTrue(sut.isMachO); + XCTAssertTrue(sut.isExecutable); + + XCTAssertFalse(sut.isDylib); + XCTAssertFalse(sut.isFat); + XCTAssertFalse(sut.isKext); + XCTAssertFalse(sut.isScript); +} + +- (void)testKext { + SNTBinaryInfo *sut = + [[SNTBinaryInfo alloc] initWithPath: + @"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"]; + + XCTAssertTrue(sut.isMachO); + XCTAssertTrue(sut.isKext); + + XCTAssertFalse(sut.isDylib); + XCTAssertFalse(sut.isExecutable); + XCTAssertFalse(sut.isFat); + XCTAssertFalse(sut.isScript); +} + +- (void)testDylibs { + SNTBinaryInfo *sut = [[SNTBinaryInfo alloc] initWithPath:@"/usr/lib/libsqlite3.dylib"]; + + XCTAssertTrue(sut.isMachO); + XCTAssertTrue(sut.isDylib); + XCTAssertTrue(sut.isFat); + + XCTAssertFalse(sut.isKext); + XCTAssertFalse(sut.isExecutable); + XCTAssertFalse(sut.isScript); +} + +- (void)testScript { + SNTBinaryInfo *sut = [[SNTBinaryInfo alloc] initWithPath:@"/usr/bin/h2ph"]; + + XCTAssertTrue(sut.isScript); + + XCTAssertFalse(sut.isDylib); + XCTAssertFalse(sut.isExecutable); + XCTAssertFalse(sut.isFat); + XCTAssertFalse(sut.isKext); + XCTAssertFalse(sut.isMachO); +} + +- (void)testBundle { + SNTBinaryInfo *sut = + [[SNTBinaryInfo alloc] initWithPath:@"/Applications/Safari.app/Contents/MacOS/Safari"]; + + XCTAssertNotNil([sut bundle]); + + XCTAssertEqualObjects([sut bundleIdentifier], @"com.apple.Safari"); + XCTAssertEqualObjects([sut bundleName], @"Safari"); + XCTAssertNotNil([sut bundleVersion]); + XCTAssertNotNil([sut bundleShortVersionString]); + XCTAssertEqualObjects([sut bundlePath], @"/Applications/Safari.app"); +} + +- (void)testEmbeddedInfoPlist { + // csreq is installed on all machines with Xcode installed. If you're running these tests, + // it should be available.. + SNTBinaryInfo *sut = [[SNTBinaryInfo alloc] initWithPath:@"/usr/bin/csreq"]; + + XCTAssertNotNil([sut infoPlist]); +} + +@end \ No newline at end of file diff --git a/Tests/LogicTests/SNTCertificateTest.m b/Tests/LogicTests/SNTCertificateTest.m new file mode 100644 index 00000000..09aa3f30 --- /dev/null +++ b/Tests/LogicTests/SNTCertificateTest.m @@ -0,0 +1,227 @@ +/// 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 +#import + +#import "SNTCertificate.h" + +@interface SNTCertificate (Testing) +- (NSString *)x509ValueForLabel:(NSString *)desiredLabel fromDictionary:(NSDictionary *)dict; +- (NSDate *)dateForX509Key:(NSString *)key; +@end + +@interface SNTCertificateTest : XCTestCase +@property NSString *testDataPEM1; +@property NSString *testDataPEM2; +@property NSData *testDataDER1; +@property NSData *testDataDER2; +@property NSString *testDataPrivateKey; +@end + +@implementation SNTCertificateTest + +- (void)setUp { + [super setUp]; + + NSString *file = [[NSBundle bundleForClass:[self class]] pathForResource:@"GIAG2" ofType:@"pem"]; + self.testDataPEM1 = [NSString stringWithContentsOfFile:file + encoding:NSUTF8StringEncoding + error:nil]; + + file = [[NSBundle bundleForClass:[self class]] pathForResource:@"apple" ofType:@"pem"]; + self.testDataPEM2 = [NSString stringWithContentsOfFile:file + encoding:NSUTF8StringEncoding + error:nil]; + + file = [[NSBundle bundleForClass:[self class]] pathForResource:@"GIAG2" ofType:@"crt"]; + self.testDataDER1 = [NSData dataWithContentsOfFile:file]; + + file = [[NSBundle bundleForClass:[self class]] pathForResource:@"tubitak" ofType:@"crt"]; + self.testDataDER2 = [NSData dataWithContentsOfFile:file]; + + self.testDataPrivateKey = @"-----BEGIN RSA PRIVATE KEY-----" + @"MIICXQIBAAKBgQDk2F9JsQQjKSveMwazXzFLbiiOD0RkDiRX1LTmQtVdi514F6l/" + @"RwohMrwxQpsoKwyzEngX58+PrGZ0XZrcVcHn666521IxswHZPaacBlWZ7k9XkB2Y" + @"m8mxULMBG9iIv/k5tRJN3MuJdtbQc8qLBsyFFsytL8hSRvBQNyP7N/OqnQIDAQAB" + @"AoGATpLUNNMonoH2Y/aVKGVY4ZNTLWOkkc4hQF7yNdVguRvE14UYV3Em0zs+TpOV" + @"/na5h4qh3WNkaupAy1eQYnK3fqmGLZw5e8cBCgUkIi8P//zMrKlgJKwfzQHSdJSP" + @"pkCvj2kliFwNzbA026jcwGEYV+uRCNazO5ldtOcP5EDb+qkCQQD/Ihc2mjtf7oq1" + @"VZSzo0xch3NtzTZMyFCRWqMpXHQO1fZTAe96EbI85zsTRmOVuqKnGxBvvtHJr2QY" + @"UoZ72+f7AkEA5Z9qte46t1F1ME3ZzWd6Ob1obCmuAa75eTPAgQKc+1bSVeFMGLTz" + @"n2M9wZ+mIpWvJp8QRdmOi0zpEArHqa68RwJBAO1YoY/CW4obOB8JxpR3TgqmV9PG" + @"HMXBdHJEh5Vq1O0YT1dZbZd57v6JfoOn7+zS+43Jt7i9JB0kdVHLNCD1qxECQQC3" + @"wXGGEhVO6pMbitGHvQ1k85yDIn+rvTjLs4yUMWErCfnc3CUniHeFz8d2EarD9oFq" + @"KNS+8TFPbMb+HYJW2gy1AkAHGBUKmZNPGiKJEUjc5jN1uN+B9OMLDX+3rMUO9Q2x" + @"jsn0m7Mobx+pPqbIAvsklMtA4Qdrt5a9pnwEgTWoJPYA" + @"-----END RSA PRIVATE KEY-----"; +} + +- (void)tearDown { + + [super tearDown]; +} + +- (void)testInitWithDER { + SNTCertificate *sut = [[SNTCertificate alloc] initWithCertificateDataDER:self.testDataDER1]; + + XCTAssertNotNil(sut); + XCTAssertEqualObjects(sut.commonName, @"Google Internet Authority G2"); + XCTAssertEqualObjects(sut.orgUnit, nil); + XCTAssertEqualObjects(sut.orgName, @"Google Inc"); + XCTAssertEqualObjects(sut.issuerCommonName, @"GeoTrust Global CA"); + XCTAssertEqualObjects(sut.issuerOrgName, @"GeoTrust Inc."); + XCTAssertEqualObjects(sut.issuerOrgUnit, nil); + XCTAssertEqualObjects(sut.SHA1, @"d83c1a7f4d0446bb2081b81a1670f8183451ca24"); + XCTAssertEqualObjects(sut.validFrom, [NSDate dateWithString:@"2013-04-05 15:15:55 +0000"]); + XCTAssertEqualObjects(sut.validUntil, [NSDate dateWithString:@"2015-04-04 15:15:55 +0000"]); + + sut = [[SNTCertificate alloc] initWithCertificateDataDER:self.testDataDER2]; + XCTAssertNotNil(sut); + XCTAssertEqualObjects(sut.commonName, + @"TÜBİTAK UEKAE Kök Sertifika Hizmet Sağlayıcısı - Sürüm 3"); + XCTAssertEqualObjects(sut.orgUnit, + @"Ulusal Elektronik ve Kriptoloji Araştırma Enstitüsü - UEKAE"); + XCTAssertEqualObjects(sut.orgName, + @"Türkiye Bilimsel ve Teknolojik Araştırma Kurumu - TÜBİTAK"); +} + +- (void)testInitWithValidPEM { + SNTCertificate *sut = [[SNTCertificate alloc] initWithCertificateDataPEM:self.testDataPEM1]; + + XCTAssertNotNil(sut); + XCTAssertEqualObjects(sut.commonName, @"Google Internet Authority G2"); + XCTAssertEqualObjects(sut.orgUnit, nil); + XCTAssertEqualObjects(sut.orgName, @"Google Inc"); + XCTAssertEqualObjects(sut.issuerCommonName, @"GeoTrust Global CA"); + XCTAssertEqualObjects(sut.SHA1, @"d83c1a7f4d0446bb2081b81a1670f8183451ca24"); + XCTAssertEqualObjects(sut.validFrom, [NSDate dateWithString:@"2013-04-05 15:15:55 +0000"]); + XCTAssertEqualObjects(sut.validUntil, [NSDate dateWithString:@"2015-04-04 15:15:55 +0000"]); + + sut = [[SNTCertificate alloc] initWithCertificateDataPEM:self.testDataPEM2]; + XCTAssertNotNil(sut); + XCTAssertEqualObjects(sut.commonName, @"www.apple.com"); + XCTAssertEqualObjects(sut.orgUnit, @"ISG for Akamai"); + XCTAssertEqualObjects(sut.orgName, @"Apple Inc."); + XCTAssertEqualObjects(sut.issuerCommonName, @"VeriSign Class 3 Extended Validation SSL SGC CA"); + XCTAssertEqualObjects(sut.issuerOrgName, @"VeriSign, Inc."); + XCTAssertEqualObjects(sut.issuerOrgUnit, @"VeriSign Trust Network"); + XCTAssertEqualObjects(sut.SHA1, @"96df534f6f4306ca474d9078fc346b20f856f0d4"); + XCTAssertEqualObjects(sut.validFrom, [NSDate dateWithString:@"2013-11-14 00:00:00 +0000"]); + XCTAssertEqualObjects(sut.validUntil, [NSDate dateWithString:@"2015-11-14 23:59:59 +0000"]); +} + +- (void)testInitWithValidPEMAfterKey { + NSString *pemWithKey = [self.testDataPrivateKey stringByAppendingString:self.testDataPEM1]; + SNTCertificate *sut = [[SNTCertificate alloc] initWithCertificateDataPEM:pemWithKey]; + + XCTAssertNotNil(sut); + XCTAssertEqualObjects(sut.commonName, @"Google Internet Authority G2"); +} + +- (void)testInitWithEmptyPEM { + NSString *badPEM = @"-----BEGIN CERTIFICATE----------END CERTIFICATE-----"; + SNTCertificate *sut = [[SNTCertificate alloc] initWithCertificateDataPEM:badPEM]; + XCTAssertNil(sut); +} + +- (void)testInitWithTruncatedPEM { + NSString *badPEM = @"-----BEGIN CERTIFICATE-----" + @"MIICXQIBAAKBgQDk2F9JsQQjKSveMwazXzFLbiiOD0RkDiRX1LTmQtVdi514F6l/"; + SNTCertificate *sut = [[SNTCertificate alloc] initWithCertificateDataPEM:badPEM]; + XCTAssertNil(sut); +} + +- (void)testInitWithInvalidPEM { + NSString *badPEM = @"This is not a valid PEM"; + SNTCertificate *sut = [[SNTCertificate alloc] initWithCertificateDataPEM:badPEM]; + XCTAssertNil(sut); + + badPEM = @"-----BEGIN CERTIFICATE-----Hello Thar-----END CERTIFICATE-----"; + sut = [[SNTCertificate alloc] initWithCertificateDataPEM:badPEM]; + XCTAssertNil(sut); +} + +- (void)testInitWithMultipleCertsInPEM { + NSString *multiPEM = [self.testDataPEM1 stringByAppendingString:self.testDataPEM2]; + + SNTCertificate *sut = [[SNTCertificate alloc] initWithCertificateDataPEM:multiPEM]; + XCTAssertNotNil(sut); + XCTAssertEqualObjects(sut.commonName, @"Google Internet Authority G2"); +} + +- (void)testArrayOfCerts { + NSString *multiPEM = [self.testDataPEM1 stringByAppendingString:self.testDataPEM2]; + + NSArray *certs = [SNTCertificate certificatesFromPEM:multiPEM]; + + XCTAssertNotNil(certs); + XCTAssertEqual(certs.count, 2); + XCTAssertEqualObjects([certs[0] commonName], @"Google Internet Authority G2"); + XCTAssertEqualObjects([certs[1] commonName], @"www.apple.com"); +} + +- (void)testPlainInit { + XCTAssertThrows([[SNTCertificate alloc] init]); +} + +- (void)testEquals { + SNTCertificate *sut1 = [[SNTCertificate alloc] initWithCertificateDataPEM:self.testDataPEM1]; + SNTCertificate *sut2 = [[SNTCertificate alloc] initWithCertificateDataPEM:self.testDataPEM1]; + + XCTAssertEqualObjects(sut1, sut2); +} + +- (void)testDescription { + SNTCertificate *sut = [[SNTCertificate alloc] initWithCertificateDataPEM:self.testDataPEM1]; + + XCTAssertEqualObjects([sut description], @"/O=Google Inc/OU=(null)/CN=Google Internet Authority G2"); +} + +- (void)testSecureCoding { + XCTAssertTrue([SNTCertificate supportsSecureCoding]); + + SNTCertificate *sut = [[SNTCertificate alloc] initWithCertificateDataPEM:self.testDataPEM1]; + + NSMutableData *encodedObject = [[NSMutableData alloc] init]; + NSKeyedArchiver *archive = [[NSKeyedArchiver alloc] initForWritingWithMutableData:encodedObject]; + [archive encodeObject:sut forKey:@"exampleCert"]; + [archive finishEncoding]; + NSKeyedUnarchiver *unarchive = [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedObject]; + SNTCertificate *newCert = [unarchive decodeObjectForKey:@"exampleCert"]; + + XCTAssertNotNil(newCert); + XCTAssertEqualObjects(newCert, sut); + XCTAssertEqualObjects(newCert.SHA1, sut.SHA1); +} + +- (void)testCachingAccessors { + SNTCertificate *sut = [[SNTCertificate alloc] initWithCertificateDataPEM:self.testDataPEM1]; + id sutMock = [OCMockObject partialMockForObject:sut]; + + // Access each of the properties to get them cached + (void)sut.orgName; + (void)sut.issuerCommonName; + (void)sut.validFrom; + + // Now break some of the properties + [[[sutMock stub] andReturn:nil] x509ValueForLabel:OCMOCK_ANY fromDictionary:OCMOCK_ANY]; + [[[sutMock stub] andReturn:nil] dateForX509Key:OCMOCK_ANY]; + + XCTAssertEqualObjects(sut.orgName, @"Google Inc"); + XCTAssertEqualObjects(sut.issuerCommonName, @"GeoTrust Global CA"); + XCTAssertEqualObjects(sut.validFrom, [NSDate dateWithString:@"2013-04-05 15:15:55 +0000"]); +} + +@end diff --git a/Tests/LogicTests/SNTCodesignCheckerTest.m b/Tests/LogicTests/SNTCodesignCheckerTest.m new file mode 100644 index 00000000..2b5331b6 --- /dev/null +++ b/Tests/LogicTests/SNTCodesignCheckerTest.m @@ -0,0 +1,103 @@ +/// 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 + +#import "SNTCertificate.h" +#import "SNTCodesignChecker.h" + +/** + Tests for @c SNTCodesignChecker + + Most of these tests rely on some facts about @c launchd: + + * launchd is in /sbin + * launchd is PID 1 + * launchd is signed + * launchd's leaf cert has a CN of "Software Signing" + * launchd's leaf cert has an OU of "Apple Software" + * launchd's leaf cert has an ON of "Apple Inc." + + These facts are pretty stable, so shouldn't be a problem. +**/ +@interface SNTCodesignCheckerTest : XCTestCase +@end + +@implementation SNTCodesignCheckerTest + +- (void)testInitWithBinaryPath { + SNTCodesignChecker *sut = [[SNTCodesignChecker alloc] initWithBinaryPath:@"/sbin/launchd"]; + XCTAssertNotNil(sut); +} + +- (void)testInitWithInvalidBinaryPath { + SNTCodesignChecker *sut = + [[SNTCodesignChecker alloc] initWithBinaryPath:@"/tmp/this/file/doesnt/exist"]; + XCTAssertNil(sut); +} + +- (void)testInitWithPID { + SNTCodesignChecker *sut = [[SNTCodesignChecker alloc] initWithPID:1]; + XCTAssertNotNil(sut); +} + +- (void)testInitWithInvalidPID { + SNTCodesignChecker *sut = [[SNTCodesignChecker alloc] initWithPID:999999999]; + XCTAssertNil(sut); +} + +- (void)testInitWithSelf { + // n.b: 'self' in this case is xctest, which should always be signed. + SNTCodesignChecker *sut = [[SNTCodesignChecker alloc] initWithSelf]; + XCTAssertNotNil(sut); +} + +- (void)testPlainInit { + XCTAssertThrows([[SNTCodesignChecker alloc] init]); +} + +- (void)testDescription { + SNTCodesignChecker *sut = [[SNTCodesignChecker alloc] initWithPID:1]; + XCTAssertEqualObjects([sut description], + @"In-memory binary, signed by Apple Inc., located at: /sbin/launchd"); +} + +- (void)testLeafCertificate { + SNTCodesignChecker *sut = [[SNTCodesignChecker alloc] initWithPID:1]; + XCTAssertNotNil(sut.leafCertificate); +} + +- (void)testBinaryPath { + SNTCodesignChecker *sut = [[SNTCodesignChecker alloc] initWithPID:1]; + XCTAssertEqualObjects(sut.binaryPath, @"/sbin/launchd"); +} + +- (void)testSigningInformationMatches { + SNTCodesignChecker *sut1 = [[SNTCodesignChecker alloc] initWithBinaryPath:@"/sbin/launchd"]; + SNTCodesignChecker *sut2 = [[SNTCodesignChecker alloc] initWithPID:1]; + XCTAssertTrue([sut1 signingInformationMatches:sut2]); +} + +- (void)testCodeRef { + SNTCodesignChecker *sut = [[SNTCodesignChecker alloc] initWithSelf]; + XCTAssertNotNil((id)sut.codeRef); +} + +- (void)testSigningInformation { + SNTCodesignChecker *sut = [[SNTCodesignChecker alloc] initWithPID:1]; + XCTAssertNotNil(sut.signingInformation); + XCTAssertEqualObjects(sut.signingInformation[@"source"], @"embedded"); +} + +@end \ No newline at end of file diff --git a/Tests/LogicTests/SNTDERDecoderTest.m b/Tests/LogicTests/SNTDERDecoderTest.m new file mode 100644 index 00000000..735bf65e --- /dev/null +++ b/Tests/LogicTests/SNTDERDecoderTest.m @@ -0,0 +1,50 @@ +/// 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 + +#import "SNTDERDecoder.h" + +@interface SNTDERDecoder (Testing) ++ (NSString *)decodeOIDWithBytes:(unsigned char *)bytes length:(NSUInteger)length; +@end + +@interface SNTDERDecoderTest : XCTestCase +@end + +@implementation SNTDERDecoderTest + +- (void)setUp { + [super setUp]; +} + +- (void)tearDown { + [super tearDown]; +} + +- (void)testOIDDecoding { + unsigned char oidBytes1[] = {0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x14}; + NSString *oidStr = [SNTDERDecoder decodeOIDWithBytes:oidBytes1 length:sizeof(oidBytes1)]; + XCTAssertEqualObjects(oidStr, @"1.3.6.1.4.1.311.21.20"); + + unsigned char oidBytes2[] = {0x2b, 0x06, 0x01, 0x04, 0x01, 0xAB, 0x0E, 0x01, 0x05, 0x2F }; + oidStr = [SNTDERDecoder decodeOIDWithBytes:oidBytes2 length:sizeof(oidBytes2)]; + XCTAssertEqualObjects(oidStr, @"1.3.6.1.4.1.5518.1.5.47"); + + unsigned char oidBytes3[] = {0x56, 0x04, 0x0A}; + oidStr = [SNTDERDecoder decodeOIDWithBytes:oidBytes3 length:sizeof(oidBytes3)]; + XCTAssertEqualObjects(oidStr, @"2.6.4.10"); +} + +@end diff --git a/Tests/LogicTests/SNTExecutionControllerTest.m b/Tests/LogicTests/SNTExecutionControllerTest.m new file mode 100644 index 00000000..66b48b0a --- /dev/null +++ b/Tests/LogicTests/SNTExecutionControllerTest.m @@ -0,0 +1,189 @@ +/// 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 +#import + +#import "SNTExecutionController.h" + +#import "SNTBinaryInfo.h" +#import "SNTCertificate.h" +#import "SNTCodesignChecker.h" +#import "SNTDriverManager.h" +#import "SNTEventTable.h" +#import "SNTNotificationMessage.h" +#import "SNTRule.h" +#import "SNTRuleTable.h" + +@interface SNTExecutionController (Testing) +- (BOOL)fileIsInScope:(NSString *)path; +@end + +@interface SNTExecutionControllerTest : XCTestCase +@property id mockBinaryInfo; +@property id mockCodesignChecker; +@property id mockDriverManager; +@property id mockRuleDatabase; +@property id mockEventDatabase; + +@property SNTExecutionController *sut; +@end + +@implementation SNTExecutionControllerTest + +- (void)setUp { + [super setUp]; + + fclose(stdout); + + self.mockBinaryInfo = [OCMockObject niceMockForClass:[SNTBinaryInfo class]]; + self.mockCodesignChecker = [OCMockObject niceMockForClass:[SNTCodesignChecker class]]; + self.mockDriverManager = [OCMockObject niceMockForClass:[SNTDriverManager class]]; + self.mockRuleDatabase = [OCMockObject niceMockForClass:[SNTRuleTable class]]; + self.mockEventDatabase = [OCMockObject niceMockForClass:[SNTEventTable class]]; + + self.sut = [[SNTExecutionController alloc] initWithDriverManager:self.mockDriverManager + ruleTable:self.mockRuleDatabase + eventTable:self.mockEventDatabase + operatingMode:CLIENTMODE_MONITOR + notifierConnection:nil]; +} + +- (void)tearDown { + [self.mockBinaryInfo verify]; + [self.mockCodesignChecker verify]; + [self.mockDriverManager verify]; + [self.mockRuleDatabase verify]; + [self.mockEventDatabase verify]; + + [self.mockBinaryInfo stopMocking]; + [self.mockCodesignChecker stopMocking]; + [self.mockDriverManager stopMocking]; + [self.mockRuleDatabase stopMocking]; + [self.mockEventDatabase stopMocking]; + + [super tearDown]; +} + +- (void)testBinaryWhitelistRule { + id mockSut = [OCMockObject partialMockForObject:self.sut]; + [[[mockSut stub] andReturnValue:OCMOCK_VALUE(YES)] fileIsInScope:OCMOCK_ANY]; + + SNTRule *rule = [[SNTRule alloc] init]; + rule.state = RULESTATE_WHITELIST; + [[[self.mockRuleDatabase stub] andReturn:rule] binaryRuleForSHA1:@"a"]; + + [[self.mockDriverManager expect] postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW + forVnodeID:1234]; + + [self.sut validateBinaryWithSHA1:@"a" + path:@"/a/file" + userName:@"nobody" + pid:@(12) + vnodeId:1234]; +} + +- (void)testBinaryBlacklistRule { + id mockSut = [OCMockObject partialMockForObject:self.sut]; + [[[mockSut stub] andReturnValue:OCMOCK_VALUE(YES)] fileIsInScope:OCMOCK_ANY]; + + SNTRule *rule = [[SNTRule alloc] init]; + rule.state = RULESTATE_BLACKLIST; + [[[self.mockRuleDatabase stub] andReturn:rule] binaryRuleForSHA1:@"a"]; + + [[self.mockDriverManager expect] postToKernelAction:ACTION_RESPOND_CHECKBW_DENY + forVnodeID:1234]; + + [self.sut validateBinaryWithSHA1:@"a" + path:@"/a/file" + userName:@"nobody" + pid:@(12) + vnodeId:1234]; +} + +- (void)testCertificateWhitelistRule { + id mockSut = [OCMockObject partialMockForObject:self.sut]; + [[[mockSut stub] andReturnValue:OCMOCK_VALUE(YES)] fileIsInScope:OCMOCK_ANY]; + + id cert = [OCMockObject niceMockForClass:[SNTCertificate class]]; + [[[self.mockCodesignChecker stub] andReturn:self.mockCodesignChecker] alloc]; + (void)[[[self.mockCodesignChecker stub] andReturn:self.mockCodesignChecker] + initWithBinaryPath:[OCMArg any]]; + [[[self.mockCodesignChecker stub] andReturn:cert] leafCertificate]; + [[[cert stub] andReturn:@"a"] SHA1]; + + SNTRule *rule = [[SNTRule alloc] init]; + rule.state = RULESTATE_WHITELIST; + [[[self.mockRuleDatabase stub] andReturn:rule] certificateRuleForSHA1:@"a"]; + + [[self.mockDriverManager expect] postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW + forVnodeID:1234]; + + [self.sut validateBinaryWithSHA1:@"a" + path:@"/a/file" + userName:@"nobody" + pid:@(12) + vnodeId:1234]; +} + +- (void)testCertificateBlacklistRule { + id mockSut = [OCMockObject partialMockForObject:self.sut]; + [[[mockSut stub] andReturnValue:OCMOCK_VALUE(YES)] fileIsInScope:OCMOCK_ANY]; + + id cert = [OCMockObject niceMockForClass:[SNTCertificate class]]; + [[[self.mockCodesignChecker stub] andReturn:self.mockCodesignChecker] alloc]; + (void)[[[self.mockCodesignChecker stub] andReturn:self.mockCodesignChecker] + initWithBinaryPath:[OCMArg any]]; + [[[self.mockCodesignChecker stub] andReturn:cert] leafCertificate]; + [[[cert stub] andReturn:@"a"] SHA1]; + + SNTRule *rule = [[SNTRule alloc] init]; + rule.state = RULESTATE_BLACKLIST; + [[[self.mockRuleDatabase stub] andReturn:rule] certificateRuleForSHA1:@"a"]; + + [[self.mockDriverManager expect] postToKernelAction:ACTION_RESPOND_CHECKBW_DENY + forVnodeID:1234]; + + [self.sut validateBinaryWithSHA1:@"a" + path:@"/a/file" + userName:@"nobody" + pid:@(12) + vnodeId:1234]; +} + +- (void)testDefaultDecision { + id mockSut = [OCMockObject partialMockForObject:self.sut]; + [[[mockSut stub] andReturnValue:OCMOCK_VALUE(YES)] fileIsInScope:OCMOCK_ANY]; + + [self.sut setOperatingMode:CLIENTMODE_MONITOR]; + [[self.mockDriverManager expect] postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW + forVnodeID:1234]; + [self.sut validateBinaryWithSHA1:@"a" + path:@"/a/file" + userName:@"nobody" + pid:@(12) + vnodeId:1234]; + + [self.sut setOperatingMode:CLIENTMODE_LOCKDOWN]; + [[self.mockDriverManager expect] postToKernelAction:ACTION_RESPOND_CHECKBW_DENY + forVnodeID:1234]; + [self.sut validateBinaryWithSHA1:@"a" + path:@"/a/file" + userName:@"nobody" + pid:@(12) + vnodeId:1234]; + +} + +@end diff --git a/Tests/LogicTests/SNTXPCConnectionTest.m b/Tests/LogicTests/SNTXPCConnectionTest.m new file mode 100644 index 00000000..afd1e3b3 --- /dev/null +++ b/Tests/LogicTests/SNTXPCConnectionTest.m @@ -0,0 +1,149 @@ +/// 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 +#import + +#import "SNTCodesignChecker.h" +#import "SNTXPCConnection.h" + +@interface SNTXPCConnection (Testing) +- (void)isConnectionValidWithBlock:(void (^)(BOOL))block; +- (void)invokeAcceptedHandler; +- (void)invokeRejectedHandler; +- (void)invokeInvalidationHandler; +@property NSXPCConnection *currentConnection; +@property NSXPCInterface *validatorInterface; +@end + +@interface SNTXPCConnectionTest : XCTestCase +@property id mockListener; +@property id mockConnection; +@end + +@implementation SNTXPCConnectionTest + +- (void)setUp { + [super setUp]; + self.mockListener = [OCMockObject niceMockForClass:[NSXPCListener class]]; + [[[self.mockListener stub] andReturn:self.mockListener] alloc]; + + self.mockConnection = [OCMockObject niceMockForClass:[NSXPCConnection class]]; + [[[self.mockConnection stub] andReturn:self.mockConnection] alloc]; +} + +- (void)tearDown { + [super tearDown]; +} + +- (void)testPlainInit { + XCTAssertThrows([[SNTXPCConnection alloc] init]); +} + +- (void)testInitClient { + (void)[[[self.mockConnection stub] andReturn:self.mockConnection] + initWithMachServiceName:@"TestClient" options:NSXPCConnectionPrivileged]; + + SNTXPCConnection *sut = [[SNTXPCConnection alloc] initClientWithName:@"TestClient" + options:NSXPCConnectionPrivileged]; + XCTAssertNotNil(sut); + + [self.mockConnection verify]; +} + +- (void)testInitServer { + (void)[[[self.mockListener stub] andReturn:self.mockListener] + initWithMachServiceName:@"TestServer"]; + + SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"]; + XCTAssertNotNil(sut); + + [self.mockListener verify]; +} + + +- (void)testResume { + (void)[[[self.mockListener stub] andReturn:self.mockListener] initWithMachServiceName:OCMOCK_ANY]; + SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"]; + + [(NSXPCListener *)[self.mockListener expect] setDelegate:sut]; + [(NSXPCListener *)[self.mockListener expect] resume]; + + [sut resume]; + + [self.mockListener verify]; +} + +- (void)testListenerShouldAcceptNewConnection { + (void)[[[self.mockListener stub] andReturn:self.mockListener] initWithMachServiceName:OCMOCK_ANY]; + SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"]; + + [[self.mockConnection expect] setExportedObject:sut]; + [[self.mockConnection expect] setExportedInterface:OCMOCK_ANY]; + [[self.mockConnection expect] setInvalidationHandler:OCMOCK_ANY]; + [[self.mockConnection expect] setInterruptionHandler:OCMOCK_ANY]; + [(NSXPCConnection *)[self.mockConnection expect] resume]; + + XCTAssertTrue([sut listener:self.mockListener shouldAcceptNewConnection:self.mockConnection]); + + [self.mockConnection verify]; +} + +- (void)testIsConnectionValidFalse { + (void)[[[self.mockListener stub] andReturn:self.mockListener] initWithMachServiceName:OCMOCK_ANY]; + SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"]; + + [sut setCurrentConnection:self.mockConnection]; + + [[[self.mockConnection stub] andReturnValue:@(1)] processIdentifier]; + [[self.mockConnection expect] invalidate]; + + id mockCodesignChecker = [OCMockObject niceMockForClass:[SNTCodesignChecker class]]; + [[[mockCodesignChecker stub] andReturn:mockCodesignChecker] alloc]; + [[[mockCodesignChecker stub] andReturn:NO] signingInformationMatches:OCMOCK_ANY]; + + [sut isConnectionValidWithBlock:^(BOOL input) { + XCTAssertFalse(input); + }]; + + XCTAssertNil(sut.currentConnection); + + [self.mockConnection verify]; + + [mockCodesignChecker stopMocking]; +} + +- (void)testIsConnectionValidTrue { + (void)[[[self.mockListener stub] andReturn:self.mockListener] initWithMachServiceName:OCMOCK_ANY]; + SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"]; + + [sut setCurrentConnection:self.mockConnection]; + + pid_t mypid = [[NSProcessInfo processInfo] processIdentifier]; + [[[self.mockConnection stub] andReturnValue:@(mypid)] processIdentifier]; + + [(NSXPCConnection *)[self.mockConnection expect] suspend]; + [(NSXPCConnection *)[self.mockConnection expect] setRemoteObjectInterface:OCMOCK_ANY]; + [(NSXPCConnection *)[self.mockConnection expect] setExportedInterface:OCMOCK_ANY]; + [(NSXPCConnection *)[self.mockConnection expect] setExportedObject:OCMOCK_ANY]; + [(NSXPCConnection *)[self.mockConnection expect] resume]; + + [sut isConnectionValidWithBlock:^(BOOL input) { + XCTAssertTrue(input); + }]; + + [self.mockConnection verify]; +} + +@end \ No newline at end of file