From c10759f7a186efdd82ddd818dae2ac1f853691fc Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Sat, 14 Dec 2024 19:35:21 +0100 Subject: [PATCH] feat: added certificate verification to the helper app. It will verify the incoming connection certificate if it's Stats. If not the connection will not be opened. It prevents abusing the helper app by 3d party applications to access the helper interface. Thanks @senzee1984 for finding and helping solve that problem. This commit will force the update of the helper app and will require a password for users who have installed the helper. --- SMC/Helper/Info.plist | 8 +- SMC/Helper/main.swift | 103 ++++++++++++++++-- .../xcshareddata/xcschemes/SMC.xcscheme | 2 +- .../xcshareddata/xcschemes/Stats.xcscheme | 2 +- .../xcschemes/WidgetsExtension.xcscheme | 2 +- Stats/Supporting Files/Info.plist | 2 +- Widgets/Supporting Files/Info.plist | 2 +- 7 files changed, 105 insertions(+), 16 deletions(-) diff --git a/SMC/Helper/Info.plist b/SMC/Helper/Info.plist index 7265bf1e..e64e4b39 100644 --- a/SMC/Helper/Info.plist +++ b/SMC/Helper/Info.plist @@ -7,11 +7,11 @@ CFBundleName eu.exelban.Stats.SMC.Helper CFBundleShortVersionString - 1.0.0 + 1.0.1 CFBundleVersion - 1 - CFBundleInfoDictionaryVersion - 6.0 + 2 + CFBundleInfoDictionaryVersion + 6.0 SMAuthorizedClients anchor apple generic and identifier "eu.exelban.Stats" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = RP2S87B72W) diff --git a/SMC/Helper/main.swift b/SMC/Helper/main.swift index d90dfdcf..d94fdef2 100644 --- a/SMC/Helper/main.swift +++ b/SMC/Helper/main.swift @@ -48,11 +48,22 @@ class Helper: NSObject, NSXPCListenerDelegate, HelperProtocol { } } - func listener(_ listener: NSXPCListener, shouldAcceptNewConnection connection: NSXPCConnection) -> Bool { - connection.exportedInterface = NSXPCInterface(with: HelperProtocol.self) - connection.exportedObject = self - connection.invalidationHandler = { - if let connectionIndex = self.connections.firstIndex(of: connection) { + func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { + do { + let isValid = try CodesignCheck.codeSigningMatches(pid: newConnection.processIdentifier) + if !isValid { + NSLog("invalid connection, dropping") + return false + } + } catch { + NSLog("error checking code signing: \(error)") + return false + } + + newConnection.exportedInterface = NSXPCInterface(with: HelperProtocol.self) + newConnection.exportedObject = self + newConnection.invalidationHandler = { + if let connectionIndex = self.connections.firstIndex(of: newConnection) { self.connections.remove(at: connectionIndex) } if self.connections.isEmpty { @@ -60,8 +71,8 @@ class Helper: NSObject, NSXPCListenerDelegate, HelperProtocol { } } - self.connections.append(connection) - connection.resume() + self.connections.append(newConnection) + newConnection.resume() return true } @@ -185,3 +196,81 @@ extension Helper { exit(0) } } + +// https://github.com/duanefields/VirtualKVM/blob/master/VirtualKVM/CodesignCheck.swift +let kSecCSDefaultFlags = 0 + +enum CodesignCheckError: Error { + case message(String) +} + +struct CodesignCheck { + public static func codeSigningMatches(pid: pid_t) throws -> Bool { + return try self.codeSigningCertificatesForSelf() == self.codeSigningCertificates(forPID: pid) + } + + private static func codeSigningCertificatesForSelf() throws -> [SecCertificate] { + guard let secStaticCode = try secStaticCodeSelf() else { return [] } + return try codeSigningCertificates(forStaticCode: secStaticCode) + } + + private static func codeSigningCertificates(forPID pid: pid_t) throws -> [SecCertificate] { + guard let secStaticCode = try secStaticCode(forPID: pid) else { return [] } + return try codeSigningCertificates(forStaticCode: secStaticCode) + } + + private static func executeSecFunction(_ secFunction: () -> (OSStatus) ) throws { + let osStatus = secFunction() + guard osStatus == errSecSuccess else { + throw CodesignCheckError.message(String(describing: SecCopyErrorMessageString(osStatus, nil))) + } + } + + private static func secStaticCodeSelf() throws -> SecStaticCode? { + var secCodeSelf: SecCode? + try executeSecFunction { SecCodeCopySelf(SecCSFlags(rawValue: 0), &secCodeSelf) } + guard let secCode = secCodeSelf else { + throw CodesignCheckError.message("SecCode returned empty from SecCodeCopySelf") + } + return try secStaticCode(forSecCode: secCode) + } + + private static func secStaticCode(forPID pid: pid_t) throws -> SecStaticCode? { + var secCodePID: SecCode? + try executeSecFunction { SecCodeCopyGuestWithAttributes(nil, [kSecGuestAttributePid: pid] as CFDictionary, [], &secCodePID) } + guard let secCode = secCodePID else { + throw CodesignCheckError.message("SecCode returned empty from SecCodeCopyGuestWithAttributes") + } + return try secStaticCode(forSecCode: secCode) + } + + private static func secStaticCode(forSecCode secCode: SecCode) throws -> SecStaticCode? { + var secStaticCodeCopy: SecStaticCode? + try executeSecFunction { SecCodeCopyStaticCode(secCode, [], &secStaticCodeCopy) } + guard let secStaticCode = secStaticCodeCopy else { + throw CodesignCheckError.message("SecStaticCode returned empty from SecCodeCopyStaticCode") + } + return secStaticCode + } + + private static func isValid(secStaticCode: SecStaticCode) throws { + try executeSecFunction { SecStaticCodeCheckValidity(secStaticCode, SecCSFlags(rawValue: kSecCSDoNotValidateResources | kSecCSCheckNestedCode), nil) } + } + + private static func secCodeInfo(forStaticCode secStaticCode: SecStaticCode) throws -> [String: Any]? { + try isValid(secStaticCode: secStaticCode) + var secCodeInfoCFDict: CFDictionary? + try executeSecFunction { SecCodeCopySigningInformation(secStaticCode, SecCSFlags(rawValue: kSecCSSigningInformation), &secCodeInfoCFDict) } + guard let secCodeInfo = secCodeInfoCFDict as? [String: Any] else { + throw CodesignCheckError.message("CFDictionary returned empty from SecCodeCopySigningInformation") + } + return secCodeInfo + } + + private static func codeSigningCertificates(forStaticCode secStaticCode: SecStaticCode) throws -> [SecCertificate] { + guard + let secCodeInfo = try secCodeInfo(forStaticCode: secStaticCode), + let secCertificates = secCodeInfo[kSecCodeInfoCertificates as String] as? [SecCertificate] else { return [] } + return secCertificates + } +} diff --git a/Stats.xcodeproj/xcshareddata/xcschemes/SMC.xcscheme b/Stats.xcodeproj/xcshareddata/xcschemes/SMC.xcscheme index acf91b28..8fb3609a 100644 --- a/Stats.xcodeproj/xcshareddata/xcschemes/SMC.xcscheme +++ b/Stats.xcodeproj/xcshareddata/xcschemes/SMC.xcscheme @@ -1,6 +1,6 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 639 + 640 Description Simple macOS system monitor in your menu bar LSApplicationCategoryType diff --git a/Widgets/Supporting Files/Info.plist b/Widgets/Supporting Files/Info.plist index e01c1cd8..2f0de2ec 100644 --- a/Widgets/Supporting Files/Info.plist +++ b/Widgets/Supporting Files/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 2.11.20 CFBundleVersion - 639 + 640 NSExtension NSExtensionPointIdentifier