diff --git a/MODULE.bazel b/MODULE.bazel index 30a28201..563ba412 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -14,7 +14,7 @@ bazel_dep(name = "googletest", version = "1.14.0.bcr.1", repo_name = "com_google bazel_dep(name = "protos", version = "1.0.1", repo_name = "northpole_protos") git_override( module_name = "protos", - commit = "86aea2de00862d5f24272d865586ce7e805a4f18", + commit = "b2d1e1214440ba42d129338c1f1e6466a83f30d9", remote = "https://github.com/northpolesec/protos", ) diff --git a/Source/common/BUILD b/Source/common/BUILD index 34f38c52..933c1c28 100644 --- a/Source/common/BUILD +++ b/Source/common/BUILD @@ -152,6 +152,24 @@ objc_library( ], ) +objc_library( + name = "EncodeEntitlements", + srcs = ["EncodeEntitlements.mm"], + hdrs = ["EncodeEntitlements.h"], + deps = [ + ":SNTLogging", + ], +) + +santa_unit_test( + name = "EncodeEntitlementsTest", + srcs = ["EncodeEntitlementsTest.mm"], + deps = [ + ":EncodeEntitlements", + ], +) + + objc_library( name = "SigningIDHelpers", srcs = ["SigningIDHelpers.m"], @@ -536,6 +554,7 @@ santa_unit_test( test_suite( name = "unit_tests", tests = [ + ":EncodeEntitlementsTest", ":PrefixTreeTest", ":SNTBlockMessageTest", ":SNTCachedDecisionTest", diff --git a/Source/common/EncodeEntitlements.h b/Source/common/EncodeEntitlements.h new file mode 100644 index 00000000..14591284 --- /dev/null +++ b/Source/common/EncodeEntitlements.h @@ -0,0 +1,29 @@ +/// 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. +/// You may obtain a copy of the License at +/// +/// https://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT 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__ENCODEENTITLEMENTS_H +#define SANTA__COMMON__ENCODEENTITLEMENTS_H + +#include + +namespace santa { + +void EncodeEntitlementsCommon(NSDictionary *entitlements, BOOL entitlements_filtered, + void (^EncodeInitBlock)(NSUInteger count, bool is_filtered), + void (^EncodeEntitlementBlock)(NSString *entitlement, + NSString *value)); + +} + +#endif diff --git a/Source/common/EncodeEntitlements.mm b/Source/common/EncodeEntitlements.mm new file mode 100644 index 00000000..9d9aaa20 --- /dev/null +++ b/Source/common/EncodeEntitlements.mm @@ -0,0 +1,129 @@ +/// 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. +/// You may obtain a copy of the License at +/// +/// https://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT 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 "Source/common/EncodeEntitlements.h" + +#include + +#include "Source/common/SNTLogging.h" + +namespace santa { + +static constexpr NSUInteger kMaxEncodeObjectEntries = 64; +static constexpr NSUInteger kMaxEncodeObjectLevels = 5; + +id StandardizedNestedObjects(id obj, int level) { + if (!obj) { + return nil; + } else if (level-- == 0) { + return [obj description]; + } + + if ([obj isKindOfClass:[NSNumber class]] || [obj isKindOfClass:[NSString class]]) { + return obj; + } else if ([obj isKindOfClass:[NSArray class]]) { + NSMutableArray *arr = [NSMutableArray array]; + for (id item in obj) { + [arr addObject:StandardizedNestedObjects(item, level)]; + } + return arr; + } else if ([obj isKindOfClass:[NSDictionary class]]) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + for (id key in obj) { + [dict setObject:StandardizedNestedObjects(obj[key], level) forKey:key]; + } + return dict; + } else if ([obj isKindOfClass:[NSData class]]) { + return [obj base64EncodedStringWithOptions:0]; + } else if ([obj isKindOfClass:[NSDate class]]) { + return [NSISO8601DateFormatter stringFromDate:obj + timeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"] + formatOptions:NSISO8601DateFormatWithFractionalSeconds | + NSISO8601DateFormatWithInternetDateTime]; + + } else { + LOGW(@"Unexpected object encountered: %@", obj); + return [obj description]; + } +} + +void EncodeEntitlementsCommon(NSDictionary *entitlements, BOOL entitlements_filtered, + void (^EncodeInitBlock)(NSUInteger count, bool is_filtered), + void (^EncodeEntitlementBlock)(NSString *entitlement, + NSString *value)) { + NSDictionary *standardized_entitlements = + StandardizedNestedObjects(entitlements, kMaxEncodeObjectLevels); + __block int num_objects_to_encode = + (int)std::min(kMaxEncodeObjectEntries, standardized_entitlements.count); + + EncodeInitBlock( + num_objects_to_encode, + entitlements_filtered != NO || num_objects_to_encode != standardized_entitlements.count); + + [standardized_entitlements enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + if (num_objects_to_encode-- == 0) { + *stop = YES; + return; + } + + if (![key isKindOfClass:[NSString class]]) { + LOGW(@"Skipping entitlement key with unexpected key type: %@", key); + return; + } + + NSError *err; + NSData *json_data; + @try { + json_data = [NSJSONSerialization dataWithJSONObject:obj + options:NSJSONWritingFragmentsAllowed + error:&err]; + } @catch (NSException *e) { + LOGW(@"Encountered entitlement that cannot directly convert to JSON: %@: %@", key, obj); + } + + if (!json_data) { + // If the first attempt to serialize to JSON failed, get a string + // representation of the object via the `description` method and attempt + // to serialize that instead. Serialization can fail for a number of + // reasons, such as arrays including invalid types. + @try { + json_data = [NSJSONSerialization dataWithJSONObject:[obj description] + options:NSJSONWritingFragmentsAllowed + error:&err]; + } @catch (NSException *e) { + LOGW(@"Unable to create fallback string: %@: %@", key, obj); + } + + if (!json_data) { + // As a final fallback, simply serialize an error message so that the + // entitlement key is still logged. + json_data = [NSJSONSerialization dataWithJSONObject:@"JSON Serialization Failed" + options:NSJSONWritingFragmentsAllowed + error:&err]; + } + } + + // This shouldn't be possible given the fallback code above. But handle it + // just in case to prevent a crash. + if (!json_data) { + LOGW(@"Failed to create valid JSON for entitlement: %@", key); + return; + } + + EncodeEntitlementBlock(key, [[NSString alloc] initWithData:json_data + encoding:NSUTF8StringEncoding]); + }]; +} + +} // namespace santa diff --git a/Source/common/EncodeEntitlementsTest.mm b/Source/common/EncodeEntitlementsTest.mm new file mode 100644 index 00000000..a0862c34 --- /dev/null +++ b/Source/common/EncodeEntitlementsTest.mm @@ -0,0 +1,158 @@ +/// 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. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT 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 "Source/common/EncodeEntitlements.h" +#include "XCTest/XCTest.h" + +#import +#import + +namespace santa { +extern id StandardizedNestedObjects(id obj, int level); +} // namespace santa + +using santa::EncodeEntitlementsCommon; +using santa::StandardizedNestedObjects; + +@interface EncodeEntitlementsTest : XCTestCase +@end + +@implementation EncodeEntitlementsTest + +- (void)testStandardizedNestedObjectsTypes { + id val = StandardizedNestedObjects(@"asdf", 1); + XCTAssertTrue([val isKindOfClass:[NSString class]]); + + val = StandardizedNestedObjects(@(0), 1); + XCTAssertTrue([val isKindOfClass:[NSNumber class]]); + + val = StandardizedNestedObjects(@[], 1); + XCTAssertTrue([val isKindOfClass:[NSArray class]]); + + val = StandardizedNestedObjects(@{}, 1); + XCTAssertTrue([val isKindOfClass:[NSDictionary class]]); + + val = StandardizedNestedObjects([[NSData alloc] init], 1); + XCTAssertTrue([val isKindOfClass:[NSString class]]); + + val = StandardizedNestedObjects([NSDate now], 1); + XCTAssertTrue([val isKindOfClass:[NSString class]]); +} + +- (void)testStandardizedNestedObjectsLevels { + NSArray *nestedObj = @[ + @[ + @[ + @[ @"111", @"112" ], + @[ @"113", @"114" ], + ], + @[ + @[ @"121", @"122" ], + @[ @"123", @"124" ], + ] + ], + @[ + @[ + @[ @"211", @"212" ], + @[ @"213", @"214" ], + ], + @[ + @[ @"221", @"222" ], + @[ @"223", @"224" ], + ] + ] + ]; + + id val = StandardizedNestedObjects(nestedObj, 1); + + XCTAssertEqual(((NSArray *)val).count, 2); + XCTAssertEqualObjects( + val[0], @"(\n (\n (\n 111,\n 112\n ),\n " + @" (\n 113,\n 114\n )\n ),\n (\n " + @" (\n 121,\n 122\n ),\n " + @"(\n 123,\n 124\n )\n )\n)"); + XCTAssertEqualObjects( + val[1], @"(\n (\n (\n 211,\n 212\n ),\n " + @" (\n 213,\n 214\n )\n ),\n (\n " + @" (\n 221,\n 222\n ),\n " + @"(\n 223,\n 224\n )\n )\n)"); + + val = StandardizedNestedObjects(nestedObj, 3); + + XCTAssertEqual(((NSArray *)val).count, 2); + XCTAssertEqual(((NSArray *)val[0]).count, 2); + XCTAssertEqual(((NSArray *)val[1]).count, 2); + XCTAssertEqual(((NSArray *)val[0][0]).count, 2); + XCTAssertEqual(((NSArray *)val[0][1]).count, 2); + XCTAssertEqualObjects(val[0][0][0], @"(\n 111,\n 112\n)"); + XCTAssertEqualObjects(val[0][0][1], @"(\n 113,\n 114\n)"); + XCTAssertEqualObjects(val[0][1][0], @"(\n 121,\n 122\n)"); + XCTAssertEqualObjects(val[0][1][1], @"(\n 123,\n 124\n)"); + XCTAssertEqualObjects(val[1][0][0], @"(\n 211,\n 212\n)"); + XCTAssertEqualObjects(val[1][0][1], @"(\n 213,\n 214\n)"); + XCTAssertEqualObjects(val[1][1][0], @"(\n 221,\n 222\n)"); + XCTAssertEqualObjects(val[1][1][1], @"(\n 223,\n 224\n)"); +} + +- (void)testEncodeEntitlementsCommonBasic { + NSDictionary *entitlements = @{ + @"ent1" : @"val1", + @"ent2" : @"val2", + }; + + EncodeEntitlementsCommon( + entitlements, false, + ^(NSUInteger count, bool is_filtered) { + XCTAssertEqual(count, entitlements.count); + XCTAssertFalse(is_filtered); + }, + ^(NSString *entitlement, NSString *value) { + if ([entitlement isEqualToString:@"ent1"]) { + XCTAssertEqualObjects(value, @"\"val1\""); + } else if ([entitlement isEqualToString:@"ent2"]) { + XCTAssertEqualObjects(value, @"\"val2\""); + } else { + XCTFail(@"Unexpected entitlement: %@", entitlement); + } + }); +} + +- (void)testEncodeEntitlementsCommonFiltered { + NSMutableDictionary *entitlements = [NSMutableDictionary dictionary]; + + EncodeEntitlementsCommon(entitlements, true, + ^(NSUInteger count, bool is_filtered) { + XCTAssertEqual(count, entitlements.count); + XCTAssertTrue(is_filtered); + }, + ^(NSString *entitlement, NSString *value){ + // noop + }); + + // Create a large dictionary that will get capped + for (int i = 0; i < 100; i++) { + entitlements[[NSString stringWithFormat:@"ent%d", i]] = [NSString stringWithFormat:@"val%d", i]; + } + + EncodeEntitlementsCommon(entitlements, false, + ^(NSUInteger count, bool is_filtered) { + XCTAssertLessThan(count, entitlements.count); + XCTAssertTrue(is_filtered); + }, + ^(NSString *entitlement, NSString *value){ + // noop + }); +} + +@end diff --git a/Source/common/SNTStoredEvent.h b/Source/common/SNTStoredEvent.h index af41996b..fd054498 100644 --- a/Source/common/SNTStoredEvent.h +++ b/Source/common/SNTStoredEvent.h @@ -174,4 +174,14 @@ /// @property(readonly) NSArray *signingChainCertRefs; +/// +/// If the executed file was entitled, this is the set of key/value pairs of entitlements +/// +@property NSDictionary *entitlements; + +/// +/// Whether or not the set of entitlements were filtered (e.g. due to configuration) +/// +@property BOOL entitlementsFiltered; + @end diff --git a/Source/common/SNTStoredEvent.m b/Source/common/SNTStoredEvent.m index decad4e3..fe889e06 100644 --- a/Source/common/SNTStoredEvent.m +++ b/Source/common/SNTStoredEvent.m @@ -30,7 +30,11 @@ @implementation SNTStoredEvent #define DECODEARRAY(cls, key) \ [decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \ forKey:key] - +#define DECODEDICT(key) \ + [decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSDictionary class], [NSArray class], \ + [NSString class], [NSNumber class], \ + [NSData class], nil] \ + forKey:key] + (BOOL)supportsSecureCoding { return YES; } @@ -55,6 +59,8 @@ - (void)encodeWithCoder:(NSCoder *)coder { ENCODE(self.teamID, @"teamID"); ENCODE(self.signingID, @"signingID"); ENCODE(self.cdhash, @"cdhash"); + ENCODE(self.entitlements, @"entitlements"); + ENCODE(@(self.entitlementsFiltered), @"entitlementsFiltered"); ENCODE(self.executingUser, @"executingUser"); ENCODE(self.occurrenceDate, @"occurrenceDate"); @@ -102,6 +108,8 @@ - (instancetype)initWithCoder:(NSCoder *)decoder { _teamID = DECODE(NSString, @"teamID"); _signingID = DECODE(NSString, @"signingID"); _cdhash = DECODE(NSString, @"cdhash"); + _entitlements = DECODEDICT(@"entitlements"); + _entitlementsFiltered = [DECODE(NSNumber, @"entitlementsFiltered") boolValue]; _executingUser = DECODE(NSString, @"executingUser"); _occurrenceDate = DECODE(NSDate, @"occurrenceDate"); diff --git a/Source/santad/BUILD b/Source/santad/BUILD index 6c96adb8..0080b639 100644 --- a/Source/santad/BUILD +++ b/Source/santad/BUILD @@ -594,6 +594,7 @@ objc_library( "//Source/common:Platform", "//Source/common:SNTCachedDecision", "//Source/common:SNTConfigurator", + "//Source/common:EncodeEntitlements", "//Source/common:SNTLogging", "//Source/common:SNTStoredEvent", "//Source/common:String", diff --git a/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm b/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm index 21e3e7b2..b3455a84 100644 --- a/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm +++ b/Source/santad/Logs/EndpointSecurity/Serializers/Protobuf.mm @@ -29,6 +29,7 @@ #include #include +#include "Source/common/EncodeEntitlements.h" #import "Source/common/SNTCachedDecision.h" #include "Source/common/SNTCommonEnums.h" #include "Source/common/SNTLogging.h" @@ -49,9 +50,6 @@ namespace santa { -static constexpr NSUInteger kMaxEncodeObjectEntries = 64; -static constexpr NSUInteger kMaxEncodeObjectLevels = 5; - std::shared_ptr Protobuf::Create(std::shared_ptr esapi, SNTDecisionCache *decision_cache, bool json) { return std::make_shared(esapi, std::move(decision_cache), json); @@ -467,122 +465,20 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info, return FinalizeProto(santa_msg); } -id StandardizedNestedObjects(id obj, int level) { - if (level-- == 0) { - return [obj description]; - } - - if ([obj isKindOfClass:[NSNumber class]] || [obj isKindOfClass:[NSString class]]) { - return obj; - } else if ([obj isKindOfClass:[NSArray class]]) { - NSMutableArray *arr = [NSMutableArray array]; - for (id item in obj) { - [arr addObject:StandardizedNestedObjects(item, level)]; - } - return arr; - } else if ([obj isKindOfClass:[NSDictionary class]]) { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - for (id key in obj) { - [dict setObject:StandardizedNestedObjects(obj[key], level) forKey:key]; - } - return dict; - } else if ([obj isKindOfClass:[NSData class]]) { - return [obj base64EncodedStringWithOptions:0]; - } else if ([obj isKindOfClass:[NSDate class]]) { - return [NSISO8601DateFormatter stringFromDate:obj - timeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"] - formatOptions:NSISO8601DateFormatWithFractionalSeconds | - NSISO8601DateFormatWithInternetDateTime]; - - } else { - LOGW(@"Unexpected object encountered: %@", obj); - return [obj description]; - } -} - void EncodeEntitlements(::pbv1::Execution *pb_exec, SNTCachedDecision *cd) { ::pbv1::EntitlementInfo *pb_entitlement_info = pb_exec->mutable_entitlement_info(); - pb_entitlement_info->set_entitlements_filtered(cd.entitlementsFiltered != NO); - - if (!cd.entitlements) { - return; - } - - // Since nested objects with varying types is hard for the API to serialize to - // JSON, first go through and standardize types to ensure better serialization - // as well as a consitent view of data. - NSDictionary *entitlements = StandardizedNestedObjects(cd.entitlements, kMaxEncodeObjectLevels); - - __block int numObjectsToEncode = (int)std::min(kMaxEncodeObjectEntries, entitlements.count); - - pb_entitlement_info->mutable_entitlements()->Reserve(numObjectsToEncode); - - [entitlements enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - if (numObjectsToEncode-- == 0) { - // Because entitlements are being clipped, ensure that we update that - // the set of entitlements were filtered. - pb_entitlement_info->set_entitlements_filtered(true); - *stop = YES; - return; - } - - if (![key isKindOfClass:[NSString class]]) { - LOGW(@"Skipping entitlement key with unexpected key type: %@", key); - return; - } - - NSError *err; - NSData *jsonData; - @try { - jsonData = [NSJSONSerialization dataWithJSONObject:obj - options:NSJSONWritingFragmentsAllowed - error:&err]; - } @catch (NSException *e) { - LOGW(@"Encountered entitlement that cannot directly convert to JSON: %@: %@", key, obj); - } - - if (!jsonData) { - // If the first attempt to serialize to JSON failed, get a string - // representation of the object via the `description` method and attempt - // to serialize that instead. Serialization can fail for a number of - // reasons, such as arrays including invalid types. - @try { - jsonData = [NSJSONSerialization dataWithJSONObject:[obj description] - options:NSJSONWritingFragmentsAllowed - error:&err]; - } @catch (NSException *e) { - LOGW(@"Unable to create fallback string: %@: %@", key, obj); - } - - if (!jsonData) { - @try { - // As a final fallback, simply serialize an error message so that the - // entitlement key is still logged. - jsonData = [NSJSONSerialization dataWithJSONObject:@"JSON Serialization Failed" - options:NSJSONWritingFragmentsAllowed - error:&err]; - } @catch (NSException *e) { - // This shouldn't be able to happen... - LOGW(@"Failed to serialize fallback error message"); - } - } - } - - // This shouldn't be possible given the fallback code above. But handle it - // just in case to prevent a crash. - if (!jsonData) { - LOGW(@"Failed to create valid JSON for entitlement: %@", key); - return; - } - - ::pbv1::Entitlement *pb_entitlement = pb_entitlement_info->add_entitlements(); - EncodeString([pb_entitlement] { return pb_entitlement->mutable_key(); }, - NSStringToUTF8StringView(key)); - EncodeString([pb_entitlement] { return pb_entitlement->mutable_value(); }, - NSStringToUTF8StringView([[NSString alloc] initWithData:jsonData - encoding:NSUTF8StringEncoding])); - }]; + EncodeEntitlementsCommon( + cd.entitlements, cd.entitlementsFiltered, + ^(NSUInteger count, bool is_filtered) { + pb_entitlement_info->set_entitlements_filtered(is_filtered); + pb_entitlement_info->mutable_entitlements()->Reserve((int)count); + }, + ^(NSString *entitlement, NSString *value) { + ::pbv1::Entitlement *pb_entitlement = pb_entitlement_info->add_entitlements(); + EncodeString([pb_entitlement] { return pb_entitlement->mutable_key(); }, entitlement); + EncodeString([pb_entitlement] { return pb_entitlement->mutable_value(); }, value); + }); } std::vector Protobuf::SerializeMessage(const EnrichedExec &msg, SNTCachedDecision *cd) { diff --git a/Source/santad/SNTExecutionController.mm b/Source/santad/SNTExecutionController.mm index ff649e57..4c373a04 100644 --- a/Source/santad/SNTExecutionController.mm +++ b/Source/santad/SNTExecutionController.mm @@ -335,6 +335,8 @@ - (void)validateExecEvent:(const Message &)esMsg postAction:(bool (^)(SNTAction) se.pid = @(audit_token_to_pid(targetProc->audit_token)); se.ppid = @(audit_token_to_pid(targetProc->parent_audit_token)); se.parentName = @(esMsg.ParentProcessName().c_str()); + se.entitlements = cd.entitlements; + se.entitlementsFiltered = cd.entitlementsFiltered; // Bundle data se.fileBundleID = [binInfo bundleIdentifier]; diff --git a/Source/santasyncservice/BUILD b/Source/santasyncservice/BUILD index b4fca2d3..ef2a956b 100644 --- a/Source/santasyncservice/BUILD +++ b/Source/santasyncservice/BUILD @@ -48,6 +48,7 @@ objc_library( deps = [ ":FCM_lib", ":broadcaster_lib", + "//Source/common:EncodeEntitlements", "//Source/common:SNTCommonEnums", "//Source/common:SNTConfigurator", "//Source/common:SNTFileInfo", @@ -104,6 +105,7 @@ santa_unit_test( deps = [ ":FCM_lib", ":broadcaster_lib", + "//Source/common:EncodeEntitlements", "//Source/common:SNTCommonEnums", "//Source/common:SNTConfigurator", "//Source/common:SNTDropRootPrivs", diff --git a/Source/santasyncservice/SNTSyncEventUpload.h b/Source/santasyncservice/SNTSyncEventUpload.h index d2014fb5..270bad38 100644 --- a/Source/santasyncservice/SNTSyncEventUpload.h +++ b/Source/santasyncservice/SNTSyncEventUpload.h @@ -1,23 +1,25 @@ /// 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. /// You may obtain a copy of the License at /// -/// http://www.apache.org/licenses/LICENSE-2.0 +/// https://www.apache.org/licenses/LICENSE-2.0 /// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT 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 "SNTSyncStage.h" +#import "Source/common/SNTStoredEvent.h" @interface SNTSyncEventUpload : SNTSyncStage -- (BOOL)uploadEvents:(NSArray *)events; +- (BOOL)uploadEvents:(NSArray *)events; @end diff --git a/Source/santasyncservice/SNTSyncEventUpload.mm b/Source/santasyncservice/SNTSyncEventUpload.mm index 40e11245..6d4cd160 100644 --- a/Source/santasyncservice/SNTSyncEventUpload.mm +++ b/Source/santasyncservice/SNTSyncEventUpload.mm @@ -1,22 +1,24 @@ /// 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. /// You may obtain a copy of the License at /// -/// http://www.apache.org/licenses/LICENSE-2.0 +/// https://www.apache.org/licenses/LICENSE-2.0 /// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT 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 "Source/santasyncservice/SNTSyncEventUpload.h" #import #import +#include "Source/common/EncodeEntitlements.h" #import "Source/common/SNTCommonEnums.h" #import "Source/common/SNTConfigurator.h" #import "Source/common/SNTFileInfo.h" @@ -34,6 +36,7 @@ namespace pbv1 = ::santa::sync::v1; using santa::NSStringToUTF8String; +using santa::NSStringToUTF8StringView; @implementation SNTSyncEventUpload @@ -53,7 +56,7 @@ - (BOOL)sync { return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0); } -- (BOOL)uploadEvents:(NSArray *)events { +- (BOOL)uploadEvents:(NSArray *)events { google::protobuf::Arena arena; auto req = google::protobuf::Arena::Create<::pbv1::EventUploadRequest>(&arena); req->set_machine_id(NSStringToUTF8String(self.syncState.machineID)); @@ -176,6 +179,20 @@ - (BOOL)uploadEvents:(NSArray *)events { c->set_valid_until([cert.validUntil timeIntervalSince1970]); } + ::pbv1::EntitlementInfo *pb_entitlement_info = e->mutable_entitlement_info(); + + santa::EncodeEntitlementsCommon( + event.entitlements, event.entitlementsFiltered, + ^(NSUInteger count, bool is_filtered) { + pb_entitlement_info->set_entitlements_filtered(is_filtered); + pb_entitlement_info->mutable_entitlements()->Reserve((int)count); + }, + ^(NSString *entitlement, NSString *value) { + ::pbv1::Entitlement *pb_entitlement = pb_entitlement_info->add_entitlements(); + pb_entitlement->set_key(NSStringToUTF8StringView(entitlement)); + pb_entitlement->set_value(NSStringToUTF8StringView(value)); + }); + // TODO: Add support the for Standalone Approval field so that a sync service // can be notified that a user self approved a binary.