Hi,
> […] test the attached patch […]
Attached, sorry.
--
Clemens Lang
Index: certsync.m
===================================================================
--- certsync.m (revision 120592)
+++ certsync.m (working copy)
@@ -25,6 +25,8 @@
* OTHER DEALINGS IN THE SOFTWARE.
*/
+#import <Availability.h>
+
#import <Foundation/Foundation.h>
#import <Security/Security.h>
@@ -60,53 +62,105 @@
int nsvfprintf (FILE *stream, NSString *format, va_list args) {
int retval;
-
+
NSString *str;
str = (NSString *) CFStringCreateWithFormatAndArguments(NULL, NULL, (CFStringRef) format, args);
retval = fprintf(stream, "%s", [str UTF8String]);
[str release];
-
+
return retval;
}
int nsfprintf (FILE *stream, NSString *format, ...) {
va_list ap;
int retval;
-
+
va_start(ap, format);
{
retval = nsvfprintf(stream, format, ap);
}
va_end(ap);
-
+
return retval;
}
int nsprintf (NSString *format, ...) {
va_list ap;
int retval;
-
+
va_start(ap, format);
{
retval = nsvfprintf(stderr, format, ap);
}
va_end(ap);
-
+
return retval;
}
/**
- * Verify that the root certificate trusts itself; this filters out certificates that
- * are still marked as trusted by the OS, but are expired or otherwise unusable.
+ * Wrapper method to retrieve the common name (CN) given a @code SecCertificateRef. If retrieving
+ * the CN isn't supported on this platform, returns @code NO, otherwise @code YES and points the
+ * given string ref @a subject to a string containing the common name. If an error occurs, subject
+ * is a NULL reference and *subjectError contains more information about the type of failure.
+ *
+ * @param cert A SecCertificateRef to the certificate for which the CN should be retrieved
+ * @param subject A pointer to a CFStringRef that will hold the CN if the retrieval is successful.
+ * @param subjectError A pointer to an NSError* that will hold an error message if an error occurs.
+ * @return BOOL indicating whether this system supports retrieving CNs from certificates
+ */
+static BOOL GetCertSubject(SecCertificateRef cert, CFStringRef *subject, NSError **subjectError) {
+ if (SecCertificateCopyShortDescription != NULL /* 10.7 */) {
+ *subject = PLCFAutorelease(SecCertificateCopyShortDescription(NULL, cert, (CFErrorRef *) subjectError));
+ return YES;
+ }
+
+ if (SecCertificateCopySubjectSummary != NULL /* 10.6 */) {
+ *subject = PLCFAutorelease(SecCertificateCopySubjectSummary(cert));
+ return YES;
+ }
+
+ if (SecCertificateCopyCommonName != NULL /* 10.5 */) {
+ OSStatus err;
+ if ((err = SecCertificateCopyCommonName(cert, subject)) == errSecSuccess && *subject != NULL) {
+ PLCFAutorelease(subject);
+ return YES;
+ }
+
+ /* In the case that the CN is simply unavailable, provide a more useful error code */
+ if (err == errSecSuccess) {
+ err = errSecNoSuchAttr;
+ }
+
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: @"SecCertificateCopyCommonName() failed", NSLocalizedDescriptionKey, nil];
+ *subjectError = [NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo: userInfo];
+ *subject = NULL;
+
+ return YES;
+ }
+
+ /* <= 10.4 */
+ return NO;
+}
+
+/**
+ * Verify that the root certificate is trusted by the system; this filters out
+ * certificates that are (1) expired, or (2) in the system keychain but marked
+ * as untrusted by a system administrator (or user).
+ *
+ * @param cert A @code SecCertificateRef representing a certificate to be
+ * checked.
+ *
+ * @return Returns a BOOL indicating that the certificate is trusted (@code
+ * YES), or not (@code NO).
*/
-static BOOL ValidateSelfTrust (SecCertificateRef cert) {
+static BOOL ValidateSystemTrust(SecCertificateRef cert) {
OSStatus err;
/* Create a new trust evaluation instance */
SecTrustRef trust;
{
SecPolicyRef policy = SecPolicyCreateBasicX509();
- if ((err = SecTrustCreateWithCertificates((CFTypeRef)cert, policy, &trust)) != errSecSuccess) {
+ if ((err = SecTrustCreateWithCertificates((CFTypeRef) cert, policy, &trust)) != errSecSuccess) {
/* Shouldn't happen */
nsfprintf(stderr, @"Failed to create SecTrustRef: %d\n", err);
CFRelease(policy);
@@ -114,37 +168,80 @@
}
CFRelease(policy);
}
-
- /* Set this certificate as the only (self-)anchor */
+
+ /* Allow verifying root certificates (which would otherwise be an error).
+ * Without this, intermediates added as roots aren't exported. */
{
- CFArrayRef certs = CFArrayCreate(NULL, (const void **) &cert, 1, &kCFTypeArrayCallBacks);
- if ((err = SecTrustSetAnchorCertificates(trust, certs)) != errSecSuccess) {
- nsfprintf(stderr, @"Failed to set anchor certificates on our SecTrustRef: %d\n", err);
- CFRelease(certs);
- CFRelease(trust);
+ CSSM_APPLE_TP_ACTION_FLAGS actionFlags = CSSM_TP_ACTION_LEAF_IS_CA;
+ CSSM_APPLE_TP_ACTION_DATA actionData;
+ CFDataRef cfActionData = NULL;
+
+ memset(&actionData, 0, sizeof(actionData));
+ actionData.Version = CSSM_APPLE_TP_ACTION_VERSION;
+ actionData.ActionFlags = actionFlags;
+ cfActionData = CFDataCreate(NULL, (UInt8 *) &actionData, sizeof(actionData));
+ if ((err = SecTrustSetParameters(trust, CSSM_TP_ACTION_DEFAULT, cfActionData))) {
+ nsfprintf(stderr, @"Failed to set SecTrustParameters: %d\n", err);
+ CFRelease(cfActionData);
return NO;
}
- CFRelease(certs);
+ CFRelease(cfActionData);
}
-
+
/* Evaluate the certificate trust */
SecTrustResultType rt;
if ((err = SecTrustEvaluate(trust, &rt)) != errSecSuccess) {
nsfprintf(stderr, @"SecTrustEvaluate() failed: %d\n", err);
CFRelease(trust);
+ return NO;
}
-
+
CFRelease(trust);
-
+
/* Check the result */
switch (rt) {
case kSecTrustResultUnspecified:
+ /* Reached a trusted root */
case kSecTrustResultProceed:
- /* Trusted */
+ /* User explicitly trusts */
return YES;
-
+
+ case kSecTrustResultDeny:
+ /* User explicitly distrusts */
+#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_7
+ case kSecTrustResultConfirm:
+ /* The user previously chose to ask for permission when this
+ * certificate is used. This is no longer used on modern OS X. */
+#endif
+ //nsfprintf(stderr, @"Marked as untrustable!\n");
+ return NO;
+
+ case kSecTrustResultRecoverableTrustFailure:
+ /* The chain as-is isn't trusted, but it could be trusted with some
+ * minor change (such as ignoring expired certs or adding an
+ * additional anchor). For certsync this likely means the cert was
+ * expired, which means we don't want to export it. */
+ {
+ SecTrustResultType result;
+ if ((err = SecTrustGetTrustResult(trust, &result)) != kSecTrustResultInvalid) {
+ /* This is the readable error message */
+ //cssmPerror("Certificate not trusted", err);
+ } else {
+ /* We shouldn't arrive here, because if we do we cannot
+ * give the user any info. */
+ nsfprintf(stderr, @"Recoverable untrusted: %d\n", rt);
+ }
+ }
+ return NO;
+
+ case kSecTrustResultFatalTrustFailure:
+ /* The chain is defective (e.g., ill-formatted). Don't export this. */
+ return NO;
+
default:
/* Untrusted */
+ nsfprintf(stderr, @"rt = %d\n", rt);
+ cssmPerror("CSSM verify error", err);
return NO;
}
}
@@ -162,7 +259,7 @@
NSMutableArray *trusted = nil;
CFArrayRef certs = nil;
OSStatus err;
-
+
/* Mac OS X >= 10.5 provides SecTrustSettingsCopyCertificates() */
if (SecTrustSettingsCopyCertificates != NULL) {
/* Fetch all certificates in the given domain */
@@ -171,27 +268,27 @@
PLCFAutorelease(certs);
} else if (err == errSecNoTrustSettings ) {
/* No data */
-
+
[pool release];
return [NSArray array];
} else if (err != errSecSuccess) {
/* Lookup failed */
if (outError != NULL)
*outError = [[NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo:nil] retain];
-
+
[pool release];
[*outError autorelease];
return nil;
}
-
+
/* Extract trusted roots */
trusted = [NSMutableArray arrayWithCapacity: CFArrayGetCount(certs)];
-
+
NSEnumerator *resultEnumerator = [(NSArray *)certs objectEnumerator];
id certObj;
while ((certObj = [resultEnumerator nextObject]) != nil) {
SecCertificateRef cert = (SecCertificateRef) certObj;
-
+
/* Fetch the trust settings */
CFArrayRef trustSettings = nil;
err = SecTrustSettingsCopyTrustSettings(cert, domain, &trustSettings);
@@ -202,7 +299,7 @@
} else {
PLCFAutorelease(trustSettings);
}
-
+
/* If empty, trust for everything (as per the Security Framework documentation) */
if (CFArrayGetCount(trustSettings) == 0) {
[trusted addObject: certObj];
@@ -213,10 +310,10 @@
while ((trustProps = [trustEnumerator nextObject]) != nil) {
CFNumberRef settingsResultNum;
SInt32 settingsResult;
-
+
settingsResultNum = (CFNumberRef) [trustProps objectForKey: (id) kSecTrustSettingsResult];
CFNumberGetValue(settingsResultNum, kCFNumberSInt32Type, &settingsResult);
-
+
/* If a root, add to the result set */
if (settingsResult == kSecTrustSettingsResultTrustRoot || settingsResult == kSecTrustSettingsResultTrustAsRoot) {
[trusted addObject: certObj];
@@ -248,7 +345,7 @@
trusted = [[(NSArray *) certs mutableCopy] autorelease];
CFRelease(certs);
}
-
+
/*
* Filter out any trusted certificates that can not actually be used in verification; eg, they are expired.
*
@@ -265,11 +362,21 @@
id certObj;
while ((certObj = [trustedEnumerator nextObject]) != nil) {
/* If self-trust validation fails, the certificate is expired or otherwise not useable */
- if (!ValidateSelfTrust((SecCertificateRef) certObj)) {
+ if (!ValidateSystemTrust((SecCertificateRef) certObj)) {
+ NSError *subjectError = NULL;
+ CFStringRef subject = NULL;
+
+ if (GetCertSubject((SecCertificateRef) certObj, &subject, &subjectError)) {
+ if (subject != NULL) {
+ nsfprintf(stderr, @"Removing untrusted certificate: %@\n", subject);
+ } else {
+ nsfprintf(stderr, @"Failed to extract certificate description for untrusted certificate: %@\n", subjectError);
+ }
+ }
[trusted removeObject: certObj];
}
}
-
+
[trusted retain];
[pool release];
return [trusted autorelease];
@@ -281,7 +388,7 @@
/*
* Fetch all certificates
*/
-
+
NSMutableArray *anchors = [NSMutableArray array];
NSArray *result;
NSError *error;
@@ -289,6 +396,16 @@
/* Current user */
if (userAnchors) {
+ /* Set the keychain preference domain to user, this causes
+ * ValidateSystemTrust to use the user's keychain */
+ if ((err = SecKeychainSetPreferenceDomain(kSecPreferencesDomainUser)) != errSecSuccess) {
+ CFStringRef errMsg = PLCFAutorelease(SecCopyErrorMessageString(err, NULL));
+ nsfprintf(stderr, @"Failed to set keychain preference domain: %@\n", errMsg);
+
+ [pool release];
+ return EXIT_FAILURE;
+ }
+
result = certificatesForTrustDomain(kSecTrustSettingsDomainUser, &error);
if (result != nil) {
[anchors addObjectsFromArray: result];
@@ -298,7 +415,17 @@
return EXIT_FAILURE;
}
}
-
+
+ /* Admin & System */
+ /* Causes ValidateSystemTrust to ignore the user's keychain */
+ if ((err = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem)) != errSecSuccess) {
+ CFStringRef errMsg = PLCFAutorelease(SecCopyErrorMessageString(err, NULL));
+ nsfprintf(stderr, @"Failed to set keychain preference domain: %@\n", errMsg);
+
+ [pool release];
+ return EXIT_FAILURE;
+ }
+
/* Admin */
result = certificatesForTrustDomain(kSecTrustSettingsDomainAdmin, &error);
if (result != nil) {
@@ -308,7 +435,7 @@
[pool release];
return EXIT_FAILURE;
}
-
+
/* System */
result = certificatesForTrustDomain(kSecTrustSettingsDomainSystem, &error);
if (result != nil) {
@@ -318,50 +445,27 @@
[pool release];
return EXIT_FAILURE;
}
-
- NSEnumerator *resultEnumerator = [result objectEnumerator];
+
+ NSEnumerator *resultEnumerator = [anchors objectEnumerator];
id certObj;
while ((certObj = [resultEnumerator nextObject]) != nil) {
NSError *subjectError = NULL;
CFStringRef subject = NULL;
- BOOL subjectUnsupported = NO;
- if (SecCertificateCopyShortDescription != NULL /* 10.7 */) {
- subject = PLCFAutorelease(SecCertificateCopyShortDescription(NULL, (SecCertificateRef) certObj, (CFErrorRef *) &subjectError));
-
- } else if (SecCertificateCopySubjectSummary != NULL /* 10.6 */) {
- subject = PLCFAutorelease(SecCertificateCopySubjectSummary((SecCertificateRef) certObj));
-
- } else if (SecCertificateCopyCommonName != NULL /* 10.5 */) {
- if ((err = SecCertificateCopyCommonName((SecCertificateRef) certObj, &subject)) == errSecSuccess && subject != NULL) {
- PLCFAutorelease(subject);
+ if (GetCertSubject((SecCertificateRef) certObj, &subject, &subjectError)) {
+ if (subject != NULL) {
+ nsfprintf(stderr, @"Found %@\n", subject);
} else {
- /* In the case that the CN is simply unavailable, provide a more useful error code */
- if (err == errSecSuccess)
- err = errSecNoSuchAttr;
-
- NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: @"SecCertificateCopyCommonName() failed", NSLocalizedDescriptionKey, nil];
- subjectError = [NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo: userInfo];
- subject = NULL;
- }
- } else /* <= 10.4 */ {
- subjectUnsupported = YES;
- }
-
- if (subject == NULL) {
- /* Don't print an error if fetching the subject is unsupported on the platform (eg, <= 10.4) */
- if (!subjectUnsupported)
nsfprintf(stderr, @"Failed to extract certificate description: %@\n", subjectError);
- } else {
- nsfprintf(stderr, @"Found %@\n", subject);
+ }
}
}
-
+
/*
* Perform export
*/
CFDataRef pemData;
-
+
/* Prefer the non-deprecated SecItemExport on Mac OS X >= 10.7. We use an ifdef to keep the code buildable with earlier SDKs, too. */
nsfprintf(stderr, @"Exporting certificates from the keychain\n");
if (SecItemExport != NULL) {
@@ -388,7 +492,7 @@
return EXIT_FAILURE;
}
}
-
+
[pool release];
return EXIT_SUCCESS;
}
@@ -405,14 +509,14 @@
/* Parse the command line arguments */
BOOL userAnchors = NO;
NSString *outputFile = nil;
-
+
int ch;
while ((ch = getopt(argc, argv, "hsuo:")) != -1) {
switch (ch) {
case 'u':
userAnchors = YES;
break;
-
+
case 'o':
outputFile = [NSString stringWithUTF8String: optarg];
break;
@@ -428,7 +532,7 @@
}
argc -= optind;
argv += optind;
-
+
/* Perform export */
int result = exportCertificates(userAnchors, outputFile);
_______________________________________________
macports-dev mailing list
[email protected]
https://lists.macosforge.org/mailman/listinfo/macports-dev