forked from northpolesec/santa
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
405 additions
and
132 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <Foundation/Foundation.h> | ||
|
||
namespace santa { | ||
|
||
void EncodeEntitlementsCommon(NSDictionary *entitlements, BOOL entitlements_filtered, | ||
void (^EncodeInitBlock)(NSUInteger count, bool is_filtered), | ||
void (^EncodeEntitlementBlock)(NSString *entitlement, | ||
NSString *value)); | ||
|
||
} | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <algorithm> | ||
|
||
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <Foundation/Foundation.h> | ||
#import <XCTest/XCTest.h> | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.