+/*
+ * SecTrustSettingsSetTrustSettings convenience wrapper function.
+ */
+void SecTrustSettingsSetTrustedCertificateForSSLHost(
+ SecCertificateRef certificate,
+ CFStringRef hostname,
+ void (^result)(SecTrustSettingsResult trustResult, CFErrorRef error))
+{
+ __block CFMutableArrayRef trustSettings = NULL;
+ __block CFNumberRef trustSettingsResult = NULL;
+ __block SecTrustSettingsDomain domain = kSecTrustSettingsDomainUser;
+
+ CFDictionaryRef policyProperties = NULL;
+ CFStringRef policyOid = NULL;
+ SecPolicyRef policy = NULL;
+
+ Boolean isSelfSigned = false;
+ Boolean hasPolicyConstraint = false;
+ Boolean hasPolicyValue = false;
+ Boolean policyConstraintChanged = false;
+ CFIndex indexOfEntryWithAllowedErrorForExpiredCert = kCFNotFound;
+ CFIndex indexOfEntryWithAllowedErrorForHostnameMismatch = kCFNotFound;
+ CFIndex i, count;
+ int32_t trustSettingsResultCode = kSecTrustSettingsResultTrustAsRoot;
+ OSStatus status = errSecSuccess;
+
+ CFRetainSafe(certificate);
+ CFRetainSafe(hostname);
+ if (!certificate || !hostname) {
+ status = errSecParam;
+ } else {
+ status = SecCertificateIsSelfSigned(certificate, &isSelfSigned);
+ }
+ if (status != errSecSuccess) {
+ goto reportErr;
+ }
+ if (isSelfSigned) {
+ trustSettingsResultCode = kSecTrustSettingsResultTrustRoot;
+ }
+ trustSettingsResult = CFNumberCreate(NULL, kCFNumberSInt32Type, &trustSettingsResultCode);
+
+ /* start with the existing trust settings for this certificate, if any */
+ {
+ CFArrayRef curTrustSettings = NULL;
+ (void)SecTrustSettingsCopyTrustSettings(certificate, domain, &curTrustSettings);
+ if (curTrustSettings) {
+ trustSettings = CFArrayCreateMutableCopy(NULL, 0, curTrustSettings);
+ CFReleaseNull(curTrustSettings);
+ } else {
+ trustSettings = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ }
+ }
+ if (!trustSettings || !trustSettingsResult) {
+ status = errSecAllocate;
+ goto reportErr;
+ }
+
+ /* set up policy and value instances to trust the certificate for SSL for a given hostname */
+ policy = SecPolicyCreateSSL(true, hostname);
+ if (!policy) {
+ status = errSecInternal;
+ goto reportErr;
+ }
+ policyProperties = SecPolicyCopyProperties(policy);
+ if (!policyProperties) {
+ status = errSecInternal;
+ goto reportErr;
+ }
+ policyOid = (CFStringRef)CFDictionaryGetValue(policyProperties, kSecPolicyOid);
+ CFRetainSafe(policyOid);
+ if (!policyOid) {
+ status = errSecInternal;
+ goto reportErr;
+ }
+
+ /* look for dictionaries in the trust settings array for this policy and value */
+ count = CFArrayGetCount(trustSettings);
+ for (i=0; i < count; i++) {
+ CFDictionaryRef constraints = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, i);
+ if (!constraints) { continue; }
+ SecPolicyRef aPolicy = (SecPolicyRef)CFDictionaryGetValue(constraints, kSecTrustSettingsPolicy);
+ if (!aPolicy) { continue; }
+ CFDictionaryRef properties = SecPolicyCopyProperties(aPolicy);
+ if (!properties) { continue; }
+ CFStringRef aPolicyOid = (CFStringRef)CFDictionaryGetValue(properties, kSecPolicyOid);
+ if (aPolicyOid && kCFCompareEqualTo == CFStringCompare(aPolicyOid, policyOid, 0)) {
+ CFStringRef aPolicyString = (CFStringRef)CFDictionaryGetValue(constraints, kSecTrustSettingsPolicyString);
+ if (aPolicyString && kCFCompareEqualTo == CFStringCompare(aPolicyString, hostname, kCFCompareCaseInsensitive)) {
+ /* found existing entry */
+ CFNumberRef allowedErr = (CFNumberRef)CFDictionaryGetValue(constraints, kSecTrustSettingsAllowedError);
+ int32_t eOld = 0;
+ if (!allowedErr || !CFNumberGetValue(allowedErr, kCFNumberSInt32Type, &eOld)) {
+ eOld = CSSM_OK;
+ }
+ CFNumberRef tsResult = (CFNumberRef)CFDictionaryGetValue(constraints, kSecTrustSettingsResult);
+ int32_t rOld = 0;
+ if (!tsResult || !CFNumberGetValue(allowedErr, kCFNumberSInt32Type, &rOld)) {
+ rOld = kSecTrustSettingsResultTrustRoot;
+ }
+ if (!hasPolicyValue) { hasPolicyValue = (aPolicyString != NULL); }
+ if (!hasPolicyConstraint) { hasPolicyConstraint = true; }
+ if (eOld == CSSMERR_TP_CERT_EXPIRED) {
+ indexOfEntryWithAllowedErrorForExpiredCert = i;
+ } else if (eOld == CSSMERR_APPLETP_HOSTNAME_MISMATCH) {
+ indexOfEntryWithAllowedErrorForHostnameMismatch = i;
+ }
+ if (trustSettingsResultCode != rOld) {
+ policyConstraintChanged = true; // we are changing existing policy constraint's result
+ }
+ }
+ }
+ CFReleaseSafe(properties);
+ }
+
+ if (!hasPolicyConstraint) {
+ policyConstraintChanged = true; // we are adding a new policy constraint
+ } else if (hostname && !hasPolicyValue) {
+ policyConstraintChanged = true; // we need to add the hostname to an existing policy constraint
+ } else if ((indexOfEntryWithAllowedErrorForExpiredCert == kCFNotFound) ||
+ (indexOfEntryWithAllowedErrorForHostnameMismatch == kCFNotFound)) {
+ policyConstraintChanged = true; // we are missing one of the expected allowed-error entries for this policy
+ }
+
+ if (policyConstraintChanged) {
+ CFMutableDictionaryRef policyDict[2] = { NULL, NULL };
+ policyDict[0] = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ policyDict[1] = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ int32_t certExpiredCode = (int32_t)CSSMERR_TP_CERT_EXPIRED;
+ CFNumberRef certExpired = CFNumberCreate(NULL, kCFNumberSInt32Type, &certExpiredCode);
+ int32_t hostnameMismatchCode = (int32_t)CSSMERR_APPLETP_HOSTNAME_MISMATCH;
+ CFNumberRef hostnameMismatch = CFNumberCreate(NULL, kCFNumberSInt32Type, &hostnameMismatchCode);
+ if (!policyDict[0] || !policyDict[1] || !certExpired || !hostnameMismatch) {
+ status = errSecInternal;
+ } else {
+ /* set up entry for policy, hostname, expired cert error, and result */
+ CFDictionarySetValue(policyDict[0], kSecTrustSettingsPolicy, policy);
+ CFDictionarySetValue(policyDict[0], kSecTrustSettingsPolicyString, hostname);
+ CFDictionarySetValue(policyDict[0], kSecTrustSettingsAllowedError, certExpired);
+ CFDictionarySetValue(policyDict[0], kSecTrustSettingsResult, trustSettingsResult);
+ if (indexOfEntryWithAllowedErrorForExpiredCert != kCFNotFound) {
+ /* if we found an existing constraint for this policy, hostname, and allowed error, replace it */
+ CFArraySetValueAtIndex(trustSettings, indexOfEntryWithAllowedErrorForExpiredCert, policyDict[0]);
+ } else if (!(hasPolicyValue)) {
+ /* add a new policy constraint */
+ CFArrayAppendValue(trustSettings, policyDict[0]);
+ }
+ /* set up additional entry for policy, hostname, hostname mismatch error, and result */
+ CFDictionarySetValue(policyDict[1], kSecTrustSettingsPolicy, policy);
+ CFDictionarySetValue(policyDict[1], kSecTrustSettingsPolicyString, hostname);
+ CFDictionarySetValue(policyDict[1], kSecTrustSettingsAllowedError, hostnameMismatch);
+ CFDictionarySetValue(policyDict[1], kSecTrustSettingsResult, trustSettingsResult);
+ if (indexOfEntryWithAllowedErrorForHostnameMismatch != kCFNotFound) {
+ /* if we found an existing constraint for this policy, hostname, and allowed error, replace it */
+ CFArraySetValueAtIndex(trustSettings, indexOfEntryWithAllowedErrorForHostnameMismatch, policyDict[1]);
+ } else if (!(hasPolicyValue)) {
+ /* add a new policy constraint */
+ CFArrayAppendValue(trustSettings, policyDict[1]);
+ }
+ }
+ CFReleaseSafe(policyDict[0]);
+ CFReleaseSafe(policyDict[1]);
+ CFReleaseSafe(certExpired);
+ CFReleaseSafe(hostnameMismatch);
+ }
+
+ if (status != errSecSuccess) {
+ goto reportErr;
+ }
+ CFReleaseSafe(policyOid);
+ CFReleaseSafe(policyProperties);
+ CFReleaseSafe(policy);
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ /* add certificate to keychain first */
+ OSStatus status = SecCertificateAddToKeychain(certificate, NULL);
+ if (status == errSecSuccess || status == errSecDuplicateItem) {
+ /* this will block on authorization UI... */
+ status = SecTrustSettingsSetTrustSettings(certificate,
+ domain, trustSettings);
+ }
+ if (result) {
+ CFErrorRef error = NULL;
+ if (status) {
+ error = CFErrorCreate(NULL, kCFErrorDomainOSStatus, status, NULL);
+ }
+ int32_t tsrc;
+ if (!CFNumberGetValue(trustSettingsResult, kCFNumberSInt32Type, (int32_t*)&tsrc)) {
+ tsrc = (int32_t)kSecTrustSettingsResultUnspecified;
+ }
+ result((SecTrustSettingsResult)tsrc, error);
+ CFReleaseSafe(error);
+ }
+ CFRelease(trustSettingsResult);
+ CFRelease(trustSettings);
+ CFRelease(certificate);
+ CFRelease(hostname);
+ });
+
+ return;
+
+reportErr:
+ CFReleaseSafe(policyOid);
+ CFReleaseSafe(policyProperties);
+ CFReleaseSafe(policy);
+ CFReleaseSafe(trustSettingsResult);
+ CFReleaseSafe(trustSettings);
+ CFReleaseSafe(certificate);
+ CFReleaseSafe(hostname);
+ if (result) {
+ CFErrorRef error = CFErrorCreate(NULL, kCFErrorDomainOSStatus, status, NULL);
+ result(kSecTrustSettingsResultInvalid, error);
+ CFReleaseSafe(error);
+ }
+}