From 07988686aecf4008c6dfd354f2bd7baa6f3cf372 Mon Sep 17 00:00:00 2001 From: Russell Hancox Date: Thu, 20 Nov 2014 16:23:13 -0500 Subject: [PATCH] Initial commit --- .gitignore | 8 + Conf/com.google.santa.asl.conf | 2 + Conf/com.google.santad.plist | 29 + Conf/com.google.santagui.plist | 18 + Conf/com.google.santasync.plist | 19 + LICENSE | 202 ++ Podfile | 12 + README.md | 157 ++ Rakefile | 210 ++ Santa.xcodeproj/project.pbxproj | 1704 +++++++++++++++++ .../xcshareddata/xcschemes/All.xcscheme | 69 + .../xcschemes/KernelTests.xcscheme | 86 + .../xcshareddata/xcschemes/Santa.xcscheme | 86 + .../xcschemes/santa-driver.xcscheme | 59 + .../xcshareddata/xcschemes/santactl.xcscheme | 86 + .../xcshareddata/xcschemes/santad.xcscheme | 86 + Source/SantaGUI/Resources/AboutWindow.xib | 73 + .../AppIcon.appiconset/Contents.json | 65 + .../AppIcon.appiconset/santa-hat-icon-128.png | Bin 0 -> 9277 bytes .../AppIcon.appiconset/santa-hat-icon-16.png | Bin 0 -> 3896 bytes .../AppIcon.appiconset/santa-hat-icon-32.png | Bin 0 -> 4757 bytes .../AppIcon.appiconset/santa-hat-icon-512.png | Bin 0 -> 36494 bytes .../AppIcon.appiconset/santa-hat-icon-64.png | Bin 0 -> 5430 bytes Source/SantaGUI/Resources/MessageWindow.xib | 202 ++ Source/SantaGUI/Resources/Santa-Info.plist | 32 + Source/SantaGUI/Resources/Santa-Prefix.pch | 3 + Source/SantaGUI/SNTAboutWindowController.h | 17 + Source/SantaGUI/SNTAboutWindowController.m | 23 + Source/SantaGUI/SNTAppDelegate.h | 17 + Source/SantaGUI/SNTAppDelegate.m | 83 + Source/SantaGUI/SNTMessageWindow.h | 24 + Source/SantaGUI/SNTMessageWindow.m | 53 + Source/SantaGUI/SNTMessageWindowController.h | 45 + Source/SantaGUI/SNTMessageWindowController.m | 118 ++ Source/SantaGUI/SNTNotificationManager.h | 22 + Source/SantaGUI/SNTNotificationManager.m | 81 + Source/SantaGUI/main.m | 25 + Source/common/SNTBinaryInfo.h | 73 + Source/common/SNTBinaryInfo.m | 291 +++ Source/common/SNTCertificate.h | 67 + Source/common/SNTCertificate.m | 336 ++++ Source/common/SNTCodesignChecker.h | 55 + Source/common/SNTCodesignChecker.m | 190 ++ Source/common/SNTCodesignChecker.m.old | 213 +++ Source/common/SNTCommonEnums.h | 66 + Source/common/SNTConfigurator.h | 61 + Source/common/SNTConfigurator.m | 151 ++ Source/common/SNTDropRootPrivs.h | 17 + Source/common/SNTDropRootPrivs.m | 30 + Source/common/SNTKernelCommon.h | 71 + Source/common/SNTLogging.h | 53 + Source/common/SNTLogging.m | 65 + Source/common/SNTNotificationMessage.h | 37 + Source/common/SNTNotificationMessage.m | 55 + Source/common/SNTRule.h | 38 + Source/common/SNTRule.m | 59 + Source/common/SNTStoredEvent.h | 40 + Source/common/SNTStoredEvent.m | 102 + Source/common/SNTSystemInfo.h | 28 + Source/common/SNTSystemInfo.m | 73 + Source/common/SNTXPCConnection.h | 87 + Source/common/SNTXPCConnection.m | 197 ++ Source/common/SNTXPCControlInterface.h | 52 + Source/common/SNTXPCControlInterface.m | 42 + Source/common/SNTXPCNotifierInterface.h | 30 + Source/common/SNTXPCNotifierInterface.m | 28 + .../Resources/santa-driver-Info.plist | 53 + Source/santa-driver/SantaDecisionManager.cc | 505 +++++ Source/santa-driver/SantaDecisionManager.h | 156 ++ Source/santa-driver/SantaDriver.cc | 37 + Source/santa-driver/SantaDriver.h | 33 + Source/santa-driver/SantaDriverClient.cc | 308 +++ Source/santa-driver/SantaDriverClient.h | 112 ++ Source/santa-driver/SantaMessage.cc | 31 + Source/santa-driver/SantaMessage.h | 42 + Source/santactl/Resources/santactl-Info.plist | 20 + Source/santactl/Resources/santactl-Prefix.pch | 3 + Source/santactl/SNTCommandController.h | 76 + Source/santactl/SNTCommandController.m | 132 ++ .../binaryinfo/SNTCommandBinaryInfo.m | 107 ++ .../flushcache/SNTCommandFlushCache.m | 53 + Source/santactl/main.m | 80 + Source/santactl/status/SNTCommandStatus.m | 114 ++ .../sync/SNTAuthenticatingURLSession.h | 54 + .../sync/SNTAuthenticatingURLSession.m | 261 +++ Source/santactl/sync/SNTCommandSync.m | 219 +++ .../santactl/sync/SNTCommandSyncEventUpload.h | 31 + .../santactl/sync/SNTCommandSyncEventUpload.m | 156 ++ .../santactl/sync/SNTCommandSyncLogUpload.h | 25 + .../santactl/sync/SNTCommandSyncLogUpload.m | 88 + .../santactl/sync/SNTCommandSyncPostflight.h | 25 + .../santactl/sync/SNTCommandSyncPostflight.m | 45 + .../santactl/sync/SNTCommandSyncPreflight.h | 25 + .../santactl/sync/SNTCommandSyncPreflight.m | 72 + .../sync/SNTCommandSyncRuleDownload.h | 25 + .../sync/SNTCommandSyncRuleDownload.m | 113 ++ Source/santactl/sync/SNTCommandSyncStatus.h | 32 + Source/santactl/sync/SNTCommandSyncStatus.m | 18 + Source/santactl/sync/SNTDERDecoder.h | 15 + Source/santactl/sync/SNTDERDecoder.m | 201 ++ Source/santad/Resources/santad-Info.plist | 18 + Source/santad/Resources/santad-Prefix.pch | 3 + Source/santad/SNTApplication.h | 21 + Source/santad/SNTApplication.m | 158 ++ Source/santad/SNTDaemonControlController.h | 26 + Source/santad/SNTDaemonControlController.m | 90 + Source/santad/SNTDatabaseController.h | 32 + Source/santad/SNTDatabaseController.m | 82 + Source/santad/SNTDatabaseTable.h | 35 + Source/santad/SNTDatabaseTable.m | 69 + Source/santad/SNTDriverManager.h | 36 + Source/santad/SNTDriverManager.m | 188 ++ Source/santad/SNTEventTable.h | 43 + Source/santad/SNTEventTable.m | 213 +++ Source/santad/SNTExecutionController.h | 57 + Source/santad/SNTExecutionController.m | 325 ++++ Source/santad/SNTRuleTable.h | 46 + Source/santad/SNTRuleTable.m | 146 ++ Source/santad/main.m | 40 + Tests/KernelTests/main.m | 421 ++++ Tests/LogicTests/Resources/GIAG2.crt | Bin 0 -> 1032 bytes Tests/LogicTests/Resources/GIAG2.pem | 24 + Tests/LogicTests/Resources/Tests-Info.plist | 22 + Tests/LogicTests/Resources/Tests-Prefix.pch | 3 + Tests/LogicTests/Resources/apple.pem | 34 + Tests/LogicTests/Resources/tubitak.crt | Bin 0 -> 1307 bytes Tests/LogicTests/SNTBinaryInfoTest.m | 102 + Tests/LogicTests/SNTCertificateTest.m | 227 +++ Tests/LogicTests/SNTCodesignCheckerTest.m | 103 + Tests/LogicTests/SNTDERDecoderTest.m | 50 + Tests/LogicTests/SNTExecutionControllerTest.m | 189 ++ Tests/LogicTests/SNTXPCConnectionTest.m | 149 ++ 132 files changed, 12542 insertions(+) create mode 100644 .gitignore create mode 100644 Conf/com.google.santa.asl.conf create mode 100644 Conf/com.google.santad.plist create mode 100644 Conf/com.google.santagui.plist create mode 100644 Conf/com.google.santasync.plist create mode 100644 LICENSE create mode 100644 Podfile create mode 100644 README.md create mode 100644 Rakefile create mode 100644 Santa.xcodeproj/project.pbxproj create mode 100644 Santa.xcodeproj/xcshareddata/xcschemes/All.xcscheme create mode 100644 Santa.xcodeproj/xcshareddata/xcschemes/KernelTests.xcscheme create mode 100644 Santa.xcodeproj/xcshareddata/xcschemes/Santa.xcscheme create mode 100644 Santa.xcodeproj/xcshareddata/xcschemes/santa-driver.xcscheme create mode 100644 Santa.xcodeproj/xcshareddata/xcschemes/santactl.xcscheme create mode 100644 Santa.xcodeproj/xcshareddata/xcschemes/santad.xcscheme create mode 100644 Source/SantaGUI/Resources/AboutWindow.xib create mode 100644 Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png create mode 100644 Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-16.png create mode 100644 Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-32.png create mode 100644 Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-512.png create mode 100644 Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-64.png create mode 100644 Source/SantaGUI/Resources/MessageWindow.xib create mode 100644 Source/SantaGUI/Resources/Santa-Info.plist create mode 100644 Source/SantaGUI/Resources/Santa-Prefix.pch create mode 100644 Source/SantaGUI/SNTAboutWindowController.h create mode 100644 Source/SantaGUI/SNTAboutWindowController.m create mode 100644 Source/SantaGUI/SNTAppDelegate.h create mode 100644 Source/SantaGUI/SNTAppDelegate.m create mode 100644 Source/SantaGUI/SNTMessageWindow.h create mode 100644 Source/SantaGUI/SNTMessageWindow.m create mode 100644 Source/SantaGUI/SNTMessageWindowController.h create mode 100644 Source/SantaGUI/SNTMessageWindowController.m create mode 100644 Source/SantaGUI/SNTNotificationManager.h create mode 100644 Source/SantaGUI/SNTNotificationManager.m create mode 100644 Source/SantaGUI/main.m create mode 100644 Source/common/SNTBinaryInfo.h create mode 100644 Source/common/SNTBinaryInfo.m create mode 100644 Source/common/SNTCertificate.h create mode 100644 Source/common/SNTCertificate.m create mode 100644 Source/common/SNTCodesignChecker.h create mode 100644 Source/common/SNTCodesignChecker.m create mode 100644 Source/common/SNTCodesignChecker.m.old create mode 100644 Source/common/SNTCommonEnums.h create mode 100644 Source/common/SNTConfigurator.h create mode 100644 Source/common/SNTConfigurator.m create mode 100644 Source/common/SNTDropRootPrivs.h create mode 100644 Source/common/SNTDropRootPrivs.m create mode 100644 Source/common/SNTKernelCommon.h create mode 100644 Source/common/SNTLogging.h create mode 100644 Source/common/SNTLogging.m create mode 100644 Source/common/SNTNotificationMessage.h create mode 100644 Source/common/SNTNotificationMessage.m create mode 100644 Source/common/SNTRule.h create mode 100644 Source/common/SNTRule.m create mode 100644 Source/common/SNTStoredEvent.h create mode 100644 Source/common/SNTStoredEvent.m create mode 100644 Source/common/SNTSystemInfo.h create mode 100644 Source/common/SNTSystemInfo.m create mode 100644 Source/common/SNTXPCConnection.h create mode 100644 Source/common/SNTXPCConnection.m create mode 100644 Source/common/SNTXPCControlInterface.h create mode 100644 Source/common/SNTXPCControlInterface.m create mode 100644 Source/common/SNTXPCNotifierInterface.h create mode 100644 Source/common/SNTXPCNotifierInterface.m create mode 100644 Source/santa-driver/Resources/santa-driver-Info.plist create mode 100644 Source/santa-driver/SantaDecisionManager.cc create mode 100644 Source/santa-driver/SantaDecisionManager.h create mode 100644 Source/santa-driver/SantaDriver.cc create mode 100644 Source/santa-driver/SantaDriver.h create mode 100644 Source/santa-driver/SantaDriverClient.cc create mode 100644 Source/santa-driver/SantaDriverClient.h create mode 100644 Source/santa-driver/SantaMessage.cc create mode 100644 Source/santa-driver/SantaMessage.h create mode 100644 Source/santactl/Resources/santactl-Info.plist create mode 100644 Source/santactl/Resources/santactl-Prefix.pch create mode 100644 Source/santactl/SNTCommandController.h create mode 100644 Source/santactl/SNTCommandController.m create mode 100644 Source/santactl/binaryinfo/SNTCommandBinaryInfo.m create mode 100644 Source/santactl/flushcache/SNTCommandFlushCache.m create mode 100644 Source/santactl/main.m create mode 100644 Source/santactl/status/SNTCommandStatus.m create mode 100644 Source/santactl/sync/SNTAuthenticatingURLSession.h create mode 100644 Source/santactl/sync/SNTAuthenticatingURLSession.m create mode 100644 Source/santactl/sync/SNTCommandSync.m create mode 100644 Source/santactl/sync/SNTCommandSyncEventUpload.h create mode 100644 Source/santactl/sync/SNTCommandSyncEventUpload.m create mode 100644 Source/santactl/sync/SNTCommandSyncLogUpload.h create mode 100644 Source/santactl/sync/SNTCommandSyncLogUpload.m create mode 100644 Source/santactl/sync/SNTCommandSyncPostflight.h create mode 100644 Source/santactl/sync/SNTCommandSyncPostflight.m create mode 100644 Source/santactl/sync/SNTCommandSyncPreflight.h create mode 100644 Source/santactl/sync/SNTCommandSyncPreflight.m create mode 100644 Source/santactl/sync/SNTCommandSyncRuleDownload.h create mode 100644 Source/santactl/sync/SNTCommandSyncRuleDownload.m create mode 100644 Source/santactl/sync/SNTCommandSyncStatus.h create mode 100644 Source/santactl/sync/SNTCommandSyncStatus.m create mode 100644 Source/santactl/sync/SNTDERDecoder.h create mode 100644 Source/santactl/sync/SNTDERDecoder.m create mode 100644 Source/santad/Resources/santad-Info.plist create mode 100644 Source/santad/Resources/santad-Prefix.pch create mode 100644 Source/santad/SNTApplication.h create mode 100644 Source/santad/SNTApplication.m create mode 100644 Source/santad/SNTDaemonControlController.h create mode 100644 Source/santad/SNTDaemonControlController.m create mode 100644 Source/santad/SNTDatabaseController.h create mode 100644 Source/santad/SNTDatabaseController.m create mode 100644 Source/santad/SNTDatabaseTable.h create mode 100644 Source/santad/SNTDatabaseTable.m create mode 100644 Source/santad/SNTDriverManager.h create mode 100644 Source/santad/SNTDriverManager.m create mode 100644 Source/santad/SNTEventTable.h create mode 100644 Source/santad/SNTEventTable.m create mode 100644 Source/santad/SNTExecutionController.h create mode 100644 Source/santad/SNTExecutionController.m create mode 100644 Source/santad/SNTRuleTable.h create mode 100644 Source/santad/SNTRuleTable.m create mode 100644 Source/santad/main.m create mode 100644 Tests/KernelTests/main.m create mode 100644 Tests/LogicTests/Resources/GIAG2.crt create mode 100644 Tests/LogicTests/Resources/GIAG2.pem create mode 100644 Tests/LogicTests/Resources/Tests-Info.plist create mode 100644 Tests/LogicTests/Resources/Tests-Prefix.pch create mode 100644 Tests/LogicTests/Resources/apple.pem create mode 100644 Tests/LogicTests/Resources/tubitak.crt create mode 100644 Tests/LogicTests/SNTBinaryInfoTest.m create mode 100644 Tests/LogicTests/SNTCertificateTest.m create mode 100644 Tests/LogicTests/SNTCodesignCheckerTest.m create mode 100644 Tests/LogicTests/SNTDERDecoderTest.m create mode 100644 Tests/LogicTests/SNTExecutionControllerTest.m create mode 100644 Tests/LogicTests/SNTXPCConnectionTest.m 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 0000000000000000000000000000000000000000..3a1046cc98730019a662e3e7b09fea4f94448290 GIT binary patch literal 9277 zcmch6by!qUxAy=;3@Xx{;{bxRB2q&RAxKC|m*fD_HI#HWf&wB)cMK(sq|!YC($ZZ6 z0~g=-z3;u>{qEoQ>}Q{|&pPY3_FikRXYIAmj!;vPe?Uk}2mk;cC@RRlzIz7ztps>? z_b#aEAOJupWg{b_rYIxBtmf?a*2dlv08n_4nd+gbF-sXX+#=L6LJDSPJ)!jmF^`EV zPyp2<6a&<-Lxb7eWpU-mNjS7EG~2P{!@%Y+@_X-t%%(v{#A-bq>awu4`Di=PewpC$ zLI$Q+Z*g{`etOnwA2HnoP`eHScDm|f1w6~qCEwfbFd!ux>^{e)CdMMt#cE%A`xYpF zA&Wh6-6EczUZ~JhbGFA5V9|`?YExr#nY#5)01L@L@BoCc$>(BzY*cI5$r?4^X#{S8 z11BjQ0-^i@lsTX;rQntB3j4Ey^(W2!AJW)Xb7=L3Xudtmif&e0$Y5Q{jRxizd|>@; zmFtUd*`leK0a!bKJU)K$Ng2RLo^JeOgDuBCLI6fivlnrM4IoJF7XIc;6m=rLB4qjM}=?7}c9-5Ufz33$~ZPO{Umwb~S%s*%=Q>{OLOZ+tOGU%(>WL3T{d#Qvw z=sC&9818OulQ362U8<^fc`_ zZKb~~sU*QJWjoD>RW*4!%_Gg2C!5`v#sB3^{&fSkdWU3(MqqBGc8=UqZKrsMA}r%e zidnc>hFOH!`(?rJ>a5=q0~6~>Rf{AyE5}&Bvd~J8fAtz`sC`u{GG{r*#~?41cQu~0 z`Td0HPs<6&lGRdw?eQA@8tq!mz-zotK9h^5uW7zUCq*|$#~nXgZya73Mm?{vF}nT6 zk#foD!0DR;PI;Sxl@eF1tmQaKKABkzFIJu4vt_e2wVk!Sn&c^o87>(1$QsTxes;Th z9kx~36ye=~wmt3I65mRq&7f_irJ>cLO?%cONR#1^(VwxFvGJ_8*80u8I^dhNHyd?E zd@1RBKQ!O6)J92OJfu~Xcy)P= zE7bRMpz7IW*?Dd9ZTkKL?IX-{CQ(u!I|_3mbJ|R^Ommma9~CE*jB@6_$rbH+VUBzk zc?ez6XRD0JR7_Ni>3^j$GqIp)(llH}% zUOu@e9;F+VNjXnx%(cs&l9_ z7v0--5uzD}mGhL%D$T=ZL}%bDwkv*o@i5P@7NYYRSNF%w!H7wc?LSKmLx<5T`0PIH zRpW{Uiu977L=a_`-zI-r{1rgk2Ex70xIoWNtj6K(A2lry1)N?CccjsEd!I%dPY z8{R!XBR-31xO(zX>0yF=RBv)Y(gyADJ)`in3Y`R(Qwd2yPrjeCT44;a# zxA1m7%jJ`48nBt%(<1vKIhvsS*hTMc^M}|QB6 zgE5wLiw=`3`M_-QWn{{?t zwN3TknLMbK z(|_~tJ6E$SlY)OY{k2ruHN5?0tEtV(-`HPvSK%;WSUyDlYj0oi_LmPB8oTJG1+IxkvEZVa;APw zMIiH@+xRH^EN%vjXEF>ia7%o=^*xC`OV6kouu>XM7^3*}Q8?Sf??}2QQq{E5AL+|ml`rI1*65m8DU`Fwa9vVsf@P2`@ z#+dJ{d|@Y4<;*IUD8o!%OusE^IobIRBzzx%*$bUR(*dKffSyp`=yfED)1(Wq#6|u(_jpp_-B%BjMo-x<2w`gTGy0JP$s;5)jezyUQs- zP6~Q10Kh%6zZDCR@rfD$z~-~j)OFQ;r7U9Z2HTC3naC!7k zA^#~y*3!k?*~ZD$#?gWKuUu0zM>kh-F!*mr|Gxe;PPo&*J92ROmsWTB@p_s%@$&QV z@&0chSDUy0HN5`^_4ms^VgJzTpPh-_nMOp$+0xY2(OJ{c(H<&g?&$2m>~87o@^^&K zcm#N!it+wWrvDnO*k7q4GLCR3XG@pA5U8LS@Bc^ppZt2SEnOV#-ToQ@DkShPz<($H zPk`?K2Kev9e*wgJ|JwAw+4!Fm_z&vc3_=O-boozjfD&RyGKvELRBsh!r8GUUb`3%C z20CsCaoVy}i}%3lLtQ0{-WTrDkdjivM=%pnHu5jT$q_Zwy_yDxuR|2m13(Fiu;)CH z-9S20BsD2BUUj?ztkkj;fh8o}|UY2~K1koO|m$v=1#qc`V${i)V}~tG4K$ zqJQ};UhTJe3?ue$_ihCVSrj8B|8GhZ?DK@#%1?&9Gvb~sq9vgwnFe}GTU7#vb|I|A zaA!s$dR+RhAjP-<4Ceqxvd*0J3*h2lJ5D>I@S5agK1wYNZOYA^4lraUfw%=7e0^La zc|y@nq@4d#<*_Pzu0n9JLOnqX-NuFRfPZ~hCESAGwwf<^qXho}+4mmnK9!;pgAm#@^%NZB`)BF{NX5q0%H+%*iGd8d&4DRNEcqRqg!ZY!f6>#O7I#_2O@KI*U6)1x*&LqCX2siRv8OqU}mNXf^AJ{x11KeDq(RTsA+unYeaqt^|SYR z=t!M#uu!|81qAuR8Nc2SgQn|^rm1;5`UO@|s#mj)`)3=4LhbvXPU>Wb-WVoJh`R60 zlpkOo#_=L9u2$w8zQ4RSTPay6cP?>>Usz|})Jb5FK_7MV$Zu7D^;wO~uc)rJbG;0I zgJD#!V@eCAXnkojG<&nsy3OvpY^CVf!14dKNnA5WL;Z7j%ntr#h}BaZO(QwQn)%p=%XiLrROg3#Zj9_B&_z1 zFyQ!x+ibTk$!uvZPx2yh<}R>0g45cERMF`wk+>Cb{u#up9wK1Y9sa(eTIQBk0uYF0ZPQPqYP^6X-EH~?DysjAx*qfyqOABWHsK6XZL3p93UWt@;_gOXxZ)XTt_+iR@!53a$sRUo~`p<(0-zItZL{*^;ru`64NnWY5_3n|jO>EXFB|W1oKwV!Z}85lX}}7}IyM4+?JV)#zZC z>euhRXLbWRtjk5%f0u0qmQ|cQbdlM7a4fUg(FXeZ>T((pDkW88eHREQ zH%oKF?a!Vh#32cgO}=71+q6s`TuLu=gtujz<~mX?BjQPU@JockoQOp`gj?8ud)fkp z5ZT=WZSr|OwX0VU;(q%_EmW5&7Jb(Y4C+CMcniDAYGJ4C8sxOiG#RTTNp)-hf49Hs%l+bQr{sv&NReAH>Z!V0#m({! zcU_odNIdBQ-SZ#Ncgx)}d^}Q()UtI^-^2u-f~)4ZpI0AwN$=kO+GUvIIvXSSL@(H4 zcP5oEudf1KYWfwfoHm@R-Ev+1*UuvjF>fxVn{h_0Jg~}rXU9f2RC`wt1mAFv+h~U+ zS;$@_tqrD0fWUFT=OiA?sJu=Aq?%lY*vcfUK4yYbuqsUYeO7MmgX0N1eWb7cxG4jt zA=s3irTkiwJnn92L)+6wdv2$TRh2JSA5c zAFqmG`|V}M?xSy`D|}rl=j&|-&lSwHxEi&o8c)XN6KLAZW?bD$h!m;;Picg=)%+s_LM`Ru#zv|~XL}^12V-W^6 z(p#K!xLmgE?LwgyTs_Sx)(eiEjYoJ+_gk^9>H6AKR#9cf-j6`-H`nD-*%0KTV0oUM zQ!ZuTTb%BWsn|_$7PV;TM!nyhwx$(g4Ca(j_B_>h6OwTUF(H%c9@iOFU3y(fkQQV} zJ2-1bDfDgU9aLcfC&)jTNHEQIF{?IXx4HVe4^tvX%N1$K=hLB#=x*Xrs@7&a`JYh96d8t0U^~^LGQ-K#-6MBUm{EgiX4OKBv(aEjlu#C zv)}39?7HD5-iXaJzJEK}TBxC`N|IWJ&h`uGJL_lrSO{dD`vROEppw&Wa}Nwy#B=zL zlgm5FWP_ocY+MoGV=Ou3pcfb?HBJU(`I7KXY{Hu4r*d~0GzwG@y6^}I(2`r<%}0{L zX1?}K`n<@#@-W-|1kAWRiMM!3=`qXLq%u;Wz_>}Rdpy-B8BnSLCU~-Yo*So>782mo zRYTUpPezPk69f5@FXFXg_p&5vX@XH+KedOwTa`QS@|BaHFNQJgMiNfSU2%*IEZ3*3 zA0TXK`41<71KoBGS>&>DFY-bSbp%PRyQz$$!x?|-DFee;5gpuHN&YKE3cxjK#|0v(XQCs zYF(4I7{ZH^iR}1N6NU{O*8#?p)pgaooN0R`tGC1vwe%M@V&1LK_VZ#c ztbz~73A`kNCUbv-C@i9OG?2U5t#aK-!{&c9B2IJ6gGJ;D&AB-hQyTa!s!_r6q`yM5 z!{4dS2vw~_omIMQ3@zRr)l45eSORF@T#r zr^M3`_AULjFye{a=brZk;7s=Js*950iHZmmi|Nd+O_s^$tT2CC4Dm)=(o`_$tF8WiV$P*TE!CN6xDbF0b&mK88Uw( z+UGsT@%m4nbo{-AmMd>ad#JF>`lnxYcGHmSmH-(v``RmyUtu^0GNa=J0~JfL4eM6| zIA5=@abw8x%i9T;idiC&d85qV6MNCgo;x}sqx*o`#<`FOd)Vy!w=@k@)>|m8T$e7V z&~j!h#va+5U<(u^oHd}2iW4APf|?z0M0ho^8o*a;6y~W&9imi&?p)ImkNX{_Czh)UeHJYZwwPbG&FELjccMq%lgc0QI0%KL4G7;xU!1?%K~~ud@W)Io!=iG%Q!+|oC~tkJ z5^@q&DUZxi6>3%ppv$ut?p+B8_ixNj1>9BQJ(so_4WI zZ7rG36@4SVr9()XnYwpl{zXj!GNzUTs??}0+dKE>OOz`KlgtjbP!BrxGE@oMps!z0 zCLV6*hRd<8Vd$Up3aErc*F(2b?d|k4;e2QH?kQ~~B_#&*l%RmwpM9C8Nff8yR*06d zRSuu|hw*BZk*;x&Sm;Ocu_Y*zzwvK86GIwG(8w|V(ofjapO3U;*BL%~7#LlN`L`Ue zG88~eT2uiEyyTaiSze|vqqZr>VN;BZQEyWJ*g}iX<&G(ZDQG>2d4dk-^rq?pXE%+xt6Dr<`V?md zHO>c;aZUq#tGiC~oTo*=RZ1D~up9OjhS<7~LXKJR z;Uqc*l=b63C?x!SX=gQ3Ar5y6UD!*gx{N_Yyx)J1ly#d*-5VWBbDKJAa>tId z7Q*sBxI$e#ZNIRhw6sYFwo{%Fv++qMO#&m;I*`=kvtj4s&}rDp!3KB6qit%koaLmj z2ICKbLQT*NhJd(K1;F)ukRAU)$+JIT%>!RLG)FPh05x56&-Qi(i?L#0<_XRp-a*<{ z3I0{w)wcRsDFG95T$RVh+MCcBIV?A=^dhLa8{%;GCQm<)M<3&(f8+i5C?b3VrmOjl z<<}S~D4^n5O(a|4XM)*O@6tjQOjS%!uE-pccMijWbj;Mil*Cd$u}ur3Em?=u?rzGE zas^iik^%xh*t46s(ea@vo|Oz1Ag_7K+KDHpw$jO%PBvd$qVK`m_%mDOUgoZhewt;( z7|Gg4FaX}~k_e}C=-@okFARvVn3cVpq1un%tdXkPh#z{6!`kX-r%|DE{b{l_{SYaO z9Z+g);yiv0EaRRBgX+P{n;M2j*neKI2JPI)#!g1o2|k=s+W1gJrll!g3tt=ll<=_o zWW%*y7W=o=kAj2YsY8j%R9F(30QH;EiDQM_?A~x!7N5Fa-4^EoEc{p{u;Fpi&qh6C z@k~rnjB6&2M7m?{!kslbprH}Jiu=3Ox|75ROz}o=5~>-e1lKc6q&ScTW<}jv#81p?l{<;(C$P2 zTZU3r;m{Y?p>&ZbS2N{v_yj)5ZRhsB>&@L%6H)pP>b|abE_06KKYp=t zfx}~M{{^-mzNl*c=)#OE)Ts||uw7hqGUF=8+v)*~3&u$3xCf_{K==Hdwo$$dv z(b*ze@ki_$G&r%`(i*UFrvT{rv);~!K^mwa8pReG^{X1}@1zsp2^c-+vLO-0EaOuI zjKn^!9=b_cR1@zM2L;KBk-+`mEqHB|wToiS)){ZuHv)gRA6gu|fneR5tyj!JMr`X9 zj_gSfJkhenYJOj+P(9O`F|V*&bhBNfCbY~H)$}$eGXd^GW+#R_8TBg?7{C7P3Rbd! zBuV0z7gPiLoxsaa0sc>|3BwsJ`-c6*NM|X%z9~?=VLI&~P5622WB5D)1_HFm8seUj z(BEkMfYaSn4^ovku4feB+d((KabW-76bxhFE&juw%2CUuK1F}eWE5d4vgOhyLH_~6 C*|O#U literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..dbd8be5b22e86979abdb16fbec0273b7873dcea3 GIT binary patch literal 3896 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c44da928206a408e4b4b8dda0dda7838e8357de2 GIT binary patch literal 36494 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 DvcGDt literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e430c558ad5d4810b2d6db2a5b6425d03fc7f656 GIT binary patch literal 5430 zcmZuz2Ut_f)=ff?-kTILB1L+nN(;RS3aB6;5L)P=mrz0zP@42Ay^3@}L?Co2qJ&;V znjwJnCLMWt?|tvx|NZyOH)rPTv)0~w?V0nPiPXKXPC?2-3IG5oG&NKn{2CR0cj7C* z?yv3L<^TXvMSEpsT}@?Wh_1VfjlB~B0MMYwPW5^8aGLQM79)unqUC^a9kBS3Kt^OV z7{Gdwn!&oDh%jz%6+$(78eRkIN9{oMXB<|l^kh#%EhkC#sC9cf^i)*wvoVe&+m+(` zbD0;tM)T7v?q?e ztANJNF>)ChuQYn<4!4AYt(#{A+H|=+CN6^#I3$%huK-9@$0cL~xtZ{wz@qnOnqiVw!d5GPxEDW5D?)>0Ar8 zg#kne%p=WA0RHIK=;(2lHULVW0spkZo$nMWqDs%a6}blj5GQv_wYXD8pGeu|08k~i zOHtrRP0JFR$#i@CYM;&NZr#HZ%iH|PUH2BBqqMOH_2e(0Ou^MIsj5IOBV>kM!e%Ag z=BClavfWAB^gMgu!wb1Szcc+^?hR~?VoZ+FywgU7FoqFu$Z7oYT3u_3KA;|XMFGE9 zp|cGE>=6R$=d$+MBb>|HyKk6M5#5iiHS4K?3F!Y{8o&tJ5b#MiYh4>AD?}tjbUNHebZl`c1BM(+r zAdTqIf&%A4?1@8ns2K_rU&k{RQDrDiP_r5n`zkBy-k~S>pd1-bov3g!#Qo|j-_zIm z%DQp~H$7RJh)tiK<`E4+#I9Tb(P1>{piv8oDL_N;bd3xfv3lpgf(nWui-@UxX7O$# zBaHH0C(|P94nQCr-kC(K&__%#tY%2ex~rlTZ^#Lxx#5_=Q3NVe&5WlkVtpU0k>IvO zaOGLPMfz379%kG#qnJfY1B(gEyU_7;t8@cxrez5mYSECB&|=H++7d&a3VCl537VI& zg53t@apq5I(rO8&EH-4=D0|zPzp=RnHeNLmaTc!vzoVLrX%1fRqFChKMYZtF(Qk)R zb$IMp2Ha>R4PpyopO3rV)5a4|VhnZ+rMi&{;dJ7BM9o6HLVOWM(UNgc~e1k=4wAgo~vHo7Y^cTbz=rwi8qO;MY?;j4X zuVM~J5L}2S_*-~3JPW?=dP^RaORfAj9E)4gC zyCyPQGf^|P|K7u?vAIX)P1t%Yx#O}Ow^+G&b8kV-S0keewTV2bF4?|-Lf@As{A6;` zhSAxKvy5jKw}c#Q}Rk@ager(IvVGaXH>(7h23IIFtBlG)|Am1TnS-ff#` znM~7~S;pr3&DcZALyj}+&rr`Wlt)vZ-nW{=(Bm}gtBZ|;yD>N-9)F(NQO(l% zY@q74L3j_w1D1 z^hM(tKb00$f_ijsa%s{E3zp36d0O@3gvZtI5|1pm9kxqh(y&NaER3$zyR{rGd~9(6 zy6`@o-Jjf9Ih{Py0p9^vkgSpxfz`kUL@+WD5?Vqpa90PgqdD}t@CcmKWQ-A{bgt|j zE=wX#Zpv`#aaBk_tb%*Pw^1Jzi1GmoTB@ojcQ5h%HYrG?Ot{?GOP6rxABb>wEU0A-8DT%oF{v|j#g4CGa8BiODMX=60 zN6n4t<>}S5n%mWFFZU?DSJGR=2o64>bo$`L;IuhwQ&;IQlg42-{KInKThuoluCIyf z^G4{$C$pap!!|L+hs9?;3*N*&UuVBJ?HguT8ktQGRV|oS;y>*b?7eG#ywPf4@x;xX z%0A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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d24272bd2774b4ed01360e50483384bb6b32b438 GIT binary patch literal 1032 zcmXqLVqr08VtTWHnTe5!iJ8eN(}0(aQ>)FR?K>|cBP%O|fs-M(0Vf-CC<~h~Q)sZE zn1Kk0!zIk?o|+$0R9al3;F*`KXDDqT2@+=(7J`er=j10P<|sHj8pw(B8X6mz7?>IY zp{Z#UkZTI$nn1Y*o~U*R8wf(|;BwE;PtQpO*U5Tt=JYfJ-89zv8U7iqhx_p zhg;o){-Yl|&ndo(`j^PN-N>bLK?!%Tf9R5PO-WPVy^f4O_K>wf=t$fY`#sSf7GHwD zt33NX@uldI^Iw8_zj*zbA9AmD--7J?_5&9Jb}PfUINFo-t3yLAb8wzrU1)=4~bP%ReSt`N*`@|%W+EN&pD;6&3@tUiErc0VXLn11k_;p2gU}&_I8I?gH&L z&5V+g0xNy}=rd!R{@}w@YxBR-P7(i5QNYs+s~xGT(PYTKPVS*KWfqU*oyKa=0e9C0`!Q26ZM6W=Rs zXPSNpTfFx{pU{77aW=PP!uC!Jtj-VDy1i)Cv^zZ| zho`B#I-Lp&OXa(b?V`A)`~x;ZcVwM!(m$|R(WDU KfPzYhaUlS5KYOtN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..49d447b348d8e6b747761715be73f327c9a08c04 GIT binary patch literal 1307 zcmXqLVih-NV*bB?nTe5!iBZsimyJ`a&7D}zB3qqZTp0Vf-CC<~h~Q%I1Z zgn<}{!_LF+o|;sZs-Uajou8bTnv-ehZr}=%;o`9iIlQMRJF_xX!6`E*Gq*T3N1-fL zAtW_BFFz+gD>GZcu_*EA{E{OZi*gecyi1Epb4!87gdCpZbYw$_qqm`lfg8vmZXWy4 zoYLaN90k{$)a;U?{5+smph4b6nFS?alTZwD%_}a+EIGWV`0yT}X`!y(j;@Bv28s}q zq`VV>mIS93m1L%6W+xU`=I1H+rWR$VR%IG`8F+vcG4nXWodD6U;C*;oHbRqvM`l%S zYKcN{;*t3|iIqn-CLh^Yd}JfgvS6Tp5AVrUFgB1A=QS`lurM+)G&D9hFpm=FH3V@D zq1@5nBrQ0b7?qF{BqJ*Wa}y&!C{=PXH8C9XFE_Ueqo zpU10mHl1d?ZM6T=>(=uNc|#9JTZ9+vC_PqrQN-Y_bVPLad(%$~75$E$tC<;}vvY^i zkP6iGJvcM!P z%f}+dBC@xm{bc{QC0yLAULXB8QF-oauP!44K9Dp&BjbM-7GOqaGvEjDg+Y8)17;wF z9Lm6K4-92S2HBclp8X~-x2sJOo5fo4-1|bVWzJ!>Z|8Ox?m2RQkJbUN5loYw}+MIXWFz`$jp0xOTFS^i+N#}jko3bI#;%9x;MIfD}=7CJ#_i> zr3;Rq)y%wRPtDa$ar7)IP58f`|EtBkRmZ_D`~ZENE*y-f~R- zs&}K%CNV`F;e`#G;&{A-=PBGsa#%g#Rf?DX8eT@BO4k+o?UETM94?F}s{gUvR(k+S;D7#lnZDBx+~h%sI%;rNrKv)2iaOa-V&7MN3ni3eViXjpmP4 XSG+azo2qki&V*ZURyx_N-Npm}F{kxH literal 0 HcmV?d00001 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