Apple schlägt auch einen anderen Weg vor, um zu authentifizieren, ob der verbindende Prozess Berechtigungen hat, um eine exponierte XPC-Methode aufzurufen.
Wenn eine Anwendung Aktionen als privilegierter Benutzer ausführen muss, installiert sie normalerweise ein HelperTool als XPC-Dienst, der als Root ausgeführt wird und von der App aufgerufen werden kann, um diese Aktionen auszuführen, anstatt die App als privilegierten Benutzer auszuführen. Die App, die den Dienst aufruft, sollte jedoch über ausreichende Berechtigungen verfügen.
ShouldAcceptNewConnection immer YES
Ein Beispiel findet sich in EvenBetterAuthorizationSample. In App/AppDelegate.m versucht es, sich mit dem HelperTool zu verbinden. Und in HelperTool/HelperTool.m wird die Funktion shouldAcceptNewConnectionkeine der zuvor angegebenen Anforderungen überprüfen. Sie wird immer YES zurückgeben:
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
// Called by our XPC listener when a new connection comes in. We configure the connection
// with our protocol and ourselves as the main object.
{
assert(listener == self.listener);
#pragma unused(listener)
assert(newConnection != nil);
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(HelperToolProtocol)];
newConnection.exportedObject = self;
[newConnection resume];
return YES;
}
Für weitere Informationen darüber, wie man dies richtig konfiguriert, siehe:
Anwendungsrechte
Es gibt jedoch eine Autorisierung, die stattfindet, wenn eine Methode des HelperTools aufgerufen wird.
Die Funktion applicationDidFinishLaunching aus App/AppDelegate.m erstellt nach dem Start der App eine leere Autorisierungsreferenz. Dies sollte immer funktionieren.
Dann wird versucht, einige Rechte zu dieser Autorisierungsreferenz hinzuzufügen, indem setupAuthorizationRights aufgerufen wird:
- (void)applicationDidFinishLaunching:(NSNotification *)note
{
[...]
err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
if (err == errAuthorizationSuccess) {
err = AuthorizationMakeExternalForm(self->_authRef, &extForm);
}
if (err == errAuthorizationSuccess) {
self.authorization = [[NSData alloc] initWithBytes:&extForm length:sizeof(extForm)];
}
assert(err == errAuthorizationSuccess);
// If we successfully connected to Authorization Services, add definitions for our default
// rights (unless they're already in the database).
if (self->_authRef) {
[Common setupAuthorizationRights:self->_authRef];
}
[self.window makeKeyAndOrderFront:self];
}
Die Funktion setupAuthorizationRights aus Common/Common.m wird die Rechte der Anwendung in der Authentifizierungsdatenbank /var/db/auth.db speichern. Beachten Sie, dass sie nur die Rechte hinzufügt, die noch nicht in der Datenbank vorhanden sind:
+ (void)setupAuthorizationRights:(AuthorizationRef)authRef
// See comment in header.
{
assert(authRef != NULL);
[Common enumerateRightsUsingBlock:^(NSString * authRightName, id authRightDefault, NSString * authRightDesc) {
OSStatus blockErr;
// First get the right. If we get back errAuthorizationDenied that means there's
// no current definition, so we add our default one.
blockErr = AuthorizationRightGet([authRightName UTF8String], NULL);
if (blockErr == errAuthorizationDenied) {
blockErr = AuthorizationRightSet(
authRef, // authRef
[authRightName UTF8String], // rightName
(__bridge CFTypeRef) authRightDefault, // rightDefinition
(__bridge CFStringRef) authRightDesc, // descriptionKey
NULL, // bundle (NULL implies main bundle)
CFSTR("Common") // localeTableName
);
assert(blockErr == errAuthorizationSuccess);
} else {
// A right already exists (err == noErr) or any other error occurs, we
// assume that it has been set up in advance by the system administrator or
// this is the second time we've run. Either way, there's nothing more for
// us to do.
}
}];
}
Die Funktion enumerateRightsUsingBlock wird verwendet, um die Berechtigungen von Anwendungen zu erhalten, die in commandInfo definiert sind:
static NSString * kCommandKeyAuthRightName = @"authRightName";
static NSString * kCommandKeyAuthRightDefault = @"authRightDefault";
static NSString * kCommandKeyAuthRightDesc = @"authRightDescription";
+ (NSDictionary *)commandInfo
{
static dispatch_once_t sOnceToken;
static NSDictionary * sCommandInfo;
dispatch_once(&sOnceToken, ^{
sCommandInfo = @{
NSStringFromSelector(@selector(readLicenseKeyAuthorization:withReply:)) : @{
kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.readLicenseKey",
kCommandKeyAuthRightDefault : @kAuthorizationRuleClassAllow,
kCommandKeyAuthRightDesc : NSLocalizedString(
@"EBAS is trying to read its license key.",
@"prompt shown when user is required to authorize to read the license key"
)
},
NSStringFromSelector(@selector(writeLicenseKey:authorization:withReply:)) : @{
kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.writeLicenseKey",
kCommandKeyAuthRightDefault : @kAuthorizationRuleAuthenticateAsAdmin,
kCommandKeyAuthRightDesc : NSLocalizedString(
@"EBAS is trying to write its license key.",
@"prompt shown when user is required to authorize to write the license key"
)
},
NSStringFromSelector(@selector(bindToLowNumberPortAuthorization:withReply:)) : @{
kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.startWebService",
kCommandKeyAuthRightDefault : @kAuthorizationRuleClassAllow,
kCommandKeyAuthRightDesc : NSLocalizedString(
@"EBAS is trying to start its web service.",
@"prompt shown when user is required to authorize to start the web service"
)
}
};
});
return sCommandInfo;
}
+ (NSString *)authorizationRightForCommand:(SEL)command
// See comment in header.
{
return [self commandInfo][NSStringFromSelector(command)][kCommandKeyAuthRightName];
}
+ (void)enumerateRightsUsingBlock:(void (^)(NSString * authRightName, id authRightDefault, NSString * authRightDesc))block
// Calls the supplied block with information about each known authorization right..
{
[self.commandInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
#pragma unused(key)
#pragma unused(stop)
NSDictionary * commandDict;
NSString * authRightName;
id authRightDefault;
NSString * authRightDesc;
// If any of the following asserts fire it's likely that you've got a bug
// in sCommandInfo.
commandDict = (NSDictionary *) obj;
assert([commandDict isKindOfClass:[NSDictionary class]]);
authRightName = [commandDict objectForKey:kCommandKeyAuthRightName];
assert([authRightName isKindOfClass:[NSString class]]);
authRightDefault = [commandDict objectForKey:kCommandKeyAuthRightDefault];
assert(authRightDefault != nil);
authRightDesc = [commandDict objectForKey:kCommandKeyAuthRightDesc];
assert([authRightDesc isKindOfClass:[NSString class]]);
block(authRightName, authRightDefault, authRightDesc);
}];
}
Dies bedeutet, dass am Ende dieses Prozesses die in commandInfo deklarierten Berechtigungen in /var/db/auth.db gespeichert werden. Beachten Sie, dass Sie dort für jede Methode, die Authentifizierung erfordert, den Berechtigungsnamen und den kCommandKeyAuthRightDefault finden können. Letzterer zeigt an, wer dieses Recht erhalten kann.
Es gibt verschiedene Bereiche, um anzugeben, wer auf ein Recht zugreifen kann. Einige davon sind in AuthorizationDB.h definiert (Sie können alle hier finden), aber zusammenfassend:
Rechteüberprüfung
In HelperTool/HelperTool.m überprüft die Funktion readLicenseKeyAuthorization, ob der Aufrufer berechtigt ist, eine solche Methode auszuführen, indem sie die Funktion checkAuthorization aufruft. Diese Funktion überprüft, ob die authData, die vom aufrufenden Prozess gesendet wird, ein korrektes Format hat, und überprüft dann, was benötigt wird, um das Recht zu erhalten, die spezifische Methode aufzurufen. Wenn alles gut geht, wird der zurückgegebene errornil sein:
- (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command
{
[...]
// First check that authData looks reasonable.
error = nil;
if ( (authData == nil) || ([authData length] != sizeof(AuthorizationExternalForm)) ) {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:paramErr userInfo:nil];
}
// Create an authorization ref from that the external form data contained within.
if (error == nil) {
err = AuthorizationCreateFromExternalForm([authData bytes], &authRef);
// Authorize the right associated with the command.
if (err == errAuthorizationSuccess) {
AuthorizationItem oneRight = { NULL, 0, NULL, 0 };
AuthorizationRights rights = { 1, &oneRight };
oneRight.name = [[Common authorizationRightForCommand:command] UTF8String];
assert(oneRight.name != NULL);
err = AuthorizationCopyRights(
authRef,
&rights,
NULL,
kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed,
NULL
);
}
if (err != errAuthorizationSuccess) {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
}
}
if (authRef != NULL) {
junk = AuthorizationFree(authRef, 0);
assert(junk == errAuthorizationSuccess);
}
return error;
}
Beachten Sie, dass die Funktion authorizationRightForCommand nur das zuvor kommentierte Objekt commandInfo überprüft, um die Anforderungen zu überprüfen, um das Recht zu erhalten, diese Methode aufzurufen. Dann wird sie AuthorizationCopyRights aufrufen, um zu überprüfen, ob es die Rechte hat, die Funktion aufzurufen (beachten Sie, dass die Flags die Interaktion mit dem Benutzer erlauben).
In diesem Fall ist kCommandKeyAuthRightDefault definiert als @kAuthorizationRuleClassAllow, um die Funktion readLicenseKeyAuthorization aufzurufen. So kann es jeder aufrufen.
DB Informationen
Es wurde erwähnt, dass diese Informationen in /var/db/auth.db gespeichert sind. Sie können alle gespeicherten Regeln mit folgendem Befehl auflisten:
Sie können alle Berechtigungskonfigurationenhier finden, aber die Kombinationen, die keine Benutzerinteraktion erfordern, wären:
'authenticate-user': 'false'
Dies ist der direkteste Schlüssel. Wenn er auf false gesetzt ist, bedeutet dies, dass ein Benutzer keine Authentifizierung bereitstellen muss, um dieses Recht zu erhalten.
Dies wird in Kombination mit einem der 2 unten oder zur Angabe einer Gruppe verwendet, zu der der Benutzer gehören muss.
'allow-root': 'true'
Wenn ein Benutzer als Root-Benutzer (der erhöhte Berechtigungen hat) arbeitet und dieser Schlüssel auf true gesetzt ist, könnte der Root-Benutzer potenziell dieses Recht ohne weitere Authentifizierung erhalten. In der Regel erfordert der Zugang zu einem Root-Benutzerstatus jedoch bereits eine Authentifizierung, sodass dies für die meisten Benutzer kein "keine Authentifizierung"-Szenario ist.
'session-owner': 'true'
Wenn auf true gesetzt, würde der Besitzer der Sitzung (der aktuell angemeldete Benutzer) automatisch dieses Recht erhalten. Dies könnte zusätzliche Authentifizierung umgehen, wenn der Benutzer bereits angemeldet ist.
'shared': 'true'
Dieser Schlüssel gewährt keine Rechte ohne Authentifizierung. Stattdessen bedeutet es, wenn er auf true gesetzt ist, dass, sobald das Recht authentifiziert wurde, es unter mehreren Prozessen geteilt werden kann, ohne dass jeder einzelne sich erneut authentifizieren muss. Aber die ursprüngliche Gewährung des Rechts würde weiterhin eine Authentifizierung erfordern, es sei denn, sie wird mit anderen Schlüsseln wie 'authenticate-user': 'false' kombiniert.
Überprüfen, ob EvenBetterAuthorization verwendet wird
Wenn Sie die Funktion [HelperTool checkAuthorization:command:] finden, ist es wahrscheinlich, dass der Prozess das zuvor erwähnte Schema für die Autorisierung verwendet:
Wenn diese Funktion Funktionen wie AuthorizationCreateFromExternalForm, authorizationRightForCommand, AuthorizationCopyRights, AuhtorizationFree aufruft, verwendet sie EvenBetterAuthorizationSample.
Überprüfen Sie die /var/db/auth.db, um festzustellen, ob es möglich ist, Berechtigungen zu erhalten, um einige privilegierte Aktionen ohne Benutzerinteraktion auszuführen.
Protokollkommunikation
Dann müssen Sie das Protokollschema finden, um eine Kommunikation mit dem XPC-Dienst herstellen zu können.
Die Funktion shouldAcceptNewConnection zeigt das exportierte Protokoll an:
Zuletzt müssen wir nur den Namen des exponierten Mach-Dienstes wissen, um eine Kommunikation mit ihm herzustellen. Es gibt mehrere Möglichkeiten, dies herauszufinden:
In der [HelperTool init], wo Sie den verwendeten Mach-Dienst sehen können: