Skip to content

Commit

Permalink
MUCertificateChainBuilder: implement and make use of.
Browse files Browse the repository at this point in the history
  • Loading branch information
mkrautz committed Mar 29, 2012
1 parent 1125080 commit 187cce4
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 79 deletions.
6 changes: 6 additions & 0 deletions Mumble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
28234D6213F09148006B830D /* talkbutton_on@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 28234D5E13F09148006B830D /* talkbutton_on@2x.png */; };
282F6A2513620BD5008F555B /* MUCertificateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 282F6A2413620BD5008F555B /* MUCertificateController.m */; };
283363DB13EF154C00A04F04 /* MUAudioTransmissionPreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 283363DA13EF154C00A04F04 /* MUAudioTransmissionPreferencesViewController.m */; };
2836767D1525053A0015958E /* MUCertificateChainBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 2836767C1525053A0015958E /* MUCertificateChainBuilder.m */; };
2838EA0F129316C200035C5D /* MUCertificatePreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2838EA0E129316C200035C5D /* MUCertificatePreferencesViewController.m */; };
2858A580123B9E5700A82155 /* MUCertificateCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2858A57F123B9E5700A82155 /* MUCertificateCell.xib */; };
2858A5AD123BA08D00A82155 /* MUCertificateCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2858A5AC123BA08D00A82155 /* MUCertificateCell.m */; };
Expand Down Expand Up @@ -238,6 +239,8 @@
282F6A2413620BD5008F555B /* MUCertificateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MUCertificateController.m; sourceTree = "<group>"; };
283363D913EF154C00A04F04 /* MUAudioTransmissionPreferencesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MUAudioTransmissionPreferencesViewController.h; sourceTree = "<group>"; };
283363DA13EF154C00A04F04 /* MUAudioTransmissionPreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MUAudioTransmissionPreferencesViewController.m; sourceTree = "<group>"; };
2836767B1525053A0015958E /* MUCertificateChainBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MUCertificateChainBuilder.h; sourceTree = "<group>"; };
2836767C1525053A0015958E /* MUCertificateChainBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MUCertificateChainBuilder.m; sourceTree = "<group>"; };
2838EA0D129316C200035C5D /* MUCertificatePreferencesViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MUCertificatePreferencesViewController.h; sourceTree = "<group>"; };
2838EA0E129316C200035C5D /* MUCertificatePreferencesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MUCertificatePreferencesViewController.m; sourceTree = "<group>"; };
2858A57F123B9E5700A82155 /* MUCertificateCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MUCertificateCell.xib; sourceTree = "<group>"; };
Expand Down Expand Up @@ -666,6 +669,8 @@
28942D7B12456F9200C63A07 /* MUCertificateCreationView.m */,
282F6A2313620BD5008F555B /* MUCertificateController.h */,
282F6A2413620BD5008F555B /* MUCertificateController.m */,
2836767B1525053A0015958E /* MUCertificateChainBuilder.h */,
2836767C1525053A0015958E /* MUCertificateChainBuilder.m */,
28BF81D0139AFFD50078B722 /* MUCertificateDiskImportViewController.h */,
28BF81D1139AFFD50078B722 /* MUCertificateDiskImportViewController.m */,
);
Expand Down Expand Up @@ -1187,6 +1192,7 @@
28FB39D0150C1BBF00E6E522 /* PLCrashAsyncImage.c in Sources */,
28FB39E1150C1E9200E6E522 /* crash_report.pb-c.c in Sources */,
28FB39E2150C1E9200E6E522 /* protobuf-c.c in Sources */,
2836767D1525053A0015958E /* MUCertificateChainBuilder.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
2 changes: 1 addition & 1 deletion MumbleKit
33 changes: 33 additions & 0 deletions Source/Classes/MUCertificateChainBuilder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* Copyright (C) 2012 Mikkel Krautz <mikkel@krautz.dk>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of the Mumble Developers nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

@interface MUCertificateChainBuilder : NSObject
+ (NSArray *) buildChainFromPersistentRef:(NSData *)persistentRef;
@end
228 changes: 228 additions & 0 deletions Source/Classes/MUCertificateChainBuilder.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/* Copyright (C) 2012 Mikkel Krautz <mikkel@krautz.dk>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of the Mumble Developers nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#import "MUCertificateChainBuilder.h"
#include <MumbleKit/MKCertificate.h>

static NSArray *FindValidParentsForCert(SecCertificateRef cert);
static NSDictionary *GetAttrsForCert(SecCertificateRef cert);
static BOOL CertIsSelfSignedAndValid(SecCertificateRef cert);
static NSArray *BuildCertChainFromCert(SecCertificateRef cert);
static NSArray *BuildCertChainFromCertInternal(SecCertificateRef cert, BOOL *isFullChain);

// Finds any certificates in the valid parents of the `cert' certficiate.
// A valid parent is a parent that:
//
// 1. Has signed the `cert' certificate's signature.
// 2. Is valid at the current system time.
//
// The function will search the app's own keychain, and all system keychains
// when looking for parents.
static NSArray *FindValidParentsForCert(SecCertificateRef cert) {
NSDictionary *attrs = GetAttrsForCert(cert);
NSData *issuer = [attrs objectForKey:(id)kSecAttrIssuer];
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassCertificate, kSecClass,
issuer, kSecAttrSubject,
kCFBooleanTrue, kSecReturnAttributes,
kCFBooleanTrue, kSecReturnRef,
kSecMatchLimitAll, kSecMatchLimit,
nil];
NSArray *allAttrs = nil;
OSStatus err = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *) &allAttrs);
if (err != noErr) {
return nil;
}

NSMutableArray *validParents = [[NSMutableArray alloc] init];
for (NSDictionary *parentAttr in allAttrs) {
SecCertificateRef parentCertRef = (SecCertificateRef) [parentAttr objectForKey:(id)kSecValueRef];
NSData *parentData = (NSData *) SecCertificateCopyData(parentCertRef);
MKCertificate *parent = [MKCertificate certificateWithCertificate:parentData privateKey:nil];
[parentData release];

NSData *childData = (NSData *) SecCertificateCopyData(cert);
MKCertificate *child = [MKCertificate certificateWithCertificate:childData privateKey:nil];

if ([parent isValidOnDate:[NSDate date]] && [child isSignedBy:parent]) {
[validParents addObject:(id)parentCertRef];
}

CFRelease(parentCertRef);
}
if ([validParents count] == 0) {
return nil;
}

return [validParents autorelease];
}

// Extracts attributes from the `cert' certificate, along with a
// reference to the passed-in certificate.
//
// The certificate itself can be acquired by looking up the object
// with the kSecValueRef key.
static NSDictionary *GetAttrsForCert(SecCertificateRef cert) {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
(id)cert, kSecValueRef,
kCFBooleanTrue, kSecReturnRef,
kCFBooleanTrue, kSecReturnAttributes,
kSecMatchLimitOne, kSecMatchLimit,
nil];
NSDictionary *attrs = nil;
OSStatus err = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *) &attrs);
if (err != noErr) {
return nil;
}
return [attrs autorelease];
}

// Checks whether the `cert' certificate is self-signed and valid.
static BOOL CertIsSelfSignedAndValid(SecCertificateRef cert) {
NSDictionary *attrs = GetAttrsForCert(cert);
NSData *subject = [attrs objectForKey:(id)kSecAttrSubject];
NSData *issuer = [attrs objectForKey:(id)kSecAttrIssuer];
if ([subject isEqualToData:issuer]) {
NSData *data = (NSData *) SecCertificateCopyData(cert);
MKCertificate *selfSigned = [MKCertificate certificateWithCertificate:data privateKey:nil];
[data release];
if ([selfSigned isValidOnDate:[NSDate date]] && [selfSigned isSignedBy:selfSigned]) {
return YES;
}
}
return NO;
}

// BuildCertChainFromCert attempts to build a full certificate chain
// starting from `cert'.
static NSArray *BuildCertChainFromCert(SecCertificateRef cert) {
return BuildCertChainFromCertInternal(cert, NULL);
}

// BuildCertChainFromCertInternal is an internal helper function for recursively
// finding a valid certificate chain starting from `cert'.
//
// The function will recurse until a full chain is found, or no full chain was
// available to be built. (In both cases, the BuildCertChainFromCertInternal
// function returns a nil NSArray to signal to the caller that further chain
// construction is impossible.)
//
// If a full chain is found, the `isFullChain' parameter will be set to YES, and
// the function will return nil as described above.
//
// Once a full chain has been found, all recursive invocations of the function will
// return a certificate chain starting at whichever certificate they were invoked
// with, resulting in a full certificate chain being available in the outermost
// invocation.
static NSArray *BuildCertChainFromCertInternal(SecCertificateRef cert, BOOL *isFullChain) {
// When we reach a root certificate, return nil and set isFullChain = YES.
if (CertIsSelfSignedAndValid(cert)) {
if (isFullChain)
*isFullChain = YES;
return nil;
} else {
if (isFullChain)
*isFullChain = NO;
}

NSArray *parents = FindValidParentsForCert(cert);
for (id obj in parents) {
SecCertificateRef parent = (SecCertificateRef) obj;

BOOL isFull = NO;
NSArray *allParents = BuildCertChainFromCertInternal(parent, &isFull);
if (isFullChain)
*isFullChain = isFull;

// Is this a root certificate?
if (isFull && allParents == nil) {
return [NSArray arrayWithObject:(id)parent];
// Or a regular intermediate?
} else if (isFull && [allParents count] > 0) {
NSMutableArray *parents = [[NSMutableArray alloc] init];
[parents addObject:(id)parent];
[parents addObjectsFromArray:allParents];
return [parents autorelease];
}
}

return nil;
}

@implementation MUCertificateChainBuilder

// Attempts to build a valid certificate chain from the SecIdentityRef
// or SecCertificateRef identified by persistentRef.
//
// The first item in the returned array will be the item identitied by
// persistentRef (either a SecIdentityRef or a SecCertificateRef).
+ (NSArray *) buildChainFromPersistentRef:(NSData *)persistentRef {
CFTypeRef thing = NULL;
OSStatus err;

NSMutableArray *chain = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];

NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
persistentRef, kSecValuePersistentRef,
kCFBooleanTrue, kSecReturnRef,
kSecMatchLimitOne, kSecMatchLimit,
nil];
err = SecItemCopyMatching((CFDictionaryRef)query, &thing);
if (err != noErr) {
return nil;
}

CFTypeID typeID = CFGetTypeID(thing);
if (typeID == SecIdentityGetTypeID()) {
SecIdentityRef identity = (SecIdentityRef) thing;

[chain addObject:(id)identity];
CFRelease(identity);

SecCertificateRef cert = NULL;
SecIdentityCopyCertificate(identity, &cert);

NSArray *firstValidChain = BuildCertChainFromCert(cert);
[chain addObjectsFromArray:firstValidChain];

CFRelease(cert);
} else if (typeID == SecCertificateGetTypeID()) {
SecCertificateRef cert = (SecCertificateRef) thing;
[chain addObject:(id)cert];
CFRelease(cert);

NSArray *firstValidChain = BuildCertChainFromCert(cert);
[chain addObjectsFromArray:firstValidChain];
}

return chain;
}

@end
4 changes: 0 additions & 4 deletions Source/Classes/MUCertificateController.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
#import <MumbleKit/MKCertificate.h>

@interface MUCertificateController : NSObject

+ (MKCertificate *) certificateWithPersistentRef:(NSData *)persistentRef;
+ (MKCertificate *) certificateAndPrivateKeyWithPersistentRef:(NSData *)persistentRef;
+ (OSStatus) deleteCertificateWithPersistentRef:(NSData *)persistentRef;
Expand All @@ -42,7 +41,4 @@

+ (NSArray *) rawCertificates;
+ (NSArray *) allPersistentRefs;

+ (NSArray *) buildChainFromPersistentRef:(NSData *)persistentRef;

@end
72 changes: 0 additions & 72 deletions Source/Classes/MUCertificateController.m
Original file line number Diff line number Diff line change
Expand Up @@ -149,76 +149,4 @@ + (NSArray *) allPersistentRefs {
return [array autorelease];
}

// Attempts to build a certificate chain from the SecIdentityRef identified by persistentRef.
// If trust can be established for the chain, the whole trusted chain is returned. If not,
// an array with only the leaf inside it is returned.
+ (NSArray *) buildChainFromPersistentRef:(NSData *)persistentRef {
CFTypeRef thing = NULL;
SecIdentityRef leafIdentity = NULL;
SecCertificateRef leaf = NULL;
OSStatus err;

NSMutableArray *chain = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];

// First, look up the SecCertificateRef for the leaf certificate's
// persistent reference.
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
persistentRef, kSecValuePersistentRef,
kCFBooleanTrue, kSecReturnRef,
kSecMatchLimitOne, kSecMatchLimit,
nil];
err = SecItemCopyMatching((CFDictionaryRef)query, &thing);
if (err == noErr) {
if (thing == NULL || CFGetTypeID(thing) != SecIdentityGetTypeID()) {
if (thing != NULL)
CFRelease(thing);
return nil;
}
leafIdentity = (SecIdentityRef) thing;
}

// Add the leafIdentity to our chain array, and release it.
[chain addObject:(id)leafIdentity];
CFRelease(leafIdentity);

// Look up all other certificates in our keychain
NSArray *otherCerts = nil;
query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassCertificate, kSecClass,
kCFBooleanTrue, kSecReturnRef,
kSecMatchLimitAll, kSecMatchLimit,
nil];

err = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef *)&otherCerts);
if (err == noErr && otherCerts != nil) {
NSMutableArray *certificates = [[NSMutableArray alloc] initWithObjects:(id)leaf, nil];
[certificates addObjectsFromArray:otherCerts];
[certificates autorelease];

SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust = NULL;
if (SecTrustCreateWithCertificates(certificates, policy, &trust) == noErr && trust != NULL) {
SecTrustResultType result;
err = SecTrustEvaluate(trust, &result);
if (err == noErr) {
switch (result) {
case kSecTrustResultProceed:
case kSecTrustResultUnspecified: // System trusts it.
{
int ncerts = SecTrustGetCertificateCount(trust);
for (int i = 0; i < ncerts; i++) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
[chain addObject:(id)cert];
}
}
}
}
CFRelease(trust);
}
[otherCerts release];
}

return chain;
}

@end

0 comments on commit 187cce4

Please sign in to comment.