mirror of
https://github.com/google/santa.git
synced 2026-04-24 03:00:12 -04:00
Merge branch 'master' of github.com:google/santa
This commit is contained in:
@@ -46,6 +46,13 @@
|
||||
|
||||
# pragma mark Client Auth Settings
|
||||
|
||||
/// If set, this contains the location of a pkcs#12 certificate to be used for
|
||||
/// sync authentication.
|
||||
@property(readonly) NSString *syncClientAuthCertificateFile;
|
||||
|
||||
/// Contains the password for the pkcs#12 certificate.
|
||||
@property(readonly) NSString *syncClientAuthCertificatePassword;
|
||||
|
||||
/// If set, this is the Common Name of a certificate in the System keychain to be used for
|
||||
/// sync authentication. The corresponding private key must also be in the keychain.
|
||||
@property(readonly) NSString *syncClientAuthCertificateCn;
|
||||
|
||||
@@ -27,6 +27,8 @@ static NSString * const kConfigFilePath = @"/var/db/santa/config.plist";
|
||||
|
||||
/// The keys in the config file
|
||||
static NSString * const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString * const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString * const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
static NSString * const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
|
||||
static NSString * const kClientAuthCertificateIssuerKey = @"ClientAuthCertificateIssuerCN";
|
||||
static NSString * const kServerAuthRootsDataKey = @"ServerAuthRootsData";
|
||||
@@ -68,6 +70,14 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return [NSURL URLWithString:self.configData[kSyncBaseURLKey]];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificateFile {
|
||||
return self.configData[kClientAuthCertificateFileKey];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificatePassword {
|
||||
return self.configData[kClientAuthCertificatePasswordKey];
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificateCn {
|
||||
return self.configData[kClientAuthCertificateCNKey];
|
||||
}
|
||||
@@ -141,11 +151,32 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
}
|
||||
|
||||
- (void)reloadConfigData {
|
||||
_configData = [[NSDictionary dictionaryWithContentsOfFile:kConfigFilePath] mutableCopy];
|
||||
NSError* error = nil;
|
||||
|
||||
NSData *readData = [NSData dataWithContentsOfFile:kConfigFilePath options:0 error:&error];
|
||||
|
||||
if (error) {
|
||||
fprintf(stderr, "%s\n", [[NSString stringWithFormat:@"Could not open configuration file %@: %@",
|
||||
kConfigFilePath, [error localizedDescription]] UTF8String]);
|
||||
|
||||
if (!_configData) {
|
||||
_configData = [NSMutableDictionary dictionary];
|
||||
return;
|
||||
}
|
||||
|
||||
CFErrorRef parseError = NULL;
|
||||
|
||||
NSDictionary *dictionary = (__bridge_transfer NSDictionary *)CFPropertyListCreateWithData(kCFAllocatorDefault,
|
||||
(__bridge CFDataRef)readData, kCFPropertyListImmutable, NULL, (CFErrorRef *)&parseError);
|
||||
|
||||
if (parseError) {
|
||||
fprintf(stderr, "%s\n", [[NSString stringWithFormat:@"Could not parse configuration file %@: %@",
|
||||
kConfigFilePath, [(__bridge NSError *)parseError localizedDescription]] UTF8String]);
|
||||
|
||||
_configData = [NSMutableDictionary dictionary];
|
||||
return;
|
||||
}
|
||||
|
||||
_configData = [dictionary mutableCopy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -48,6 +48,7 @@ static NSMutableDictionary *registeredCommands;
|
||||
[cmdName UTF8String], [cmd shortHelpText]];
|
||||
}
|
||||
|
||||
[helpText appendFormat:@"\nSee 'santactl help <command>' to read about a specific subcommand."];
|
||||
return helpText;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,14 @@
|
||||
/// certificate chain. This will override the trusted roots in the System Roots.
|
||||
@property(nonatomic) NSData *serverRootsPemData;
|
||||
|
||||
/// If set and client certificate authentication is needed, the pkcs#12 file will be
|
||||
/// loaded
|
||||
@property(nonatomic) NSString *clientCertFile;
|
||||
|
||||
/// If set and client certificate authentication is needed, the password being used for
|
||||
/// loading the clientCertFile
|
||||
@property(nonatomic) NSString *clientCertPassword;
|
||||
|
||||
/// If set and client certificate authentication is needed, will search the keychain for a
|
||||
/// certificate matching this common name and use that for authentication
|
||||
/// @note: Not case sensitive
|
||||
|
||||
@@ -75,19 +75,20 @@
|
||||
}
|
||||
|
||||
if (!protectionSpace.receivesCredentialSecurely) {
|
||||
LOGD(@"Protection Space: secure authentication or protocol cannot be established");
|
||||
LOGD(@"Protection Space: secure authentication or protocol cannot be established.");
|
||||
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *authMethod = [protectionSpace authenticationMethod];
|
||||
|
||||
if (authMethod == NSURLAuthenticationMethodClientCertificate && NO) {
|
||||
if (authMethod == NSURLAuthenticationMethodClientCertificate) {
|
||||
NSURLCredential *cred = [self clientCredentialForProtectionSpace:protectionSpace];
|
||||
if (cred) {
|
||||
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
|
||||
return;
|
||||
} else {
|
||||
LOGE(@"Server asks for client authentication, no usable client certificate found.");
|
||||
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
|
||||
return;
|
||||
}
|
||||
@@ -97,6 +98,7 @@
|
||||
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
|
||||
return;
|
||||
} else {
|
||||
LOGE(@"Server asks for client authentication, no usable client certificate found.");
|
||||
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
|
||||
return;
|
||||
}
|
||||
@@ -108,33 +110,60 @@
|
||||
#pragma mark Private Helpers for URLSession:didReceiveChallenge:completionHandler:
|
||||
|
||||
/// Handles the process of locating a valid client certificate for authentication.
|
||||
/// Operates in one of three modes, depending on the configuration in config.plist
|
||||
/// Operates in one of four modes, depending on the configuration in config.plist
|
||||
///
|
||||
/// Mode 1: if syncClientAuthCertificateCn is set, look for an identity in the keychain with a
|
||||
/// Mode 1: if syncClientAuthCertificateFile is set, use the identity in the pkcs file
|
||||
/// Mode 2: if syncClientAuthCertificateCn is set, look for an identity in the keychain with a
|
||||
/// matching common name and return it.
|
||||
/// Mode 2: if syncClientAuthCertificateIssuer is set, look for an identity in the keychain with a
|
||||
/// Mode 3: if syncClientAuthCertificateIssuer is set, look for an identity in the keychain with a
|
||||
/// matching issuer common name and return it.
|
||||
/// Mode 3: use the list of issuer details sent down by the server to find an identity in the
|
||||
/// Mode 4: use the list of issuer details sent down by the server to find an identity in the
|
||||
/// keychain.
|
||||
///
|
||||
/// If a valid identity cannot be found, returns nil.
|
||||
- (NSURLCredential *)clientCredentialForProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
|
||||
__block OSStatus err = errSecSuccess;
|
||||
__block SecIdentityRef _foundIdentity;
|
||||
|
||||
if (self.clientCertFile) {
|
||||
NSError *error = nil;
|
||||
|
||||
NSData *data = [NSData dataWithContentsOfFile:self.clientCertFile options:0 error:&error];
|
||||
if (error) {
|
||||
LOGE(@"Couldn't open client certificate %@ : %@", self.clientCertFile, [error localizedDescription]);
|
||||
return nil;
|
||||
}
|
||||
|
||||
CFDataRef inPKCS12Data = (__bridge CFDataRef)data;
|
||||
|
||||
SecTrustRef trust = nil;
|
||||
err = extractIdentityAndTrust(inPKCS12Data, self.clientCertPassword, &_foundIdentity, &trust);
|
||||
if (err != errSecSuccess) {
|
||||
LOGE(@"Couldn't load client certificate %@ : %d", self.clientCertFile, err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
CFRetain(_foundIdentity);
|
||||
|
||||
return [NSURLCredential credentialWithIdentity:_foundIdentity
|
||||
certificates:nil
|
||||
persistence:NSURLCredentialPersistenceForSession];
|
||||
}
|
||||
|
||||
CFArrayRef cfIdentities = NULL;
|
||||
err = SecItemCopyMatching((__bridge CFDictionaryRef)@{
|
||||
(id)kSecClass : (id)kSecClassIdentity,
|
||||
(id)kSecReturnRef : @YES,
|
||||
(id)kSecMatchLimit : (id)kSecMatchLimitAll }, (CFTypeRef *)&cfIdentities);
|
||||
|
||||
(id)kSecClass : (id)kSecClassIdentity,
|
||||
(id)kSecReturnRef : @YES,
|
||||
(id)kSecMatchLimit : (id)kSecMatchLimitAll }, (CFTypeRef *)&cfIdentities);
|
||||
|
||||
if (err != noErr) {
|
||||
LOGD(@"Client Trust: Failed to load client identities, SecItemCopyMatching returned: %d",
|
||||
(int)err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSArray *identities = CFBridgingRelease(cfIdentities);
|
||||
|
||||
__block SecIdentityRef _foundIdentity;
|
||||
|
||||
// Manually iterate through available identities to find one with an allowed issuer.
|
||||
[identities enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
SecIdentityRef identityRef = (__bridge SecIdentityRef)obj;
|
||||
@@ -142,27 +171,25 @@
|
||||
SecCertificateRef certificate = NULL;
|
||||
err = SecIdentityCopyCertificate(identityRef, &certificate);
|
||||
if (err != errSecSuccess) {
|
||||
LOGD(@"Client Trust: Failed to read certificate data: %d. Skipping identity", (int)err);
|
||||
LOGD(@"Client Trust: Failed to read certificate data: %d. Skipping identity.", (int)err);
|
||||
return;
|
||||
}
|
||||
|
||||
SNTCertificate *clientCert = [[SNTCertificate alloc] initWithSecCertificateRef:certificate];
|
||||
CFRelease(certificate);
|
||||
|
||||
|
||||
// Switch identity finding method depending on config
|
||||
if (self.clientCertCommonName) {
|
||||
if (self.clientCertCommonName && clientCert.commonName) {
|
||||
if ([clientCert.commonName compare:self.clientCertCommonName
|
||||
options:NSCaseInsensitiveSearch]) {
|
||||
LOGD(@"Client Trust: Valid client identity %@", clientCert);
|
||||
options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
||||
_foundIdentity = identityRef;
|
||||
CFRetain(_foundIdentity);
|
||||
*stop = YES;
|
||||
return; // return from enumeration block
|
||||
}
|
||||
} else if (self.clientCertIssuerCn) {
|
||||
} else if (self.clientCertIssuerCn && clientCert.issuerCommonName) {
|
||||
if ([clientCert.issuerCommonName compare:self.clientCertIssuerCn
|
||||
options:NSCaseInsensitiveSearch]) {
|
||||
LOGD(@"Client Trust: Valid client identity %@", clientCert);
|
||||
options:NSCaseInsensitiveSearch] == NSOrderedSame) {
|
||||
_foundIdentity = identityRef;
|
||||
CFRetain(_foundIdentity);
|
||||
*stop = YES;
|
||||
@@ -176,7 +203,7 @@
|
||||
[clientCert.issuerCountryName isEqual:decoder.countryName] &&
|
||||
[clientCert.issuerOrgName isEqual:decoder.organizationName] &&
|
||||
[clientCert.issuerOrgUnit isEqual:decoder.organizationalUnit]) {
|
||||
LOGD(@"Client Trust: Valid client identity %@", clientCert);
|
||||
|
||||
_foundIdentity = identityRef;
|
||||
CFRetain(_foundIdentity);
|
||||
*stop = YES;
|
||||
@@ -185,16 +212,36 @@
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
if (_foundIdentity == NULL) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSURLCredential credentialWithIdentity:_foundIdentity
|
||||
|
||||
if (_foundIdentity) {
|
||||
LOGD(@"Client Trust: Valid client identity %@.", _foundIdentity);
|
||||
return [NSURLCredential credentialWithIdentity:_foundIdentity
|
||||
certificates:nil
|
||||
persistence:NSURLCredentialPersistenceForSession];
|
||||
} else {
|
||||
LOGD(@"Client Trust: No valid identity found.");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
OSStatus extractIdentityAndTrust(CFDataRef inP12data, NSString *password,
|
||||
SecIdentityRef *identity, SecTrustRef *trust) {
|
||||
NSDictionary *options = (password ? @{(__bridge id)kSecImportExportPassphrase: password} : @{});
|
||||
|
||||
CFArrayRef items = nil;
|
||||
|
||||
OSStatus err = SecPKCS12Import(inP12data, (__bridge CFDictionaryRef) options, &items);
|
||||
|
||||
if (err == errSecSuccess) {
|
||||
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
|
||||
*identity = (SecIdentityRef) CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
|
||||
*trust = (SecTrustRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemTrust);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/// Handles the process of evaluating the server's certificate chain.
|
||||
/// Operates in one of three modes, depending on the configuration in config.plist
|
||||
///
|
||||
@@ -228,7 +275,7 @@
|
||||
// Set this array of certs as the anchors to trust.
|
||||
err = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)certRefs);
|
||||
if (err != errSecSuccess) {
|
||||
LOGE(@"Server Trust: Could not set anchor certificates");
|
||||
LOGE(@"Server Trust: Could not set anchor certificates: %d", err);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
@@ -237,7 +284,7 @@
|
||||
SecTrustResultType result = kSecTrustResultInvalid;
|
||||
err = SecTrustEvaluate(serverTrust, &result);
|
||||
if (err != errSecSuccess) {
|
||||
LOGE(@"Server Trust: Unable to evaluate certificate chain for server");
|
||||
LOGE(@"Server Trust: Unable to evaluate certificate chain for server: %d", err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,14 +77,24 @@ REGISTER_COMMAND_NAME(@"sync");
|
||||
|
||||
// Configure server auth
|
||||
if ([config syncServerAuthRootsFile]) {
|
||||
NSData *rootsData = [NSData dataWithContentsOfFile:[config syncServerAuthRootsFile]];
|
||||
NSError *error = nil;
|
||||
|
||||
NSData *rootsData = [NSData dataWithContentsOfFile:[config syncServerAuthRootsFile] options:0 error:&error];
|
||||
authURLSession.serverRootsPemData = rootsData;
|
||||
|
||||
if (rootsData == nil) {
|
||||
LOGE(@"Couldn't open server root certificate file %@ with error: %@.", [config syncServerAuthRootsFile], [error localizedDescription]);
|
||||
exit(1);
|
||||
}
|
||||
} else if ([config syncServerAuthRootsData]) {
|
||||
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
|
||||
}
|
||||
|
||||
// Configure client auth
|
||||
if ([config syncClientAuthCertificateCn]) {
|
||||
if ([config syncClientAuthCertificateFile]) {
|
||||
authURLSession.clientCertFile = [config syncClientAuthCertificateFile];
|
||||
authURLSession.clientCertPassword = [config syncClientAuthCertificatePassword];
|
||||
} else if ([config syncClientAuthCertificateCn]) {
|
||||
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
|
||||
} else if ([config syncClientAuthCertificateIssuer]) {
|
||||
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
|
||||
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
[req setHTTPBody:requestBody];
|
||||
|
||||
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
error:nil];
|
||||
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
[req setHTTPBody:requestBody];
|
||||
|
||||
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
options:0
|
||||
error:nil]];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
|
||||
NSURLResponse *response,
|
||||
NSError *error) {
|
||||
|
||||
Reference in New Issue
Block a user