Merge branch 'master' of github.com:google/santa

This commit is contained in:
Russell Hancox
2015-01-05 22:04:07 -05:00
9 changed files with 140 additions and 33 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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];

View File

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

View File

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

View File

@@ -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) {