Skip to content

Commit

Permalink
This adds a new operating mode to Santa called standalone mode.
Browse files Browse the repository at this point in the history
When running in standalone mode TouchID can be used to approved binaries. If a
binary is properly signed a SigningID rule is generated otherwise a SHA256 rule
is generated. Note this lacks a GUI for browsing rules in the local rule db.

Also adds localizable strings for Approve button.
  • Loading branch information
pmarkowsky committed Nov 7, 2024
1 parent 55d3861 commit 4689294
Show file tree
Hide file tree
Showing 31 changed files with 333 additions and 68 deletions.
9 changes: 9 additions & 0 deletions Source/common/SNTConfigurator.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// Copyright 2015-2022 Google Inc. All rights reserved.
/// Copyright 2024 North Pole Security, Inc.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -310,6 +311,14 @@
///
@property(readonly, nonatomic) BOOL enableSilentTTYMode;

///
/// When standalonemode is enabled, Santa will let a user allow a
/// binary using biometric authentication.
///
/// Defaults to NO.
///
@property(readonly, nonatomic) BOOL enableStandaloneMode;

///
/// The text to display when opening Santa.app.
/// If unset, the default text will be displayed.
Expand Down
8 changes: 8 additions & 0 deletions Source/common/SNTConfigurator.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// Copyright 2014-2022 Google Inc. All rights reserved.
/// Copyright 2024 North Pole Security, Inc.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -93,6 +94,7 @@ @implementation SNTConfigurator

static NSString *const kEnableSilentModeKey = @"EnableSilentMode";
static NSString *const kEnableSilentTTYModeKey = @"EnableSilentTTYMode";
static NSString *const kEnableStandaloneMode = @"EnableStandaloneMode";
static NSString *const kAboutTextKey = @"AboutText";
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
static NSString *const kEventDetailURLKey = @"EventDetailURL";
Expand Down Expand Up @@ -225,6 +227,7 @@ - (instancetype)initWithSyncStateFile:(NSString *)syncStateFilePath
kEnableBadSignatureProtectionKey : number,
kEnableSilentModeKey : number,
kEnableSilentTTYModeKey : number,
kEnableStandaloneMode : number,
kAboutTextKey : string,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
Expand Down Expand Up @@ -775,6 +778,11 @@ - (BOOL)enableSilentTTYMode {
return number ? [number boolValue] : NO;
}

- (BOOL)enableStandaloneMode {
NSNumber *number = self.configState[kEnableStandaloneMode];
return number ? [number boolValue] : NO;
}

- (NSString *)aboutText {
return self.configState[kAboutTextKey];
}
Expand Down
5 changes: 5 additions & 0 deletions Source/common/SNTStoredEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@
///
@property NSDate *occurrenceDate;

///
/// Was this event approved as a result of a standalone mode authentication.
///
@property BOOL standaloneApproval;

///
/// The decision santad returned.
///
Expand Down
2 changes: 2 additions & 0 deletions Source/common/SNTStoredEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ - (void)encodeWithCoder:(NSCoder *)coder {
ENCODE(self.quarantineRefererURL, @"quarantineRefererURL");
ENCODE(self.quarantineTimestamp, @"quarantineTimestamp");
ENCODE(self.quarantineAgentBundleID, @"quarantineAgentBundleID");
ENCODE([NSNumber numberWithBool:self.standaloneApproval], @"standaloneApproval");
}

- (instancetype)init {
Expand Down Expand Up @@ -117,6 +118,7 @@ - (instancetype)initWithCoder:(NSCoder *)decoder {
_quarantineRefererURL = DECODE(NSString, @"quarantineRefererURL");
_quarantineTimestamp = DECODE(NSDate, @"quarantineTimestamp");
_quarantineAgentBundleID = DECODE(NSString, @"quarantineAgentBundleID");
_standaloneApproval = [DECODE(NSNumber, @"standaloneApproval") boolValue];
}
return self;
}
Expand Down
4 changes: 3 additions & 1 deletion Source/common/SNTXPCNotifierInterface.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// Copyright 2024 North Pole Security, Inc.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
Expand All @@ -25,7 +26,8 @@
@protocol SNTNotifierXPC
- (void)postBlockNotification:(SNTStoredEvent *)event
withCustomMessage:(NSString *)message
andCustomURL:(NSString *)url;
andCustomURL:(NSString *)url
andReply:(void (^)(BOOL authenticated))reply;
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event;
- (void)postFileAccessBlockNotification:(SNTFileAccessEvent *)event
customMessage:(NSString *)message
Expand Down
5 changes: 3 additions & 2 deletions Source/common/TestUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc, ActionType ac
}

void SleepMS(long ms) {
struct timespec ts {
.tv_sec = ms / 1000, .tv_nsec = (long)((ms % 1000) * NSEC_PER_MSEC),
struct timespec ts{
.tv_sec = ms / 1000,
.tv_nsec = (long)((ms % 1000) * NSEC_PER_MSEC),
};

while (nanosleep(&ts, &ts) != 0) {
Expand Down
1 change: 1 addition & 0 deletions Source/gui/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ objc_library(
"SecurityInterface",
"SystemExtensions",
"UserNotifications",
"LocalAuthentication",
],
deps = [
":SNTAboutWindowView",
Expand Down
3 changes: 3 additions & 0 deletions Source/gui/Resources/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* The default message to show the user when access to a file is blocked */
"Access to a file has been denied" = "Der Zugriff auf eine Datei wurde verweigert";

/* No comment provided by engineer. */
"Approve" = "Genehmigen";

/* No comment provided by engineer. */
"Application" = "Applikation";

Expand Down
3 changes: 3 additions & 0 deletions Source/gui/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
/* No comment provided by engineer. */
"Application" = "Application";

/* No comment provided by engineer. */
"Approve" = "Approve";

/* No comment provided by engineer. */
"Bundle Hash" = "Bundle Hash";

Expand Down
3 changes: 3 additions & 0 deletions Source/gui/Resources/ru.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* The default message to show the user when access to a file is blocked */
"Access to a file has been denied" = "Доступ к файлу был запрещен";

/* No comment provided by engineer. */
"Approve" = "Утвердить";

/* No comment provided by engineer. */
"Application" = "Приложение";

Expand Down
3 changes: 3 additions & 0 deletions Source/gui/Resources/uk.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/* No comment provided by engineer. */
"Approve" = "Затвердити";

/* No comment provided by engineer. */
"Application" = "Заявка";

Expand Down
8 changes: 7 additions & 1 deletion Source/gui/SNTBinaryMessageWindowController.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@

- (instancetype)initWithEvent:(SNTStoredEvent *)event
customMsg:(NSString *)message
customURL:(NSString *)url;
customURL:(NSString *)url
reply:(void (^)(BOOL authenticated))replyBlock;

- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash;

Expand All @@ -49,6 +50,11 @@
///
@property(readonly) SNTStoredEvent *event;

///
/// The reply block to call when the user has made a decision in standalone
/// mode.
@property(readonly, nonatomic) void (^replyBlock)(BOOL authenticated);

///
/// The root progress object. Child nodes are vended to santad to report on work being done.
///
Expand Down
80 changes: 71 additions & 9 deletions Source/gui/SNTBinaryMessageWindowController.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
#import "Source/gui/SNTBinaryMessageWindowView-Swift.h"

#include <AppKit/AppKit.h>
#import <LocalAuthentication/LocalAuthentication.h>
#import <MOLCertificate/MOLCertificate.h>
#import <SecurityInterface/SFCertificatePanel.h>
#include <dispatch/dispatch.h>

#import "Source/common/CertificateHelpers.h"
#import "Source/common/SNTBlockMessage.h"
Expand All @@ -39,12 +41,14 @@ @implementation SNTBinaryMessageWindowController

- (instancetype)initWithEvent:(SNTStoredEvent *)event
customMsg:(NSString *)message
customURL:(NSString *)url {
customURL:(NSString *)url
reply:(void (^)(BOOL))replyBlock {
self = [super init];
if (self) {
_event = event;
_customMessage = message;
_customURL = url;
_replyBlock = replyBlock;
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
[_progress addObserver:self
forKeyPath:@"fractionCompleted"
Expand Down Expand Up @@ -78,6 +82,14 @@ - (void)windowDidResize:(NSNotification *)notification {
- (void)showWindow:(id)sender {
if (self.window) [self.window orderOut:sender];

NSError *err;
NSString *standaloneErrorMessage;

if (![self isAbleToAuthenticateInStandaloneMode:&err]) {
standaloneErrorMessage = [NSString
stringWithFormat:@"Unable to authenticate with Touch ID: %@", err.localizedDescription];
}

self.window =
[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 0, 0)
styleMask:NSWindowStyleMaskClosable | NSWindowStyleMaskResizable |
Expand All @@ -89,15 +101,24 @@ - (void)showWindow:(id)sender {
[self.window standardWindowButton:NSWindowZoomButton].hidden = YES;
[self.window standardWindowButton:NSWindowCloseButton].hidden = YES;
[self.window standardWindowButton:NSWindowMiniaturizeButton].hidden = YES;
self.window.contentViewController = [SNTBinaryMessageWindowViewFactory
createWithWindow:self.window
event:self.event
customMsg:self.customMessage
customURL:self.customURL
self.window.contentViewController =
[SNTBinaryMessageWindowViewFactory createWithWindow:self.window
event:self.event
customMsg:self.customMessage
customURL:self.customURL
bundleProgress:self.bundleProgress
uiStateCallback:^(NSTimeInterval preventNotificationsPeriod) {
self.silenceFutureNotificationsPeriod = preventNotificationsPeriod;
}];
uiStateCallback:^(NSTimeInterval preventNotificationsPeriod) {
self.silenceFutureNotificationsPeriod = preventNotificationsPeriod;
}
standaloneErrorMessage:standaloneErrorMessage
replyCallback:^(BOOL addRule, void (^guiCompletionHandler)()) {
if (addRule) {
[self approveBinaryForStandaloneMode];
} else {
self.replyBlock(NO);
}
guiCompletionHandler();
}];

self.window.delegate = self;

Expand All @@ -120,4 +141,45 @@ - (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString
});
}

#pragma mark - Standlone mode methods

// Check if the user is able to authenticate using Touch ID. Returns YES if the
// user is able to NO Otherwise
- (BOOL)isAbleToAuthenticateInStandaloneMode:(NSError **)err {
LAContext *context = [[LAContext alloc] init];

if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:err]) {
return NO;
}

return YES;
}

// When running in standalone mode, the user is prompted to approve the binary.
- (void)approveBinaryForStandaloneMode {
if (self.replyBlock == nil) {
return;
}

LAContext *context = [[LAContext alloc] init];

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:[NSString stringWithFormat:@"Approve %@", self.event.signingID]
reply:^(BOOL success, NSError *error) {
if (self.replyBlock == nil) {
dispatch_semaphore_signal(sema);
return;
}

if (success) {
self.replyBlock(YES);
} else {
self.replyBlock(NO);
}
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}

@end
Loading

0 comments on commit 4689294

Please sign in to comment.