/*- * Copyright (c) 2009 - 2011 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Portions Copyright (c) 2009 - 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include "mech_locl.h" #include #include #include "krb5.h" /** * Acquire a new initial credentials using long term credentials (password, certificate). * * Credentials acquired should be free-ed with gss_release_cred() or * destroyed with (removed from storage) gss_destroy_cred(). * * Some mechanism types can not directly acquire or validate * credential (for example PK-U2U, SCRAM, NTLM or IAKERB), for those * mechanisms its instead the gss_init_sec_context() that will either acquire or * force validation of the credential. * * This function is blocking and should not be used on threads used for UI updates. * * @param desired_name name to use to acquire credential. Import the name using gss_import_name(). The type of the name has to be supported by the desired_mech used. * * @param desired_mech mechanism to use to acquire credential. GSS_C_NO_OID is not valid input and a mechanism must be selected. For example GSS_KRB5_MECHANISM, GSS_NTLM_MECHNISM or any other mechanisms supported by the implementation. See gss_indicate_mechs(). * * @param attributes CFDictionary that contains how to acquire the credential, see below for examples * * @param output_cred_handle the resulting credential handle, value is set to GSS_C_NO_CREDENTIAL on failure. * * @param error an CFErrorRef returned in case of an error, that needs to be released with CFRelease() by the caller, input can be NULL. * * @returns a gss_error code, see the CFErrorRef passed back in error for the failure message. * * attributes must contains one of the following keys * * kGSSICPassword - CFStringRef password * * kGSSICCertificate - SecIdentityRef, SecCertificate, or CFDataRef[data of a Keychain Persistent Reference] to the certificate to use with PKINIT/PKU2U * * optional keys * * kGSSCredentialUsage - one of kGSS_C_INITIATE, kGSS_C_ACCEPT, kGSS_C_BOTH, default if not given is kGSS_C_INITIATE * * kGSSICVerifyCredential - validate the credential with a trusted source that there was no MITM * * kGSSICLKDCHostname - CFStringRef hostname of LKDC hostname * * kGSSICKerberosCacheName - CFStringRef name of cache that will be created (including type) * * kGSSICSiteName - CFStringRef name of site (you are authenticating too) used for load balancing in DNS in Kerberos) * * kGSSICAppIdentifierACL - CFArrayRef[CFStringRef] prefix of bundle ID allowed to access this credential * * kGSSICCreateNewCredential - CFBooleanRef if set caller wants to create a new credential and not overwrite a credential with the same name * * kGSSICAuthenticationContext - CFBooleanRef/YES to allow authentication UI, or LAContext to pass a pre-evaluated authentication context * * * kGSSICAppleSourceApp - CFDictionaryRef application we are performing this on behalf of (only applies to AppVPN) * * Keys for kGSSICAppleSourceApp dictionary: * * - kGSSICAppleSourceAppAuditToken - audit token of process this is * preformed on behalf of, the audit_token_t is wrapped * in a CFDataRef. * - kGSSICAppleSourceAppPID - PID in a CFNumberRef of process this is * preformed on behalf of * - kGSSICAppleSourceAppUUID - UUID of the application * - kGSSICAppleSourceAppSigningIdentity - bundle/signing identity of the application * * * @ingroup gssapi */ OM_uint32 GSSAPI_LIB_FUNCTION gss_aapl_initial_cred(__nonnull const gss_name_t desired_name, __nonnull gss_const_OID desired_mech, __nullable CFDictionaryRef attributes, __nonnull gss_cred_id_t * __nullable output_cred_handle, __nullable CFErrorRef *__nullable error) { OM_uint32 major_status, minor_status; gss_buffer_desc credential; CFStringRef usage; CFTypeRef password, certificate; gss_cred_usage_t cred_usage = GSS_C_INITIATE; gss_const_OID cred_type; void *cred_value; credential.value = NULL; credential.length = 0; HEIM_WARN_BLOCKING("gss_aapl_initial_cred", warn_once); if (error) *error = NULL; if (desired_mech == GSS_C_NO_OID) return GSS_S_BAD_MECH; if (desired_name == GSS_C_NO_NAME) return GSS_S_BAD_NAME; if (output_cred_handle == NULL) return GSS_S_CALL_INACCESSIBLE_READ; *output_cred_handle = GSS_C_NO_CREDENTIAL; /* require password or certificate */ password = CFDictionaryGetValue(attributes, kGSSICPassword); certificate = CFDictionaryGetValue(attributes, kGSSICCertificate); if (password == NULL && certificate == NULL) { return GSS_S_CALL_INACCESSIBLE_READ; } /* check usage */ usage = CFDictionaryGetValue(attributes, kGSSCredentialUsage); if (usage && CFGetTypeID(usage) == CFStringGetTypeID()) { if (CFStringCompare(usage, kGSS_C_INITIATE, 0) == kCFCompareEqualTo) cred_usage = GSS_C_INITIATE; else if (CFStringCompare(usage, kGSS_C_ACCEPT, 0) == kCFCompareEqualTo) cred_usage = GSS_C_ACCEPT; else if (CFStringCompare(usage, kGSS_C_BOTH, 0) == kCFCompareEqualTo) cred_usage = GSS_C_BOTH; else return GSS_S_FAILURE; } if (gss_oid_equal(desired_mech, GSS_KRB5_MECHANISM)) { cred_value = (void *)attributes; cred_type = GSS_C_CRED_HEIMBASE; } else if (password && CFGetTypeID(password) == CFStringGetTypeID()) { char *str = rk_cfstring2cstring(password); if (str == NULL) return GSS_S_FAILURE; credential.value = str; credential.length = strlen(str); cred_value = &credential; cred_type = GSS_C_CRED_PASSWORD; } else if (password && CFGetTypeID(password) == CFDataGetTypeID()) { credential.value = malloc(CFDataGetLength(password)); if (credential.value == NULL) return GSS_S_FAILURE; credential.length = CFDataGetLength(password); memcpy(credential.value, CFDataGetBytePtr(password), CFDataGetLength(password)); cred_value = &credential; cred_type = GSS_C_CRED_PASSWORD; } else if (certificate && CFGetTypeID(certificate) == SecIdentityGetTypeID()) { cred_value = rk_UNCONST(certificate); cred_type = GSS_C_CRED_SecIdentity; } else if (certificate && CFGetTypeID(certificate) == SecCertificateGetTypeID()) { cred_value = rk_UNCONST(certificate); cred_type = GSS_C_CRED_SecIdentity; } else return GSS_S_FAILURE; major_status = gss_acquire_cred_ext(&minor_status, desired_name, cred_type, cred_value, GSS_C_INDEFINITE, desired_mech, cred_usage, output_cred_handle); if (credential.length) { memset(credential.value, 0, credential.length); free(credential.value); } if (major_status && error) { *error = _gss_mg_create_cferror(major_status, minor_status, desired_mech); return major_status; } return major_status; } /** * Change pasword for a gss name * * @param name name to change password for * @param mech mechanism to use * @param attributes old and new password (kGSSChangePasswordOldPassword and kGSSChangePasswordNewPassword) and other attributes. * @param error if not NULL, error might be set case function doesn't * return GSS_S_COMPLETE, in that case is must be released with * CFRelease(). * * @returns returns GSS_S_COMPLETE on success, error might be set if passed in. * * @ingroup gssapi */ OM_uint32 GSSAPI_LIB_FUNCTION gss_aapl_change_password(__nonnull const gss_name_t name, __nonnull gss_const_OID mech, __nonnull CFDictionaryRef attributes, __nullable CFErrorRef *__nullable error) { struct _gss_mechanism_name *mn = NULL; char *oldpw = NULL, *newpw = NULL; OM_uint32 maj_stat, min_stat; gssapi_mech_interface m; CFStringRef old, new; _gss_load_mech(); m = __gss_get_mechanism(mech); if (m == NULL) { maj_stat = GSS_S_BAD_MECH; min_stat = 0; goto out; } if (m->gm_aapl_change_password == NULL) { maj_stat = GSS_S_UNAVAILABLE; min_stat = 0; goto out; } maj_stat = _gss_find_mn(&min_stat, (struct _gss_name *)name, mech, &mn); if (maj_stat != GSS_S_COMPLETE) goto out; old = CFDictionaryGetValue(attributes, kGSSChangePasswordOldPassword); new = CFDictionaryGetValue(attributes, kGSSChangePasswordNewPassword); heim_assert(old != NULL, "old password missing"); heim_assert(new != NULL, "new password missing"); oldpw = rk_cfstring2cstring(old); newpw = rk_cfstring2cstring(new); if (oldpw == NULL || newpw == NULL) { maj_stat = GSS_S_FAILURE; min_stat = 0; goto out; } maj_stat = m->gm_aapl_change_password(&min_stat, mn->gmn_name, oldpw, newpw); if (maj_stat) _gss_mg_error(m, min_stat); out: if (maj_stat && error) *error = _gss_mg_create_cferror(maj_stat, min_stat, mech); if (oldpw) { memset(oldpw, 0, strlen(oldpw)); free(oldpw); } if (newpw) { memset(newpw, 0, strlen(newpw)); free(newpw); } return maj_stat; } /** * Returns a copy of the UUID of the GSS credential * * @param credential credential * * @returns CFUUIDRef that can be used to turn into a credential, * normal CoreFoundaton rules for rules applies so the CFUUIDRef needs * to be released. * * @ingroup gssapi */ __nullable CFUUIDRef GSSCredentialCopyUUID(gss_cred_id_t __nonnull credential) { OM_uint32 major, minor; gss_buffer_set_t dataset = GSS_C_NO_BUFFER_SET; krb5_error_code ret; krb5_uuid uuid; CFUUIDBytes cfuuid; major = gss_inquire_cred_by_oid(&minor, credential, GSS_C_NT_UUID, &dataset); if (major || dataset->count != 1) { gss_release_buffer_set(&minor, &dataset); return NULL; } if (dataset->elements[0].length != 36) { gss_release_buffer_set(&minor, &dataset); return NULL; } ret = krb5_string_to_uuid(dataset->elements[0].value, uuid); gss_release_buffer_set(&minor, &dataset); if (ret) return NULL; memcpy(&cfuuid, uuid, sizeof(uuid)); return CFUUIDCreateFromUUIDBytes(NULL, cfuuid); } /** * Returns a GSS credential for a given UUID if the credential exists. * * @param uuid the UUID of the credential to fetch * * @returns a gss_cred_id_t, normal CoreFoundaton rules for rules * applies so the CFUUIDRef needs to be released with either CFRelease() or gss_release_name(). * * @ingroup gssapi */ __nullable gss_cred_id_t GSSAPI_LIB_FUNCTION GSSCreateCredentialFromUUID(__nonnull CFUUIDRef uuid) { OM_uint32 min_stat, maj_stat; gss_cred_id_t cred; CFStringRef name; gss_name_t gname; name = CFUUIDCreateString(NULL, uuid); if (name == NULL) return NULL; gname = GSSCreateName(name, GSS_C_NT_UUID, NULL); CFRelease(name); if (gname == NULL) return NULL; maj_stat = gss_acquire_cred(&min_stat, gname, GSS_C_INDEFINITE, NULL, GSS_C_INITIATE, &cred, NULL, NULL); gss_release_name(&min_stat, &gname); if (maj_stat != GSS_S_COMPLETE) return NULL; return cred; } static CFStringRef CopyFoldString(CFStringRef host) { CFMutableStringRef string = CFStringCreateMutableCopy(NULL, 0, host); static dispatch_once_t once; static CFLocaleRef locale; dispatch_once(&once, ^{ locale = CFLocaleCreate(NULL, CFSTR("C")); }); CFStringFold(string, kCFCompareCaseInsensitive, locale); return string; } static bool FoldedHostName(CFStringRef stringOrURL, CFStringRef *scheme, CFStringRef *host, CFStringRef *path) { CFRange range; *scheme = NULL; *host = NULL; *path = NULL; range = CFStringFind(stringOrURL, CFSTR(":"), 0); if (range.location != kCFNotFound) { CFURLRef url; url = CFURLCreateWithString(NULL, stringOrURL, NULL); if (url) { CFStringRef hn = CFURLCopyHostName(url); if (hn == NULL) { *host = CFSTR(""); } else { *host = CopyFoldString(hn); CFRelease(hn); if (*host == NULL) { CFRelease(url); return false; } } *scheme = CFURLCopyScheme(url); if (*scheme == NULL) *scheme = CFSTR(""); *path = CFURLCopyPath(url); if (*path == NULL || CFStringCompare(*path, CFSTR(""), 0) == kCFCompareEqualTo) { if (*path) CFRelease(*path); *path = CFSTR("/"); } CFRelease(url); } } if (*host == NULL) { *host = CopyFoldString(stringOrURL); if (*scheme) CFRelease(*scheme); *scheme = CFSTR("any"); *path = CFSTR("/"); } return true; } /* * */ void GSSRuleAddMatch(__nonnull CFMutableDictionaryRef rules, __nonnull CFStringRef host, __nonnull CFStringRef value) { CFStringRef scheme = NULL, hostname = NULL, path = NULL; CFMutableDictionaryRef match; if (!FoldedHostName(host, &scheme, &hostname, &path)) return; match = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (match == NULL) goto out; CFDictionarySetValue(match, CFSTR("scheme"), scheme); CFDictionarySetValue(match, CFSTR("path"), path); CFDictionarySetValue(match, CFSTR("value"), value); CFArrayRef array = CFDictionaryGetValue(rules, hostname); CFMutableArrayRef mutable; if (array) { mutable = CFArrayCreateMutableCopy(NULL, 0, array); } else { mutable = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); } if (mutable) { CFIndex n, count = CFArrayGetCount(mutable); for (n = 0; n < count; n++) { CFDictionaryRef item = (CFDictionaryRef)CFArrayGetValueAtIndex(mutable, n); CFStringRef p = CFDictionaryGetValue(item, CFSTR("path")); CFStringRef s = CFDictionaryGetValue(item, CFSTR("scheme")); if (CFStringCompare(s, scheme, kCFCompareCaseInsensitive) == kCFCompareLessThan) continue; if (CFStringHasPrefix(path, p)) { CFArrayInsertValueAtIndex(mutable, n, match); break; } } if (n >= count) CFArrayAppendValue(mutable, match); CFDictionarySetValue(rules, hostname, mutable); CFRelease(mutable); } out: CFRelease(scheme); CFRelease(hostname); CFRelease(path); if (match) CFRelease(match); } /* * host is a URL string or hostname string */ __nullable CFStringRef GSSRuleGetMatch(__nonnull CFDictionaryRef rules, __nonnull CFStringRef hostname) { CFStringRef scheme = NULL, hostFolded = NULL, path = NULL; CFTypeRef result = NULL; const char *p; if (!FoldedHostName(hostname, &scheme, &hostFolded, &path)) return NULL; char *host = rk_cfstring2cstring(hostFolded); CFRelease(hostFolded); if (host == NULL) { CFRelease(path); return NULL; } if (host[0] == '\0') { CFRelease(scheme); free(host); CFRelease(path); return NULL; } for (p = host; p != NULL && result == NULL; p = strchr(p + 1, '.')) { CFStringRef partial = CFStringCreateWithCString(NULL, p, kCFStringEncodingUTF8); CFArrayRef array = (CFArrayRef)CFDictionaryGetValue(rules, partial); CFRelease(partial); if (array) { CFIndex n, count = CFArrayGetCount(array); for (n = 0; n < count && result == NULL; n++) { CFDictionaryRef item = (CFDictionaryRef)CFArrayGetValueAtIndex(array, n); CFStringRef matchScheme = CFDictionaryGetValue(item, CFSTR("scheme")); if (CFStringCompare(scheme, matchScheme, kCFCompareCaseInsensitive) != kCFCompareEqualTo && CFStringCompare(CFSTR("any"), matchScheme, kCFCompareCaseInsensitive) != kCFCompareEqualTo) continue; CFStringRef matchPath = CFDictionaryGetValue(item, CFSTR("path")); if (CFStringHasPrefix(path, matchPath)) result = CFDictionaryGetValue(item, CFSTR("value")); } } } CFRelease(scheme); free(host); CFRelease(path); return result; } /** * Create a GSS name from a buffer and type. * * @param name name buffer describing a credential, can be either a CFDataRef or CFStringRef of a name. * @param name_type on OID of the GSS_C_NT_* OIDs constants specifiy the name type. * @param error if an error happen, this may be set to a CFErrorRef describing the failure futher. * * @returns returns gss_name_t or NULL on failure. Must be freed using gss_release_name() or CFRelease(). Follows CoreFoundation Create/Copy rule. * * @ingroup gssapi */ __nullable gss_name_t GSSCreateName(__nonnull CFTypeRef name, __nonnull gss_const_OID name_type, __nullable CFErrorRef *__nullable error) { OM_uint32 maj_stat, min_stat; gss_buffer_desc buffer; int free_data = 0; gss_name_t n; if (error) *error = NULL; if (CFGetTypeID(name) == CFStringGetTypeID()) { buffer.value = rk_cfstring2cstring(name); if (buffer.value == NULL) return GSS_S_FAILURE; buffer.length = strlen((char *)buffer.value); free_data = 1; } else if (CFGetTypeID(name) == CFDataGetTypeID()) { buffer.value = (void *)CFDataGetBytePtr(name); buffer.length = (OM_uint32)CFDataGetLength(name); } else { return GSS_C_NO_NAME; } maj_stat = gss_import_name(&min_stat, &buffer, name_type, &n); if (free_data) free(buffer.value); if (maj_stat) return GSS_C_NO_NAME; return n; } /** * Copy the name describing the credential * * @param cred the credential to get the name from * * @returns returns gss_name_t or NULL on failure. Must be freed using gss_release_name() or CFRelease(). Follows CoreFoundation Create/Copy rule. * * @ingroup gssapi */ __nullable gss_name_t GSSCredentialCopyName(__nonnull gss_cred_id_t cred) { OM_uint32 major, minor; gss_name_t name; major = gss_inquire_cred(&minor, cred, &name, NULL, NULL, NULL); if (major != GSS_S_COMPLETE) return NULL; return name; } /** * Return the lifetime (in seconds) left of the credential. * * @param cred the credential to get the name from * * @returns the lifetime of the credentials. 0 on failure and * GSS_C_INDEFINITE on credentials that never expire. * * @ingroup gssapi */ OM_uint32 GSSCredentialGetLifetime(__nonnull gss_cred_id_t cred) { OM_uint32 maj_stat, min_stat; OM_uint32 lifetime; maj_stat = gss_inquire_cred(&min_stat, cred, NULL, &lifetime, NULL, NULL); if (maj_stat != GSS_S_COMPLETE) return 0; return lifetime; } /** * Returns a string that is suitable for displaying to user, must not * be used for verify subjects on an ACLs. * * @param name to get a display strings from * * @returns a string that is printable. Follows CoreFoundation Create/Copy rule. * * @ingroup gssapi */ __nullable CFStringRef GSSNameCreateDisplayString(__nonnull gss_name_t name) { OM_uint32 maj_stat, min_stat; gss_buffer_desc buffer; CFStringRef str; maj_stat = gss_display_name(&min_stat, name, &buffer, NULL); if (maj_stat != GSS_S_COMPLETE) return NULL; str = CFStringCreateWithBytes(NULL, (const UInt8 *)buffer.value, buffer.length, kCFStringEncodingUTF8, false); gss_release_buffer(&min_stat, &buffer); return str; } /* * Create a CFErrorRef from GSS-API major and minor status code. * * @param major_status Major status code returned by the funcation that failed * @param major_status Major status code returned by the funcation that failed * @param mech Mechanism passed in, if not available GSS_C_NO_OID should be used * * @returns a CFErrorRef in the domain org.h5l.GSS domain * * @ingroup gssapi */ __nullable CFErrorRef GSSCreateError(__nonnull gss_const_OID mech, OM_uint32 major_status, OM_uint32 minor_status) { return _gss_mg_create_cferror(major_status, minor_status, mech); } /* deprecated */ OM_uint32 GSSCredGetLifetime(__nonnull gss_cred_id_t cred) { return GSSCredentialGetLifetime(cred); } gss_name_t GSSCredCopyName(__nonnull gss_cred_id_t cred) { return GSSCredentialCopyName(cred); }