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.

This commit is contained in:
Serhiy Mytrovtsiy
2024-12-14 19:35:21 +01:00
parent 1093b27fb2
commit c10759f7a1
7 changed files with 105 additions and 16 deletions

View File

@@ -7,11 +7,11 @@
<key>CFBundleName</key>
<string>eu.exelban.Stats.SMC.Helper</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>1.0.1</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<string>2</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>SMAuthorizedClients</key>
<array>
<string>anchor apple generic and identifier &quot;eu.exelban.Stats&quot; 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)</string>

View File

@@ -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
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
LastUpgradeVersion = "1610"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
LastUpgradeVersion = "1610"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
LastUpgradeVersion = "1610"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction

View File

@@ -17,7 +17,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>639</string>
<string>640</string>
<key>Description</key>
<string>Simple macOS system monitor in your menu bar</string>
<key>LSApplicationCategoryType</key>

View File

@@ -13,7 +13,7 @@
<key>CFBundleShortVersionString</key>
<string>2.11.20</string>
<key>CFBundleVersion</key>
<string>639</string>
<string>640</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>