Ignore:
Timestamp:
Aug 28, 2014, 5:22:36 PM (5 years ago)
Author:
cal@…
Message:

certsync: Don't export certificates marked as untrusted, export manually added certificates

  • provide a function that reads a certificate's subject, if any (and supported)
  • verify that a CA is trusted by the system, filtering (a) expired, and (b) explicitly untrusted ones
Location:
trunk/dports/security/certsync
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/dports/security/certsync/Portfile

    r117895 r124828  
    44
    55name                    certsync
    6 version                 1.0.7
     6version                 1.1.0
    77categories              security
    88conflicts               curl-ca-bundle
  • trunk/dports/security/certsync/files/certsync.m

    r117895 r124828  
    6161int nsvfprintf (FILE *stream, NSString *format, va_list args) {
    6262    int retval;
    63    
     63
    6464    NSString *str;
    6565    str = (NSString *) CFStringCreateWithFormatAndArguments(NULL, NULL, (CFStringRef) format, args);
    6666    retval = fprintf(stream, "%s", [str UTF8String]);
    6767    [str release];
    68    
     68
    6969    return retval;
    7070}
     
    7373    va_list ap;
    7474    int retval;
    75    
     75
    7676    va_start(ap, format);
    7777    {
     
    7979    }
    8080    va_end(ap);
    81    
     81
    8282    return retval;
    8383}
     
    8686    va_list ap;
    8787    int retval;
    88    
     88
    8989    va_start(ap, format);
    9090    {
     
    9292    }
    9393    va_end(ap);
    94    
     94
    9595    return retval;
    9696}
    9797
    9898/**
    99  * Verify that the root certificate trusts itself; this filters out certificates that
    100  * are still marked as trusted by the OS, but are expired or otherwise unusable.
     99 * Wrapper method to retrieve the common name (CN) given a @code SecCertificateRef. If retrieving
     100 * the CN isn't supported on this platform, returns @code NO, otherwise @code YES and points the
     101 * given string ref @a subject to a string containing the common name. If an error occurs, subject
     102 * is a NULL reference and *subjectError contains more information about the type of failure.
     103 *
     104 * @param cert A SecCertificateRef to the certificate for which the CN should be retrieved
     105 * @param subject A pointer to a CFStringRef that will hold the CN if the retrieval is successful.
     106 * @param subjectError A pointer to an NSError* that will hold an error message if an error occurs.
     107 * @return BOOL indicating whether this system supports retrieving CNs from certificates
    101108 */
    102 static BOOL ValidateSelfTrust (SecCertificateRef cert) {
     109static BOOL GetCertSubject(SecCertificateRef cert, CFStringRef *subject, NSError **subjectError) {
     110    if (SecCertificateCopyShortDescription != NULL /* 10.7 */) {
     111        *subject = PLCFAutorelease(SecCertificateCopyShortDescription(NULL, cert, (CFErrorRef *) subjectError));
     112        return YES;
     113    }
     114
     115    if (SecCertificateCopySubjectSummary   != NULL /* 10.6 */) {
     116        *subject = PLCFAutorelease(SecCertificateCopySubjectSummary(cert));
     117        return YES;
     118    }
     119
     120    if (SecCertificateCopyCommonName       != NULL /* 10.5 */) {
     121        OSStatus err;
     122        if ((err = SecCertificateCopyCommonName(cert, subject)) == errSecSuccess && *subject != NULL) {
     123            PLCFAutorelease(subject);
     124            return YES;
     125        }
     126
     127        /* In the case that the CN is simply unavailable, provide a more useful error code */
     128        if (err == errSecSuccess) {
     129            err = errSecNoSuchAttr;
     130        }
     131
     132        NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: @"SecCertificateCopyCommonName() failed", NSLocalizedDescriptionKey, nil];
     133        *subjectError = [NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo: userInfo];
     134        *subject = NULL;
     135
     136        return YES;
     137    }
     138
     139    /* <= 10.4 */
     140    return NO;
     141}
     142
     143/**
     144 * Verify that the root certificate is trusted by the system; this filters out
     145 * certificates that are (1) expired, or (2) in the system keychain but marked
     146 * as untrusted by a system administrator (or user).
     147 *
     148 * @param cert A @code SecCertificateRef representing a certificate to be
     149 *             checked.
     150 *
     151 * @return Returns a BOOL indicating that the certificate is trusted (@code
     152 *         YES), or not (@code NO).
     153 */
     154static BOOL ValidateSystemTrust(SecCertificateRef cert) {
    103155    OSStatus err;
    104156
    105157    /* Create a new trust evaluation instance */
    106158    SecTrustRef trust;
     159        {
     160                SecPolicyRef policy;
     161                if (SecPolicyCreateBasicX509 != NULL) /* >= 10.6 */ {
     162                        policy = SecPolicyCreateBasicX509();
     163                } else /* < 10.6 */ {
     164                        SecPolicySearchRef searchRef = NULL;
     165                        const CSSM_OID *policyOID = &CSSMOID_APPLE_X509_BASIC;
     166
     167                        if ((err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policyOID, NULL, &searchRef)) != errSecSuccess) {
     168                                cssmPerror("SecPolicySearchCreate", err);
     169                                return NO;
     170                        }
     171                        if ((err = SecPolicySearchCopyNext(searchRef, &policy))) {
     172                                cssmPerror("SecPolicySearchCopyNext", err);
     173                                return NO;
     174                        }
     175                }
     176
     177                if ((err = SecTrustCreateWithCertificates((CFTypeRef) cert, policy, &trust)) != errSecSuccess) {
     178                        /* Shouldn't happen */
     179                        nsfprintf(stderr, @"Failed to create SecTrustRef: %d\n", err);
     180                        CFRelease(policy);
     181                        return NO;
     182                }
     183
     184                CFRelease(policy);
     185        }
     186
     187    /* Allow verifying root certificates (which would otherwise be an error).
     188     * Without this, intermediates added as roots aren't exported. */
    107189    {
    108         SecPolicyRef policy = SecPolicyCreateBasicX509();
    109         if ((err = SecTrustCreateWithCertificates((CFTypeRef)cert, policy, &trust)) != errSecSuccess) {
    110             /* Shouldn't happen */
    111             nsfprintf(stderr, @"Failed to create SecTrustRef: %d\n", err);
    112             CFRelease(policy);
     190        CSSM_APPLE_TP_ACTION_FLAGS actionFlags = CSSM_TP_ACTION_LEAF_IS_CA;
     191        CSSM_APPLE_TP_ACTION_DATA actionData;
     192        CFDataRef cfActionData = NULL;
     193
     194        memset(&actionData, 0, sizeof(actionData));
     195        actionData.Version = CSSM_APPLE_TP_ACTION_VERSION;
     196        actionData.ActionFlags = actionFlags;
     197        cfActionData = CFDataCreate(NULL, (UInt8 *) &actionData, sizeof(actionData));
     198        if ((err = SecTrustSetParameters(trust, CSSM_TP_ACTION_DEFAULT, cfActionData))) {
     199            nsfprintf(stderr, @"Failed to set SecTrustParameters: %d\n", err);
     200            CFRelease(cfActionData);
    113201            return NO;
    114202        }
    115         CFRelease(policy);
    116     }
    117    
    118     /* Set this certificate as the only (self-)anchor */
    119     {
    120         CFArrayRef certs = CFArrayCreate(NULL, (const void **) &cert, 1, &kCFTypeArrayCallBacks);
    121         if ((err = SecTrustSetAnchorCertificates(trust, certs)) != errSecSuccess) {
    122             nsfprintf(stderr, @"Failed to set anchor certificates on our SecTrustRef: %d\n", err);
    123             CFRelease(certs);
    124             CFRelease(trust);
    125             return NO;
    126         }
    127         CFRelease(certs);
    128     }
    129    
     203        CFRelease(cfActionData);
     204    }
     205
    130206    /* Evaluate the certificate trust */
    131207    SecTrustResultType rt;
     
    133209        nsfprintf(stderr, @"SecTrustEvaluate() failed: %d\n", err);
    134210        CFRelease(trust);
    135     }
    136    
     211        return NO;
     212    }
     213
    137214    CFRelease(trust);
    138    
     215
    139216    /* Check the result */
    140217    switch (rt) {
    141218        case kSecTrustResultUnspecified:
     219            /* Reached a trusted root */
    142220        case kSecTrustResultProceed:
    143             /* Trusted */
     221            /* User explicitly trusts */
    144222            return YES;
    145            
     223
     224        case kSecTrustResultDeny:
     225            /* User explicitly distrusts */
     226#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_7
     227        case kSecTrustResultConfirm:
     228            /* The user previously chose to ask for permission when this
     229             * certificate is used. This is no longer used on modern OS X. */
     230#endif
     231            //nsfprintf(stderr, @"Marked as untrustable!\n");
     232            return NO;
     233
     234        case kSecTrustResultRecoverableTrustFailure:
     235            /* The chain as-is isn't trusted, but it could be trusted with some
     236             * minor change (such as ignoring expired certs or adding an
     237             * additional anchor). For certsync this likely means the cert was
     238             * expired, which means we don't want to export it. */
     239            return NO;
     240
     241        case kSecTrustResultFatalTrustFailure:
     242            /* The chain is defective (e.g., ill-formatted). Don't export this. */
     243            return NO;
     244
    146245        default:
    147246            /* Untrusted */
     247            nsfprintf(stderr, @"rt = %d\n", rt);
     248            cssmPerror("CSSM verify error", err);
    148249            return NO;
    149250    }
     
    163264    CFArrayRef certs = nil;
    164265    OSStatus err;
    165    
     266
    166267    /* Mac OS X >= 10.5 provides SecTrustSettingsCopyCertificates() */
    167268    if (SecTrustSettingsCopyCertificates != NULL) {
     
    172273        } else if (err == errSecNoTrustSettings ) {
    173274            /* No data */
    174        
     275
    175276            [pool release];
    176277            return [NSArray array];
     
    179280            if (outError != NULL)
    180281                *outError = [[NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo:nil] retain];
    181        
     282
    182283            [pool release];
    183284            [*outError autorelease];
    184285            return nil;
    185286        }
    186    
     287
    187288        /* Extract trusted roots */
    188289        trusted = [NSMutableArray arrayWithCapacity: CFArrayGetCount(certs)];
    189        
     290
    190291        NSEnumerator *resultEnumerator = [(NSArray *)certs objectEnumerator];
    191292        id certObj;
    192293        while ((certObj = [resultEnumerator nextObject]) != nil) {
    193294            SecCertificateRef cert = (SecCertificateRef) certObj;
    194        
     295
    195296            /* Fetch the trust settings */
    196297            CFArrayRef trustSettings = nil;
     
    203304                PLCFAutorelease(trustSettings);
    204305            }
    205        
     306
    206307            /* If empty, trust for everything (as per the Security Framework documentation) */
    207308            if (CFArrayGetCount(trustSettings) == 0) {
     
    214315                    CFNumberRef settingsResultNum;
    215316                    SInt32 settingsResult;
    216                
     317
    217318                    settingsResultNum = (CFNumberRef) [trustProps objectForKey: (id) kSecTrustSettingsResult];
    218319                    CFNumberGetValue(settingsResultNum, kCFNumberSInt32Type, &settingsResult);
    219                
     320
    220321                    /* If a root, add to the result set */
    221322                    if (settingsResult == kSecTrustSettingsResultTrustRoot || settingsResult == kSecTrustSettingsResultTrustAsRoot) {
     
    249350        CFRelease(certs);
    250351    }
    251    
     352
    252353    /*
    253354     * Filter out any trusted certificates that can not actually be used in verification; eg, they are expired.
     
    266367    while ((certObj = [trustedEnumerator nextObject]) != nil) {
    267368        /* If self-trust validation fails, the certificate is expired or otherwise not useable */
    268         if (!ValidateSelfTrust((SecCertificateRef) certObj)) {
     369        if (!ValidateSystemTrust((SecCertificateRef) certObj)) {
     370            NSError *subjectError = NULL;
     371            CFStringRef subject = NULL;
     372
     373            if (GetCertSubject((SecCertificateRef) certObj, &subject, &subjectError)) {
     374                if (subject != NULL) {
     375                    nsfprintf(stderr, @"Removing untrusted certificate: %@\n", subject);
     376                } else {
     377                    nsfprintf(stderr, @"Failed to extract certificate description for untrusted certificate: %@\n", subjectError);
     378                }
     379            }
    269380            [trusted removeObject: certObj];
    270381        }
    271382    }
    272    
     383
    273384    [trusted retain];
    274385    [pool release];
     
    282393     * Fetch all certificates
    283394     */
    284    
     395
    285396    NSMutableArray *anchors = [NSMutableArray array];
    286397    NSArray *result;
     
    290401    /* Current user */
    291402    if (userAnchors) {
     403        /* Set the keychain preference domain to user, this causes
     404         * ValidateSystemTrust to use the user's keychain */
     405        if ((err = SecKeychainSetPreferenceDomain(kSecPreferencesDomainUser)) != errSecSuccess) {
     406            CFStringRef errMsg = PLCFAutorelease(SecCopyErrorMessageString(err, NULL));
     407            nsfprintf(stderr, @"Failed to set keychain preference domain: %@\n", errMsg);
     408
     409            [pool release];
     410            return EXIT_FAILURE;
     411        }
     412
    292413        result = certificatesForTrustDomain(kSecTrustSettingsDomainUser, &error);
    293414        if (result != nil) {
     
    299420        }
    300421    }
    301    
     422
     423    /* Admin & System */
     424    /* Causes ValidateSystemTrust to ignore the user's keychain */
     425    if ((err = SecKeychainSetPreferenceDomain(kSecPreferencesDomainSystem)) != errSecSuccess) {
     426        CFStringRef errMsg = PLCFAutorelease(SecCopyErrorMessageString(err, NULL));
     427        nsfprintf(stderr, @"Failed to set keychain preference domain: %@\n", errMsg);
     428
     429        [pool release];
     430        return EXIT_FAILURE;
     431    }
     432
    302433    /* Admin */
    303434    result = certificatesForTrustDomain(kSecTrustSettingsDomainAdmin, &error);
     
    309440        return EXIT_FAILURE;
    310441    }
    311    
     442
    312443    /* System */
    313444    result = certificatesForTrustDomain(kSecTrustSettingsDomainSystem, &error);
     
    319450        return EXIT_FAILURE;
    320451    }
    321    
    322     NSEnumerator *resultEnumerator = [result objectEnumerator];
     452
     453    NSEnumerator *resultEnumerator = [anchors objectEnumerator];
    323454    id certObj;
    324455    while ((certObj = [resultEnumerator nextObject]) != nil) {
    325456        NSError *subjectError = NULL;
    326457        CFStringRef subject = NULL;
    327         BOOL subjectUnsupported = NO;
    328 
    329         if (SecCertificateCopyShortDescription != NULL /* 10.7 */) {
    330             subject = PLCFAutorelease(SecCertificateCopyShortDescription(NULL, (SecCertificateRef) certObj, (CFErrorRef *) &subjectError));
    331            
    332         } else if (SecCertificateCopySubjectSummary != NULL /* 10.6 */) {
    333             subject = PLCFAutorelease(SecCertificateCopySubjectSummary((SecCertificateRef) certObj));
    334            
    335         } else if (SecCertificateCopyCommonName != NULL /* 10.5 */) {
    336             if ((err = SecCertificateCopyCommonName((SecCertificateRef) certObj, &subject)) == errSecSuccess && subject != NULL) {
    337                 PLCFAutorelease(subject);
     458
     459        if (GetCertSubject((SecCertificateRef) certObj, &subject, &subjectError)) {
     460            if (subject != NULL) {
     461                nsfprintf(stderr, @"Found %@\n", subject);
    338462            } else {
    339                 /* In the case that the CN is simply unavailable, provide a more useful error code */
    340                 if (err == errSecSuccess)
    341                     err = errSecNoSuchAttr;
    342 
    343                 NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: @"SecCertificateCopyCommonName() failed", NSLocalizedDescriptionKey, nil];
    344                 subjectError = [NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo: userInfo];
    345                 subject = NULL;
     463                nsfprintf(stderr, @"Failed to extract certificate description: %@\n", subjectError);
    346464            }
    347         } else /* <= 10.4 */ {
    348             subjectUnsupported = YES;
    349         }
    350 
    351         if (subject == NULL) {
    352             /* Don't print an error if fetching the subject is unsupported on the platform (eg, <= 10.4) */
    353             if (!subjectUnsupported)
    354                 nsfprintf(stderr, @"Failed to extract certificate description: %@\n", subjectError);
    355         } else {
    356             nsfprintf(stderr, @"Found %@\n", subject);
    357         }
    358     }
    359    
     465        }
     466    }
     467
    360468    /*
    361469     * Perform export
    362470     */
    363471    CFDataRef pemData;
    364    
     472
    365473    /* Prefer the non-deprecated SecItemExport on Mac OS X >= 10.7. We use an ifdef to keep the code buildable with earlier SDKs, too. */
    366474    nsfprintf(stderr, @"Exporting certificates from the keychain\n");
     
    389497        }
    390498    }
    391    
     499
    392500    [pool release];
    393501    return EXIT_SUCCESS;
     
    406514    BOOL userAnchors = NO;
    407515    NSString *outputFile = nil;
    408    
     516
    409517    int ch;
    410518    while ((ch = getopt(argc, argv, "hsuo:")) != -1) {
     
    413521                userAnchors = YES;
    414522                break;
    415                
     523
    416524            case 'o':
    417525                outputFile = [NSString stringWithUTF8String: optarg];
     
    429537    argc -= optind;
    430538    argv += optind;
    431    
     539
    432540    /* Perform export  */
    433541    int result = exportCertificates(userAnchors, outputFile);
  • trunk/dports/security/certsync/files/compat.h

    r115009 r124828  
    7575    extern CFStringRef SecCertificateCopySubjectSummary (SecCertificateRef certificate) __attribute__((weak_import));
    7676    #define SecCertificateCopySubjectSummary ((CFStringRef(*)(SecCertificateRef)) NULL) /* We can't safely weak-link what we don't have */
     77
     78    /* SecPolicyCreateBasicX509() was added in 10.6 */
     79    extern SecPolicyRef SecPolicyCreateBasicX509 (void) __attribute__((weak_import));
     80    #define SecPolicyCreateBasicX509 ((SecPolicyRef(*)(void)) NULL) /* We can't safely weak-link what we don't have */
    7781#endif
    7882
Note: See TracChangeset for help on using the changeset viewer.