#include "NetworkAuthenticationHelper.h"
#include "NetworkAuthenticationHelperGSS.h"
#include "KerberosHelper.h"
#include "KerberosHelperContext.h"
#include <Heimdal/krb5.h>
#include <Heimdal/hx509.h>
#include <GSS/gssapi.h>
#include <GSS/gssapi_ntlm.h>
#include <GSS/gssapi_krb5.h>
#include <GSS/gssapi_spi.h>
#include <Kernel/gssd/gssd_mach_types.h>
#include <CoreFoundation/CFRuntime.h>
#include <Security/Security.h>
#include <Security/SecCertificatePriv.h>
#include <CommonCrypto/CommonDigest.h>
#include <CoreServices/CoreServices.h>
#include <CoreServices/CoreServicesPriv.h>
#include <os/log.h>
#include "DeconstructServiceName.h"
#include "utils.h"
static void add_user_selections(NAHRef na);
const CFStringRef kNAHServiceAFPServer = CFSTR("afpserver");
const CFStringRef kNAHServiceCIFSServer = CFSTR("cifs");
const CFStringRef kNAHServiceHostServer = CFSTR("host");
const CFStringRef kNAHServiceVNCServer = CFSTR("vnc");
static const char *nah_created = "nah-created";
static bool nah_use_gss_uam = true;
static bool nah_vnc_support_iakerb = true;
static dispatch_once_t init_globals;
enum NAHMechType {
NO_MECH = 0,
GSS_KERBEROS,
GSS_KERBEROS_U2U,
GSS_KERBEROS_IAKERB,
GSS_KERBEROS_PKU2U,
GSS_NTLM,
GSS_SPNEGO
};
struct NAHSelectionData {
CFRuntimeBase base;
NAHRef na;
int have_cred;
enum NAHMechType mech;
CFStringRef client;
CFStringRef clienttype;
CFStringRef server;
CFStringRef servertype;
SecIdentityRef certificate;
bool spnego;
CFStringRef inferredLabel;
krb5_ccache ccache;
};
struct NAHData {
CFRuntimeBase base;
CFAllocatorRef alloc;
CFMutableStringRef hostname;
CFMutableStringRef lchostname;
CFStringRef service;
CFStringRef username;
CFStringRef specificname;
CFDictionaryRef servermechs;
CFStringRef spnegoServerName;
CFArrayRef x509identities;
CFStringRef password;
CFArrayRef mechs;
dispatch_queue_t q;
dispatch_queue_t bgq;
krb5_context context;
hx509_context hxctx;
CFMutableArrayRef selections;
};
#define CFRELEASE(x) do { if ((x)) { CFRelease((x)); (x) = NULL; } } while(0)
CFStringRef
NAHCopyMMeUserNameFromCertificate(SecCertificateRef cert)
{
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
char str[CC_SHA1_DIGEST_LENGTH * 2 + 1];
CFDataRef certData;
CC_SHA1_CTX ctx;
char *cpOut;
unsigned dex;
certData = SecCertificateCopyData(cert);
CFRelease(cert);
if (NULL == certData)
return NULL;
CC_SHA1_Init(&ctx);
CC_SHA1_Update(&ctx, CFDataGetBytePtr(certData), (CC_LONG)CFDataGetLength(certData));
CC_SHA1_Final(digest, &ctx);
CFRelease(certData);
cpOut = str;
for(dex = 0; dex < CC_SHA1_DIGEST_LENGTH; dex++) {
snprintf(cpOut, 3, "%02X", (unsigned)(digest[dex]));
cpOut += 2;
}
return CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII);
}
static char *
cf2cstring(CFStringRef inString)
{
char *string = NULL;
CFIndex length;
string = (char *) CFStringGetCStringPtr(inString, kCFStringEncodingUTF8);
if (string)
return strdup(string);
length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(inString),
kCFStringEncodingUTF8) + 1;
string = malloc (length);
if (string == NULL)
return NULL;
if (!CFStringGetCString(inString, string, length, kCFStringEncodingUTF8)) {
free (string);
return NULL;
}
return string;
}
static os_log_t
na_get_oslog(void)
{
static dispatch_once_t onceToken;
static os_log_t log;
dispatch_once(&onceToken, ^{
log = os_log_create("com.apple.KerberosHelper", "KerberosHelper");
});
return log;
}
static void
naselrelease(NAHSelectionRef nasel)
{
CFRELEASE(nasel->client);
CFRELEASE(nasel->clienttype);
CFRELEASE(nasel->server);
CFRELEASE(nasel->servertype);
if (nasel->ccache)
krb5_cc_close(nasel->na->context, nasel->ccache);
CFRELEASE(nasel->certificate);
CFRELEASE(nasel->inferredLabel);
}
static CFStringRef naseldebug(NAHSelectionRef nasel) CF_RETURNS_RETAINED;
static CFStringRef naselformatting(CFTypeRef cf, CFDictionaryRef formatOptions) CF_RETURNS_RETAINED;
static CFStringRef
naseldebug(NAHSelectionRef nasel)
{
CFStringRef mech = NAHSelectionGetInfoForKey(nasel, kNAHMechanism);
CFStringRef innermech = NAHSelectionGetInfoForKey(nasel, kNAHInnerMechanism);
return CFStringCreateWithFormat(NULL, NULL,
CFSTR("<NetworkAuthenticationSelection: "
"%@<%@>, %@ %@ spnego: %s>"),
mech, innermech,
nasel->client,
nasel->server,
nasel->spnego ? "yes" : "no");
}
static CFStringRef
naselformatting(CFTypeRef cf, CFDictionaryRef formatOptions)
{
return naseldebug((NAHSelectionRef)cf);
}
const CFStringRef kNAHErrorDomain = CFSTR("com.apple.NetworkAuthenticationHelper");
static bool
updateError(CFAllocatorRef alloc, CFErrorRef *error, CFIndex errorcode, CFStringRef fmt, ...)
{
const void *keys[1] = { kCFErrorDescriptionKey };
void *values[1];
va_list va;
if (error == NULL)
return false;
va_start(va, fmt);
values[0] = (void *)CFStringCreateWithFormatAndArguments(alloc, NULL, fmt, va);
va_end(va);
if (values[0] == NULL) {
*error = NULL;
return false;
}
os_log(na_get_oslog(), "NAH: error: %@", values[0]);
*error = CFErrorCreateWithUserInfoKeysAndValues(alloc, kNAHErrorDomain, errorcode,
(const void * const *)keys,
(const void * const *)values, 1);
CFRelease(values[0]);
return true;
}
static struct {
CFStringRef name;
enum NAHMechType mech;
gss_OID oid;
} mechs[] = {
{ CFSTR("Kerberos"), GSS_KERBEROS, GSS_KRB5_MECHANISM },
{ CFSTR("KerberosUser2User"), GSS_KERBEROS_U2U, NULL },
{ CFSTR("PKU2U"), GSS_KERBEROS_PKU2U, GSS_PKU2U_MECHANISM },
{ CFSTR("IAKerb"), GSS_KERBEROS_IAKERB, GSS_IAKERB_MECHANISM },
{ CFSTR("NTLM"), GSS_NTLM, GSS_NTLM_MECHANISM },
{ CFSTR("SPNEGO"), GSS_SPNEGO, GSS_SPNEGO_MECHANISM },
{ CFSTR("SPENGO"), GSS_SPNEGO, GSS_SPNEGO_MECHANISM }
};
static enum NAHMechType
name2mech(CFStringRef name)
{
size_t n;
if (name == NULL)
return NO_MECH;
for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) {
if (CFStringCompare(mechs[n].name, name, kCFCompareCaseInsensitive) == kCFCompareEqualTo)
return mechs[n].mech;
}
return NO_MECH;
}
static gss_OID
name2oid(CFStringRef name)
{
size_t n;
if (name == NULL)
return NULL;
for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) {
if (CFStringCompare(mechs[n].name, name, kCFCompareCaseInsensitive) == kCFCompareEqualTo)
return mechs[n].oid;
}
return NULL;
}
static CFStringRef
mech2name(enum NAHMechType mech)
{
size_t n;
for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) {
if (mechs[n].mech == mech)
return mechs[n].name;
}
return NULL;
}
static gss_OID
mech2oid(enum NAHMechType mech)
{
size_t n;
for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) {
if (mechs[n].mech == mech)
return mechs[n].oid;
}
return GSS_C_NO_OID;
}
static CFTypeID
NAHSelectionGetTypeID(void)
{
static CFTypeID naselid = _kCFRuntimeNotATypeID;
static dispatch_once_t inited;
dispatch_once(&inited, ^{
static const CFRuntimeClass naselclass = {
0,
"NetworkAuthenticationSelection",
NULL,
NULL,
(void(*)(CFTypeRef))naselrelease,
NULL,
NULL,
naselformatting,
(CFStringRef (*)(CFTypeRef))naseldebug
};
naselid = _CFRuntimeRegisterClass(&naselclass);
});
return naselid;
}
static NAHSelectionRef
NAHSelectionAlloc(NAHRef na)
{
CFTypeID id = NAHSelectionGetTypeID();
NAHSelectionRef nasel;
if (id == _kCFRuntimeNotATypeID)
return NULL;
nasel = (NAHSelectionRef)_CFRuntimeCreateInstance(na->alloc, id, sizeof(struct NAHSelectionData) - sizeof(CFRuntimeBase), NULL);
if (nasel == NULL)
return NULL;
nasel->na = na;
nasel->spnego = true;
return nasel;
}
static void
nahrelease(NAHRef na)
{
CFRELEASE(na->hostname);
CFRELEASE(na->lchostname);
CFRELEASE(na->service);
CFRELEASE(na->username);
CFRELEASE(na->specificname);
CFRELEASE(na->servermechs);
CFRELEASE(na->spnegoServerName);
CFRELEASE(na->x509identities);
CFRELEASE(na->password);
CFRELEASE(na->mechs);
CFRELEASE(na->selections);
if (na->q)
dispatch_release(na->q);
if (na->context)
krb5_free_context(na->context);
if (na->hxctx)
hx509_context_free(&na->hxctx);
}
static CFTypeID
NAGetTypeID(void)
{
static CFTypeID naid = _kCFRuntimeNotATypeID;
static dispatch_once_t inited;
dispatch_once(&inited, ^{
static const CFRuntimeClass naclass = {
0,
"NetworkAuthentication",
NULL,
NULL,
(void(*)(CFTypeRef))nahrelease,
NULL,
NULL,
NULL,
NULL
};
naid = _CFRuntimeRegisterClass(&naclass);
});
return naid;
}
static NAHRef
NAAlloc(CFAllocatorRef alloc)
{
CFTypeID id = NAGetTypeID();
NAHRef na;
if (id == _kCFRuntimeNotATypeID)
return NULL;
na = (NAHRef)_CFRuntimeCreateInstance(alloc, id, sizeof(struct NAHData) - sizeof(CFRuntimeBase), NULL);
if (na == NULL)
return NULL;
na->q = dispatch_queue_create("network-authentication", NULL);
na->bgq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
na->alloc = alloc;
return na;
}
static bool
haveMech(NAHRef na, CFStringRef mech)
{
if (na->servermechs == NULL)
return false;
if (CFDictionaryGetValue(na->servermechs, mech) != NULL)
return true;
return false;
}
const CFStringRef kNAHSelectionHaveCredential = CFSTR("kNAHSelectionHaveCredential");
const CFStringRef kNAHSelectionUserPrintable = CFSTR("kNAHSelectionUserPrintable");
const CFStringRef kNAHClientPrincipal = CFSTR("kNAHClientPrincipal");
const CFStringRef kNAHServerPrincipal = CFSTR("kNAHServerPrincipal");
const CFStringRef kNAHMechanism = CFSTR("kNAHMechanism");
const CFStringRef kNAHInnerMechanism = CFSTR("kNAHInnerMechanism");
const CFStringRef kNAHCredentialType = CFSTR("kNAHCredentialType");
const CFStringRef kNAHUseSPNEGO = CFSTR("kNAHUseSPNEGO");
const CFStringRef kNAHClientNameType = CFSTR("kNAHClientNameType");
const CFStringRef kNAHClientNameTypeGSSD = CFSTR("kNAHClientNameTypeGSSD");
const CFStringRef kNAHServerNameType = CFSTR("kNAHServerNameType");
const CFStringRef kNAHServerNameTypeGSSD = CFSTR("kNAHServerNameTypeGSSD");
const CFStringRef kNAHNTUsername = CFSTR("kNAHNTUsername");
const CFStringRef kNAHNTServiceBasedName = CFSTR("kNAHNTServiceBasedName");
const CFStringRef kNAHNTKRB5PrincipalReferral = CFSTR("kNAHNTKRB5PrincipalReferral");
const CFStringRef kNAHNTKRB5Principal = CFSTR("kNAHNTKRB5Principal");
const CFStringRef kNAHNTUUID = CFSTR("kNAHNTUUID");
const CFStringRef kNAHInferredLabel = CFSTR("kNAHInferredLabel");
enum {
USE_SPNEGO = 1,
FORCE_ADD = 2
};
static NAHSelectionRef
addSelection(NAHRef na,
CFStringRef client,
CFStringRef clienttype,
CFStringRef server,
CFStringRef servertype,
enum NAHMechType mech,
int *duplicate,
unsigned long flags)
{
NAHSelectionRef nasel;
int matching;
CFIndex n;
if (clienttype == NULL)
clienttype = kNAHNTUsername;
if (servertype == NULL)
servertype = kNAHNTServiceBasedName;
matching = (flags & FORCE_ADD) || (na->specificname == NULL) || CFStringHasPrefix(client, na->specificname);
os_log(na_get_oslog(), "addSelection: %@ (%d) %@ %@ %s %s",
mech2name(mech), (int)mech, client, server, (flags & USE_SPNEGO) ? "SPNEGO" : "raw",
matching ? "matching" : "no-matching");
if (matching == 0)
return NULL;
for (n = 0; n < CFArrayGetCount(na->selections); n++) {
nasel = (NAHSelectionRef)CFArrayGetValueAtIndex(na->selections, n);
if (nasel->mech != mech)
continue;
if (CFStringCompare(nasel->client, client, 0) != kCFCompareEqualTo)
continue;
if (nasel->server && server && CFStringCompare(nasel->server, server, 0) != kCFCompareEqualTo)
continue;
if (CFStringCompare(nasel->servertype, servertype, 0) != kCFCompareEqualTo)
continue;
if (duplicate)
*duplicate = 1;
return nasel;
}
if (duplicate)
*duplicate = 0;
nasel = NAHSelectionAlloc(na);
if (nasel == NULL)
return NULL;
nasel->client = CFRetain(client);
nasel->server = CFRetain(server);
nasel->clienttype = clienttype;
CFRetain(clienttype);
nasel->servertype = servertype;
CFRetain(servertype);
nasel->mech = mech;
nasel->spnego = (flags & USE_SPNEGO) ? true : false;
CFArrayAppendValue(na->selections, nasel);
CFRelease(nasel);
return nasel;
}
static bool
findUsername(CFAllocatorRef alloc, NAHRef na, CFDictionaryRef info)
{
char *name;
if (info) {
na->username = CFDictionaryGetValue(info, kNAHUserName);
if (na->username) {
CFRange range, ur;
CFRetain(na->username);
if (CFStringFindWithOptions(na->username, CFSTR("@"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
ur.location = 0;
ur.length = range.location;
na->specificname = CFStringCreateWithSubstring(na->alloc, na->username, ur);
} else if (CFStringFindWithOptions(na->username, CFSTR("\\"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
ur.location = range.location + 1;
ur.length = CFStringGetLength(na->username) - ur.location;
na->specificname = CFStringCreateWithSubstring(na->alloc, na->username, ur);
} else {
na->specificname = na->username;
CFRetain(na->specificname);
}
os_log(na_get_oslog(), "NAH: specific name is: %@ foo", na->specificname);
return true;
}
}
name = getlogin();
if (name == NULL)
return false;
na->username = CFStringCreateWithCString(alloc, name, kCFStringEncodingUTF8);
if (na->username == NULL)
return false;
return true;
}
static bool
have_lkdcish_hostname(NAHRef na, bool localIsLKDC)
{
CFMutableStringRef btmmDomain = NULL;
CFStringRef btmmDomainData;
bool ret = false;
btmmDomainData = _CSBackToMyMacCopyDomain();
if (btmmDomainData) {
btmmDomain = CFStringCreateMutableCopy(na->alloc, 0, btmmDomainData);
CFRELEASE(btmmDomainData);
if (btmmDomain) {
CFStringTrim(btmmDomain, CFSTR("."));
os_log(na_get_oslog(), "using BTMM domain %@", btmmDomain);
}
}
if (na->lchostname == NULL) {
na->lchostname = CFStringCreateMutableCopy(NULL, 0, na->hostname);
if (na->lchostname == NULL) {
CFRELEASE(btmmDomain);
return false;
}
}
CFStringLowercase(na->lchostname, CFLocaleGetSystem());
if (localIsLKDC && CFStringHasSuffix(na->lchostname, CFSTR(".local")))
ret = true;
else if (btmmDomain && CFStringHasSuffix(na->lchostname, btmmDomain))
ret = true;
CFRELEASE(btmmDomain);
return ret;
}
static void
add_realms(NAHRef na, char **realms, unsigned long flags)
{
CFStringRef u, s;
size_t n;
for (n = 0; realms[n] != NULL; n++) {
u = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%s"), na->username, realms[n]);
s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%s"), na->service, na->hostname, realms[n]);
if (u && s)
addSelection(na, u, kNAHNTKRB5Principal,
s, kNAHNTKRB5PrincipalReferral, GSS_KERBEROS, NULL, flags);
CFRELEASE(u);
CFRELEASE(s);
}
}
static void
use_classic_kerberos(NAHRef na, unsigned long flags)
{
CFRange range, dr, ur;
char **realms, *str;
int ret;
if (have_lkdcish_hostname(na, false))
return;
ret = __KRBCreateUTF8StringFromCFString(na->hostname, &str);
if (ret)
return;
if (CFStringFindWithOptions(na->username, CFSTR("@"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
CFStringRef domain = NULL, s = NULL;
CFMutableStringRef domainm = NULL;
dr.location = range.location + 1;
dr.length = CFStringGetLength(na->username) - dr.location;
domain = CFStringCreateWithSubstring(na->alloc, na->username, dr);
if (domain) {
domainm = CFStringCreateMutableCopy(na->alloc, 0, domain);
if (domainm) {
CFStringUppercase(domainm, NULL);
s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%@"),
na->service, na->hostname, domainm);
if (s)
addSelection(na, na->username, kNAHNTKRB5Principal,
s, kNAHNTKRB5PrincipalReferral, GSS_KERBEROS, NULL, flags);
}
}
CFRELEASE(domainm);
CFRELEASE(domain);
CFRELEASE(s);
}
if (CFStringFindWithOptions(na->username, CFSTR("\\"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
CFStringRef domain = NULL, user = NULL, user2 = NULL, s = NULL;
CFMutableStringRef domainm = NULL;
dr.location = 0;
dr.length = range.location;
ur.location = range.location + 1;
ur.length = CFStringGetLength(na->username) - ur.location;
user = CFStringCreateWithSubstring(na->alloc, na->username, ur);
domain = CFStringCreateWithSubstring(na->alloc, na->username, dr);
if (domain && user) {
domainm = CFStringCreateMutableCopy(na->alloc, 0, domain);
user2 = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@@%@"), user, domain);
if (domainm && user2) {
CFStringUppercase(domainm, NULL);
s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%@"),
na->service, na->hostname, domainm);
if (s)
addSelection(na, user2, kNAHNTKRB5Principal,
s, kNAHNTKRB5PrincipalReferral, GSS_KERBEROS, NULL, flags|FORCE_ADD);
}
}
CFRELEASE(domainm);
CFRELEASE(domain);
CFRELEASE(user2);
CFRELEASE(user);
CFRELEASE(s);
}
ret = krb5_get_host_realm(na->context, str, &realms);
__KRBReleaseUTF8String(str);
if (ret == 0) {
add_realms(na, realms, flags);
krb5_free_host_realm(na->context, realms);
}
ret = krb5_get_default_realms(na->context, &realms);
if (ret == 0) {
add_realms(na, realms, flags);
krb5_free_host_realm(na->context, realms);
}
}
static void
use_existing_principals(NAHRef na, int only_lkdc, unsigned long flags)
{
krb5_cccol_cursor cursor;
krb5_principal client;
krb5_error_code ret;
CFStringRef server;
krb5_ccache id;
CFStringRef u;
char *c;
ret = krb5_cccol_cursor_new(na->context, &cursor);
if (ret)
return;
while ((ret = krb5_cccol_cursor_next(na->context, cursor, &id)) == 0 && id != NULL) {
NAHSelectionRef nasel;
int is_lkdc;
time_t t;
ret = krb5_cc_get_principal(na->context, id, &client);
if (ret) {
krb5_cc_close(na->context, id);
continue;
}
ret = krb5_cc_get_lifetime(na->context, id, &t);
if (ret || t <= 0) {
krb5_cc_close(na->context, id);
continue;
}
is_lkdc = krb5_principal_is_lkdc(na->context, client);
if ((only_lkdc && !is_lkdc) || (!only_lkdc && is_lkdc)) {
krb5_free_principal(na->context, client);
krb5_cc_close(na->context, id);
continue;
}
ret = krb5_unparse_name(na->context, client, &c);
if (ret) {
krb5_free_principal(na->context, client);
krb5_cc_close(na->context, id);
continue;
}
u = CFStringCreateWithCString(na->alloc, c, kCFStringEncodingUTF8);
free(c);
if (u == NULL) {
krb5_free_principal(na->context, client);
krb5_cc_close(na->context, id);
continue;
}
if (is_lkdc) {
CFStringRef cr = NULL;
krb5_data data;
ret = krb5_cc_get_config(na->context, id, NULL, "lkdc-hostname", &data);
if (ret == 0) {
cr = CFStringCreateWithBytes(na->alloc, data.data, data.length, kCFStringEncodingUTF8, false);
krb5_data_free(&data);
}
if (cr == NULL || CFStringCompare(na->hostname, cr, 0) != kCFCompareEqualTo) {
krb5_free_principal(na->context, client);
krb5_cc_close(na->context, id);
continue;
}
server = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%s@%s"),
na->service, client->realm, client->realm);
os_log(na_get_oslog(), "Adding existing LKDC cache: %@ -> %@", u, server);
} else {
server = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%s"), na->service, na->hostname,
krb5_principal_get_realm(na->context, client));
os_log(na_get_oslog(), "Adding existing cache: %@ -> %@", u, server);
}
krb5_free_principal(na->context, client);
nasel = addSelection(na, u, kNAHNTKRB5Principal,
server, kNAHNTKRB5PrincipalReferral, GSS_KERBEROS, NULL, flags);
CFRELEASE(u);
CFRELEASE(server);
if (nasel != NULL && nasel->ccache == NULL) {
krb5_data data;
nasel->ccache = id;
nasel->have_cred = 1;
if (nasel->inferredLabel == NULL) {
ret = krb5_cc_get_config(na->context, id, NULL, "FriendlyName", &data);
if (ret == 0) {
nasel->inferredLabel = CFStringCreateWithBytes(na->alloc, data.data, data.length, kCFStringEncodingUTF8, false);
krb5_data_free(&data);
}
}
} else
krb5_cc_close(na->context, id);
}
krb5_cccol_cursor_free(na->context, &cursor);
}
static CFStringRef kWELLKNOWN_LKDC = CFSTR("WELLKNOWN:COM.APPLE.LKDC");
static void
wellknown_lkdc(NAHRef na, enum NAHMechType mechtype, unsigned long flags)
{
CFStringRef s, u;
CFIndex n;
u = CFStringCreateWithFormat(na->alloc, NULL,
CFSTR("%@@%@"),
na->username, kWELLKNOWN_LKDC);
if (u == NULL)
return;
s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/localhost@%@"),
na->service, kWELLKNOWN_LKDC);
if (s == NULL) {
CFRELEASE(u);
return;
}
if (na->password)
addSelection(na, u, kNAHNTKRB5Principal,
s, kNAHNTKRB5Principal, mechtype, NULL, flags);
CFRELEASE(u);
for (n = 0; na->x509identities && n < CFArrayGetCount(na->x509identities); n++) {
SecIdentityRef identity = (void *)CFArrayGetValueAtIndex(na->x509identities, n);
CFStringRef csstr;
SecCertificateRef cert = NULL;
if (SecIdentityCopyCertificate(identity, &cert))
continue;
csstr = _CSCopyKerberosPrincipalForCertificate(cert);
CFRelease(cert);
if (csstr == NULL) {
hx509_cert hxcert;
char *str;
int ret;
ret = hx509_cert_init_SecFramework(na->hxctx, identity, &hxcert);
if (ret)
continue;
ret = hx509_cert_get_appleid(na->hxctx, hxcert, &str);
hx509_cert_free(hxcert);
if (ret)
continue;
u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%s@%@"),
str, kWELLKNOWN_LKDC);
krb5_xfree(str);
if (u == NULL)
continue;
} else {
u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@@%@"),
csstr, kWELLKNOWN_LKDC);
CFRelease(csstr);
if (u == NULL)
continue;
}
NAHSelectionRef nasel = addSelection(na, u, kNAHNTKRB5Principal, s, kNAHNTKRB5PrincipalReferral, mechtype, NULL, flags);
CFRELEASE(u);
if (nasel) {
CFRetain(identity);
nasel->certificate = identity;
}
}
CFRELEASE(s);
}
static bool
is_smb(NAHRef na)
{
if (CFStringCompare(na->service, kNAHServiceHostServer, 0) == kCFCompareEqualTo ||
CFStringCompare(na->service, kNAHServiceCIFSServer, 0) == kCFCompareEqualTo)
return true;
return false;
}
static void
guess_kerberos(NAHRef na)
{
bool try_wlkdc = false;
bool try_iakerb_with_lkdc = false;
bool have_kerberos = false;
krb5_error_code ret;
unsigned long flags = USE_SPNEGO;
if (nah_use_gss_uam
&& (na->password || na->x509identities)
&& haveMech(na, kGSSAPIMechIAKERB)
&& haveMech(na, kGSSAPIMechSupportsAppleLKDC))
{
try_iakerb_with_lkdc = true;
} else if (haveMech(na, kGSSAPIMechPKU2UOID) || haveMech(na, kGSSAPIMechSupportsAppleLKDC)) {
try_wlkdc = true;
} else if (CFStringCompare(na->service, kNAHServiceVNCServer, 0) == kCFCompareEqualTo) {
try_wlkdc = true;
if (nah_vnc_support_iakerb && (na->password || na->x509identities))
try_iakerb_with_lkdc = true;
}
if (CFStringCompare(na->service, kNAHServiceAFPServer, 0) == kCFCompareEqualTo &&
!haveMech(na, kGSSAPIMechSupportsAppleLKDC))
{
flags &= (~USE_SPNEGO);
}
have_kerberos = (na->servermechs == NULL) ||
haveMech(na, kGSSAPIMechIAKERB) ||
haveMech(na, kGSSAPIMechKerberosOID) ||
haveMech(na, kGSSAPIMechKerberosMicrosoftOID) ||
haveMech(na, kGSSAPIMechPKU2UOID);
os_log(na_get_oslog(), "NAHCreate-krb: have_kerberos=%s try_iakerb_with_lkdc=%s try-wkdc=%s use-spnego=%s",
have_kerberos ? "yes" : "no",
try_iakerb_with_lkdc ? "yes" : "no",
try_wlkdc ? "yes" : "no",
(flags & USE_SPNEGO) ? "yes" : "no");
if (!have_kerberos)
return;
ret = krb5_init_context(&na->context);
if (ret)
return;
ret = hx509_context_init(&na->hxctx);
if (ret)
return;
use_existing_principals(na, 1, flags);
if (try_iakerb_with_lkdc) {
wellknown_lkdc(na, GSS_KERBEROS_IAKERB, flags);
}
if (try_wlkdc)
wellknown_lkdc(na, GSS_KERBEROS, flags);
if (na->password)
use_classic_kerberos(na, flags);
use_existing_principals(na, 0, flags);
}
static void
guess_ntlm(NAHRef na)
{
OM_uint32 junk;
CFStringRef s;
unsigned long flags = USE_SPNEGO;
if (!haveMech(na, kGSSAPIMechNTLMOID))
return;
if (na->servermechs) {
CFDataRef data = CFDictionaryGetValue(na->servermechs, kGSSAPIMechNTLMOID);
if (data && CFDataGetLength(data) == 3 && memcmp(CFDataGetBytePtr(data), "raw", 3) == 0)
flags &= (~USE_SPNEGO);
}
s = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%@"), na->service, na->hostname);
if (s == NULL)
return;
if (na->password) {
CFRange range, ur, dr;
CFStringRef u = NULL;
unsigned long flags2 = 0;
if (CFStringFindWithOptions(na->username, CFSTR("@"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
u = na->username;
CFRetain(u);
flags2 = FORCE_ADD;
} else if (CFStringFindWithOptions(na->username, CFSTR("\\"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) {
CFStringRef ustr, dstr;
dr.location = 0;
dr.length = range.location;
ur.location = range.location + 1;
ur.length = CFStringGetLength(na->username) - ur.location;
dstr = CFStringCreateWithSubstring(NULL, na->username, dr);
ustr = CFStringCreateWithSubstring(NULL, na->username, ur);
if (dstr && ustr)
u = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%@"), ustr, dstr);
CFRELEASE(ustr);
CFRELEASE(dstr);
flags2 = FORCE_ADD;
} else {
u = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@\\%@"), na->username, na->hostname);
}
if (u) {
addSelection(na, u, kNAHNTUsername, s, NULL, GSS_NTLM, NULL, flags | flags2);
CFRELEASE(u);
}
if (na->specificname) {
u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@@\\%@"), na->specificname, na->hostname);
addSelection(na, u, kNAHNTUsername, s, NULL, GSS_NTLM, NULL, flags);
CFRELEASE(u);
}
}
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
if (sema == NULL)
goto out;
(void)gss_iter_creds(&junk, 0, GSS_NTLM_MECHANISM, ^(gss_OID oid, gss_cred_id_t cred) {
OM_uint32 min_stat;
gss_name_t name = GSS_C_NO_NAME;
gss_buffer_desc buffer = { 0, NULL };
if (cred == NULL) {
dispatch_semaphore_signal(sema);
return;
}
gss_inquire_cred(&min_stat, cred, &name, NULL, NULL, NULL);
gss_display_name(&min_stat, name, &buffer, NULL);
gss_release_name(&min_stat, &name);
CFStringRef u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%.*s"),
(int)buffer.length, buffer.value);
gss_release_buffer(&min_stat, &buffer);
if (u == NULL)
return;
NAHSelectionRef nasel = addSelection(na, u, kNAHNTUsername, s, NULL, GSS_NTLM, NULL, flags);
CFRELEASE(u);
if (nasel) {
nasel->have_cred = 1;
}
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
out:
CFRELEASE(s);
}
static void
addSecItem(CFMutableArrayRef certs, CFTypeRef item)
{
CFTypeID type = CFGetTypeID(item);
if (CFArrayGetTypeID() == type) {
CFIndex n, count = CFArrayGetCount(item);
for (n = 0; n < count; n++) {
CFTypeRef arrayItem = CFArrayGetValueAtIndex(item, n);
addSecItem(certs, arrayItem);
}
} else if (SecCertificateGetTypeID() == type) {
SecIdentityRef identity = NULL;
SecIdentityCreateWithCertificate(NULL, (SecCertificateRef)item, &identity);
if (identity) {
CFArrayAppendValue(certs, identity);
CFRelease(identity);
}
} else if (SecIdentityGetTypeID() == type) {
CFArrayAppendValue(certs, item);
} else {
CFStringRef desc = CFCopyDescription(item);
os_log(na_get_oslog(), "unknown type of certificates: %@", desc);
CFRELEASE(desc);
}
}
const CFStringRef kNAHNegTokenInit = CFSTR("kNAHNegTokenInit");
const CFStringRef kNAHUserName = CFSTR("kNAHUserName");
const CFStringRef kNAHCertificates = CFSTR("kNAHCertificates");
const CFStringRef kNAHPassword = CFSTR("kNAHPassword");
NAHRef
NAHCreate(CFAllocatorRef alloc,
CFStringRef hostname,
CFStringRef service,
CFDictionaryRef info)
{
NAHRef na = NAAlloc(alloc);
CFStringRef canonname;
char *hostnamestr = NULL;
dispatch_once(&init_globals, ^{
Boolean have_key = false;
nah_use_gss_uam = CFPreferencesGetAppBooleanValue(CFSTR("GSSEnable"), CFSTR("com.apple.NetworkAuthenticationHelper"), &have_key);
if (!have_key)
nah_use_gss_uam = true;
nah_vnc_support_iakerb = CFPreferencesGetAppBooleanValue(CFSTR("VNCSupportIAKerb"), CFSTR("com.apple.NetworkAuthenticationHelper"), &have_key);
});
os_log(na_get_oslog(), "NAHCreate: hostname=%@ service=%@", hostname, service);
if (_CFNetServiceDeconstructServiceName(hostname, &hostnamestr)) {
canonname = CFStringCreateWithCString(na->alloc, hostnamestr, kCFStringEncodingUTF8);
free(hostnamestr);
if (canonname == NULL)
return NULL;
} else {
canonname = hostname;
CFRetain(canonname);
}
na->hostname = CFStringCreateMutableCopy(alloc, 0, canonname);
CFRelease(canonname);
if (na->hostname == NULL) {
CFRELEASE(na);
return NULL;
}
CFStringTrim(na->hostname, CFSTR("."));
os_log(na_get_oslog(), "NAHCreate: will use hostname=%@", na->hostname);
na->service = CFRetain(service);
os_log(na_get_oslog(), "NAHCreate: will use service=%@", na->service);
if (!findUsername(alloc, na, info)) {
CFRELEASE(na);
return NULL;
}
os_log(na_get_oslog(), "NAHCreate: username=%@ username %s", na->username, na->specificname ?
"given" : "generated");
if (info) {
na->password = CFDictionaryGetValue(info, kNAHPassword);
if (na->password) {
os_log(na_get_oslog(), "NAHCreate: password");
CFRetain(na->password);
}
}
na->selections = CFArrayCreateMutable(na->alloc, 0, &kCFTypeArrayCallBacks);
if (na->selections == NULL) {
CFRELEASE(na);
return NULL;
}
if (info) {
CFDictionaryRef nti;
CFArrayRef certs;
nti = CFDictionaryGetValue(info, kNAHNegTokenInit);
if (nti) {
na->servermechs = CFDictionaryGetValue(nti, kSPNEGONegTokenInitMechs);
if (na->servermechs)
CFRetain(na->servermechs);
na->spnegoServerName = CFDictionaryGetValue(nti, kSPNEGONegTokenInitHintsHostname);
if (na->spnegoServerName) {
os_log(na_get_oslog(), "NAHCreate: SPNEGO hints name %@", na->spnegoServerName);
CFRetain(na->spnegoServerName);
}
}
certs = CFDictionaryGetValue(info, kNAHCertificates);
if (certs) {
CFMutableArrayRef a = CFArrayCreateMutable(na->alloc, 0, &kCFTypeArrayCallBacks);
os_log(na_get_oslog(), "NAHCreate: certs %@", certs);
addSecItem(a, certs);
if (CFArrayGetCount(a)) {
na->x509identities = a;
} else {
CFRelease(a);
os_log(na_get_oslog(), "NAHCreate: we got no certs");
}
}
}
add_user_selections(na);
guess_kerberos(na);
if (na->x509identities == NULL && is_smb(na))
guess_ntlm(na);
return na;
}
CFArrayRef
NAHGetSelections(NAHRef na)
{
return na->selections;
}
static void
setFriendlyName(NAHRef na,
NAHSelectionRef selection,
krb5_ccache id,
int is_lkdc)
{
CFStringRef inferredLabel = NULL;
if (na->specificname || is_lkdc) {
inferredLabel = na->username;
CFRetain(inferredLabel);
} else {
inferredLabel = selection->client;
CFRetain(inferredLabel);
}
if (inferredLabel) {
char *label;
if (__KRBCreateUTF8StringFromCFString(inferredLabel, &label) == noErr) {
krb5_data data;
data.data = label;
data.length = strlen(label) + 1;
krb5_cc_set_config(na->context, id, NULL, "FriendlyName", &data);
free(label);
}
selection->inferredLabel = inferredLabel;
}
}
static int
acquire_kerberos(NAHRef na,
NAHSelectionRef selection,
CFStringRef password,
SecIdentityRef cert,
CFErrorRef *error)
{
krb5_init_creds_context icc = NULL;
krb5_get_init_creds_opt *opt = NULL;
krb5_principal client = NULL;
int destroy_cache = 0;
krb5_ccache id = NULL;
krb5_error_code ret;
krb5_creds cred;
char *str = NULL;
int parseflags = 0;
int is_lkdc = 0;
memset(&cred, 0, sizeof(cred));
os_log(na_get_oslog(), "acquire_kerberos: %@ with pw:%s cert:%s",
selection->client,
password ? "yes" : "no",
cert ? "yes" : "no");
ret = __KRBCreateUTF8StringFromCFString(selection->client, &str);
if (ret)
goto out;
{
char *p = strchr(str, '@');
if (p && (p = strchr(p + 1, '@')) != NULL)
parseflags |= KRB5_PRINCIPAL_PARSE_ENTERPRISE;
}
ret = krb5_parse_name_flags(na->context, str, parseflags, &client);
__KRBReleaseUTF8String(str);
if (ret)
goto out;
ret = krb5_unparse_name(na->context, client, &str);
if (ret == 0) {
os_log(na_get_oslog(), "acquire_kerberos: trying with %s as client principal", str);
free(str);
}
ret = krb5_get_init_creds_opt_alloc(na->context, &opt);
if (ret)
goto out;
if (cert) {
ret = krb5_get_init_creds_opt_set_pkinit(na->context, opt, client,
NULL, "KEYCHAIN:",
NULL, NULL, 0,
NULL, NULL, NULL);
if (ret)
goto out;
}
krb5_get_init_creds_opt_set_canonicalize(na->context, opt, TRUE);
krb5_get_init_creds_opt_set_win2k(na->context, opt, TRUE);
ret = krb5_init_creds_init(na->context, client, NULL, NULL,
0, opt, &icc);
if (ret)
goto out;
if (krb5_principal_is_lkdc(na->context, client)) {
char *tcphostname = NULL;
ret = __KRBCreateUTF8StringFromCFString(na->hostname, &str);
if (ret)
goto out;
asprintf(&tcphostname, "tcp/%s", str);
__KRBReleaseUTF8String(str);
if (tcphostname == NULL) {
ret = ENOMEM;
goto out;
}
krb5_init_creds_set_kdc_hostname(na->context, icc, tcphostname);
free(tcphostname);
}
if (cert) {
hx509_cert hxcert;
ret = hx509_cert_init_SecFramework(na->hxctx, cert, &hxcert);
if (ret)
goto out;
ret = krb5_init_creds_set_pkinit_client_cert(na->context, icc, hxcert);
hx509_cert_free(hxcert);
if (ret)
goto out;
} else if (password) {
ret = __KRBCreateUTF8StringFromCFString(password, &str);
if (ret)
goto out;
ret = krb5_init_creds_set_password(na->context, icc, str);
__KRBReleaseUTF8String(str);
if (ret)
goto out;
} else {
abort();
}
ret = krb5_init_creds_get(na->context, icc);
if (ret)
goto out;
ret = krb5_init_creds_get_creds(na->context, icc, &cred);
if (ret)
goto out;
ret = krb5_cc_cache_match(na->context, cred.client, &id);
if (ret) {
ret = krb5_cc_new_unique(na->context, NULL, NULL, &id);
if (ret)
goto out;
destroy_cache = 1;
}
ret = krb5_cc_initialize(na->context, id, cred.client);
if (ret)
goto out;
ret = krb5_cc_store_cred(na->context, id, &cred);
if (ret)
goto out;
ret = krb5_init_creds_store_config(na->context, icc, id);
if (ret)
goto out;
{
CFStringRef newclient, newserver;
const char *realm = krb5_principal_get_realm(na->context, cred.client);
is_lkdc = krb5_realm_is_lkdc(realm);
ret = krb5_unparse_name(na->context, cred.client, &str);
if (ret)
goto out;
newclient = CFStringCreateWithCString(na->alloc, str, kCFStringEncodingUTF8);
free(str);
if (newclient == NULL) {
ret = ENOMEM;
goto out;
}
os_log(na_get_oslog(), "acquire_kerberos: got %@ as client principal", newclient);
if (CFStringCompare(newclient, selection->client, 0) != kCFCompareEqualTo) {
CFRELEASE(selection->client);
selection->client = newclient;
if (is_lkdc) {
newserver = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%s@%s"),
na->service, realm, realm);
} else {
newserver = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%s"),
na->service, na->hostname, realm);
}
if (newserver) {
CFRELEASE(selection->server);
selection->server = newserver;
}
} else {
CFRELEASE(newclient);
}
}
setFriendlyName(na, selection, id, is_lkdc);
{
krb5_data data;
data.data = "1";
data.length = 1;
krb5_cc_set_config(na->context, id, NULL, nah_created, &data);
}
out:
if (ret) {
const char *e = krb5_get_error_message(na->context, ret);
updateError(NULL, error, ret, CFSTR("acquire_kerberos failed %@: %d - %s"),
selection->client, ret, e);
krb5_free_error_message(na->context, e);
} else {
os_log(na_get_oslog(), "acquire_kerberos successful");
}
if (opt)
krb5_get_init_creds_opt_free(na->context, opt);
if (icc)
krb5_init_creds_free(na->context, icc);
if (id) {
if (ret != 0 && destroy_cache)
krb5_cc_destroy(na->context, id);
else
krb5_cc_close(na->context, id);
}
krb5_free_cred_contents(na->context, &cred);
if (client)
krb5_free_principal(na->context, client);
return ret;
}
Boolean
NAHSelectionAcquireCredentialAsync(NAHSelectionRef selection,
CFDictionaryRef info,
dispatch_queue_t q,
void (^result)(CFErrorRef error))
{
void (^r)(CFErrorRef error) = (void (^)(CFErrorRef))Block_copy(result);
CFRetain(selection->na);
dispatch_async(selection->na->bgq, ^{
CFErrorRef e = NULL;
Boolean res;
res = NAHSelectionAcquireCredential(selection, info, &e);
if (!res) {
r(NULL);
CFRelease(selection->na);
Block_release(r);
return;
}
dispatch_async(q, ^{
r(e);
if (e)
CFRelease(e);
CFRelease(selection->na);
Block_release(r);
});
});
return true;
}
static void
setGSSLabel(gss_cred_id_t cred, const char *label, CFStringRef value)
{
gss_buffer_desc buf;
OM_uint32 junk;
buf.value = cf2cstring(value);
if (buf.value == NULL)
return;
buf.length = strlen((char *)buf.value);
gss_cred_label_set(&junk, cred, label, &buf);
free(buf.value);
}
const CFStringRef kNAHForceRefreshCredential = CFSTR("kNAHForceRefreshCredential");
Boolean
NAHSelectionAcquireCredential(NAHSelectionRef selection,
CFDictionaryRef info,
CFErrorRef *error)
{
if (error)
*error = NULL;
CFRetain(selection->na);
if (selection->mech == GSS_KERBEROS) {
os_log(na_get_oslog(), "NAHSelectionAcquireCredential: kerberos client: %@ (server %@)",
selection->client, selection->server);
if (selection->ccache) {
os_log(na_get_oslog(), "have ccache");
KRBCredChangeReferenceCount(selection->client, 1, 1);
return true;
}
if (selection->na->password == NULL && selection->certificate == NULL) {
os_log(na_get_oslog(), "krb5: no password or cert, punting");
CFRelease(selection->na);
return false;
}
int ret;
ret = acquire_kerberos(selection->na,
selection,
selection->na->password,
selection->certificate,
error);
CFRelease(selection->na);
if (ret && error && *error)
os_log(na_get_oslog(), "NAHSelectionAcquireCredential %@", *error);
return (ret == 0) ? true : false;
} else if (selection->mech == GSS_NTLM) {
gss_auth_identity_desc identity;
gss_name_t name = GSS_C_NO_NAME;
gss_buffer_desc gbuf;
char *password, *user;
OM_uint32 major, minor, junk;
dispatch_semaphore_t s;
char *str;
os_log(na_get_oslog(), "NAHSelectionAcquireCredential: ntlm");
if (selection->have_cred) {
CFRelease(selection->na);
return true;
}
if (selection->na->password == NULL) {
CFRelease(selection->na);
return false;
}
__KRBCreateUTF8StringFromCFString(selection->client, &user);
gbuf.value = user;
gbuf.length = strlen(user);
major = gss_import_name(&minor, &gbuf, GSS_C_NT_USER_NAME, &name);
__KRBReleaseUTF8String(user);
if (major) {
CFRelease(selection->na);
return false;
}
s = dispatch_semaphore_create(0);
__KRBCreateUTF8StringFromCFString(selection->client, &user);
__KRBCreateUTF8StringFromCFString(selection->na->password, &password);
selection->inferredLabel = selection->client;
CFRetain(selection->inferredLabel);
str = strchr(user, '@');
if (str)
*str++ = '\0';
identity.username = user;
if (str)
identity.realm = str;
else
identity.realm = "";
identity.password = password;
major = gss_acquire_cred_ex(name,
0,
GSS_C_INDEFINITE,
GSS_NTLM_MECHANISM,
GSS_C_INITIATE,
&identity,
^(gss_status_id_t status, gss_cred_id_t cred,
gss_OID_set set, OM_uint32 flags) {
if (cred) {
gss_buffer_desc buffer;
OM_uint32 min_stat;
buffer.value = user;
buffer.length = strlen(user);
gss_cred_label_set(&min_stat, cred, "FriendlyName", &buffer);
buffer.value = "1";
buffer.length = 1;
gss_cred_label_set(&min_stat, cred, nah_created, &buffer);
} else {
updateError(NULL, error, 1, CFSTR("failed to create ntlm cred"));
}
dispatch_semaphore_signal(s);
CFRelease(selection->na);
});
gss_release_name(&junk, &name);
if (major == GSS_S_COMPLETE) {
dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER);
if (error && *error)
os_log(na_get_oslog(), "NAHSelectionAcquireCredential ntlm %@", *error);
} else {
updateError(NULL, error, major, CFSTR("Failed to acquire NTLM credentials"));
CFRelease(selection->na);
}
__KRBReleaseUTF8String(user);
__KRBReleaseUTF8String(password);
dispatch_release(s);
return true;
} else if (selection->mech == GSS_KERBEROS_IAKERB) {
gss_name_t name = GSS_C_NO_NAME;
gss_buffer_desc gbuf;
char *user;
OM_uint32 major, minor, junk;
gss_cred_id_t cred;
os_log(na_get_oslog(), "NAHSelectionAcquireCredential: iakerb %@", selection->client);
if (selection->have_cred) {
os_log(na_get_oslog(), "NAHSelectionAcquireCredential: already have cred, why iakerb then ?");
CFRelease(selection->na);
return false;
}
if (selection->na->password == NULL && selection->certificate == NULL) {
os_log(na_get_oslog(), "NAHSelectionAcquireCredential: no password nor cert");
CFRelease(selection->na);
return false;
}
__KRBCreateUTF8StringFromCFString(selection->client, &user);
gbuf.value = user;
gbuf.length = strlen(user);
major = gss_import_name(&minor, &gbuf, GSS_C_NT_USER_NAME, &name);
__KRBReleaseUTF8String(user);
if (major) {
CFRelease(selection->na);
return false;
}
if (selection->inferredLabel == NULL) {
CFMutableStringRef str = CFStringCreateMutableCopy(NULL, 0, selection->client);
CFRange range = CFStringFind(str, CFSTR("@"), 0);
if (range.location != kCFNotFound)
CFStringPad(str, NULL, range.location, 0);
selection->inferredLabel = str;
}
CFMutableDictionaryRef dict = NULL;
dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (selection->na->password)
CFDictionaryAddValue(dict, kGSSICPassword, selection->na->password);
if (selection->certificate)
CFDictionaryAddValue(dict, kGSSICCertificate, selection->certificate);
major = gss_aapl_initial_cred(name, GSS_IAKERB_MECHANISM, dict, &cred, error);
CFRelease(dict);
gss_release_name(&junk, &name);
if (major) {
if (error && *error)
os_log(na_get_oslog(), "NAHSelectionAcquireCredential iakerb %@", *error);
CFRelease(selection->na);
return false;
}
setGSSLabel(cred, "FriendlyName", selection->inferredLabel);
setGSSLabel(cred, "lkdc-hostname", selection->na->hostname);
{
gss_buffer_set_t dataset = GSS_C_NO_BUFFER_SET;
major = gss_inquire_cred_by_oid(&minor, cred, GSS_C_NT_UUID, &dataset);
if (major || dataset->count != 1) {
os_log(na_get_oslog(), "NAHSelectionAcquireCredential: failed with no uuid");
gss_release_buffer_set(&junk, &dataset);
CFRelease(selection->na);
return false;
}
CFStringRef newclient = CFStringCreateWithBytes(NULL, dataset->elements[0].value, dataset->elements[0].length, kCFStringEncodingUTF8, false);
if (newclient) {
CFRELEASE(selection->client);
selection->client = newclient;
selection->clienttype = kNAHNTUUID;
}
gss_release_buffer_set(&junk, &dataset);
}
os_log(na_get_oslog(), "NAHSelectionAcquireCredential complete: iakerb %@ - %@: %@", selection->client, selection->inferredLabel, cred);
gss_release_cred(&junk, &cred);
CFRelease(selection->na);
return true;
} else {
os_log(na_get_oslog(), "NAHSelectionAcquireCredential: unknown");
}
return false;
}
CFTypeRef
NAHSelectionGetInfoForKey(NAHSelectionRef selection, CFStringRef key)
{
if (CFStringCompare(kNAHSelectionHaveCredential, key, 0) == kCFCompareEqualTo) {
if (selection->ccache)
return kCFBooleanTrue;
return kCFBooleanFalse;
} else if (CFStringCompare(kNAHSelectionUserPrintable, key, 0) == kCFCompareEqualTo) {
return selection->client;
} else if (CFStringCompare(kNAHServerPrincipal, key, 0) == kCFCompareEqualTo) {
return selection->server;
} else if (CFStringCompare(kNAHClientPrincipal, key, 0) == kCFCompareEqualTo) {
return selection->client;
} else if (CFStringCompare(kNAHMechanism, key, 0) == kCFCompareEqualTo) {
if (!selection->spnego)
return mech2name(selection->mech);
return kGSSAPIMechSPNEGO;
} else if (CFStringCompare(kNAHInnerMechanism, key, 0) == kCFCompareEqualTo) {
return mech2name(selection->mech);
} else if (CFStringCompare(kNAHUseSPNEGO, key, 0) == kCFCompareEqualTo) {
return selection->spnego ? kCFBooleanTrue : kCFBooleanFalse;
} else if (CFStringCompare(kNAHCredentialType, key, 0) == kCFCompareEqualTo) {
return mech2name(selection->mech);
} else if (CFStringCompare(kNAHInferredLabel, key, 0) == kCFCompareEqualTo) {
return selection->inferredLabel;
}
return NULL;
}
CFDictionaryRef
NAHSelectionCopyAuthInfo(NAHSelectionRef selection)
{
CFMutableDictionaryRef dict;
CFStringRef string;
int gssdclient, gssdserver;
if (selection->server == NULL)
return NULL;
dict = CFDictionaryCreateMutable (kCFAllocatorDefault, 5,
&kCFCopyStringDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (dict == NULL)
return NULL;
CFDictionaryAddValue(dict, kNAHMechanism,
NAHSelectionGetInfoForKey(selection, kNAHMechanism));
CFDictionaryAddValue(dict, kNAHCredentialType,
NAHSelectionGetInfoForKey(selection, kNAHCredentialType));
CFDictionaryAddValue(dict, kNAHClientNameType, selection->clienttype);
if (CFStringCompare(selection->clienttype, kNAHNTUUID, 0) == 0) {
gssdclient = GSSD_UUID;
} else if (CFStringCompare(selection->clienttype, kNAHNTKRB5Principal, 0) == 0) {
gssdclient = GSSD_KRB5_PRINCIPAL;
} else if (CFStringCompare(selection->clienttype, kNAHNTUsername, 0) == 0) {
gssdclient = GSSD_NTLM_PRINCIPAL;
} else {
gssdclient = GSSD_USER;
}
CFDictionaryAddValue(dict, kNAHServerNameType, selection->servertype);
if (CFStringCompare(selection->servertype, kNAHNTServiceBasedName, 0) == 0)
gssdserver = GSSD_HOSTBASED;
else if (CFStringCompare(selection->servertype, kNAHNTKRB5PrincipalReferral, 0) == 0)
gssdserver = GSSD_KRB5_REFERRAL;
else if (CFStringCompare(selection->servertype, kNAHNTKRB5Principal, 0) == 0)
gssdserver = GSSD_KRB5_PRINCIPAL;
else
gssdserver = GSSD_HOSTBASED;
CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &gssdclient);
if (num) {
CFDictionaryAddValue(dict, kNAHClientNameTypeGSSD, num);
CFRelease(num);
}
num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &gssdserver);
if (num) {
CFDictionaryAddValue(dict, kNAHServerNameTypeGSSD, num);
CFRelease(num);
}
CFDictionaryAddValue(dict, kNAHClientPrincipal,
NAHSelectionGetInfoForKey(selection, kNAHClientPrincipal));
CFDictionaryAddValue(dict, kNAHServerPrincipal,
NAHSelectionGetInfoForKey(selection, kNAHServerPrincipal));
if ((string = NAHSelectionGetInfoForKey(selection, kNAHInferredLabel)) != NULL)
CFDictionaryAddValue(dict, kNAHInferredLabel, string);
CFDictionaryAddValue(dict, kNAHUseSPNEGO, NAHSelectionGetInfoForKey(selection, kNAHUseSPNEGO));
return dict;
}
void
NAHCancel(NAHRef na)
{
}
static Boolean
CredChange(CFStringRef referenceKey, int count, const char *label)
{
const char *mechname;
gss_OID nametype;
gss_OID oid;
if (referenceKey == NULL)
return false;
os_log(na_get_oslog(), "NAHCredChange: %@ count: %d label: %s",
referenceKey, count, label ? label : "<nolabel>");
if (CFStringHasPrefix(referenceKey, CFSTR("krb5:"))) {
oid = GSS_KRB5_MECHANISM;
nametype = GSS_C_NT_USER_NAME;
mechname = "kerberos";
} else if (CFStringHasPrefix(referenceKey, CFSTR("uuid:"))) {
oid = NULL;
nametype = GSS_C_NT_UUID;
mechname = "uuid";
} else if (CFStringHasPrefix(referenceKey, CFSTR("ntlm:"))) {
oid = GSS_NTLM_MECHANISM;
nametype = GSS_C_NT_USER_NAME;
mechname = "ntlm";
} else
return false;
{
gss_cred_id_t cred;
gss_buffer_desc gbuf;
OM_uint32 min_stat, maj_stat;
CFStringRef name;
gss_name_t gname;
OSStatus ret;
gss_OID_set_desc mechset;
char *n;
if (oid) {
mechset.elements = oid;
mechset.count = 1;
}
name = CFStringCreateWithSubstring(NULL, referenceKey, CFRangeMake(5, CFStringGetLength(referenceKey) - 5));
if (name == NULL)
return false;
ret = __KRBCreateUTF8StringFromCFString(name, &n);
CFRelease(name);
if (ret)
return false;
gbuf.value = n;
gbuf.length = strlen(n);
maj_stat = gss_import_name(&min_stat, &gbuf, nametype, &gname);
if (maj_stat != GSS_S_COMPLETE) {
os_log(na_get_oslog(), "ChangeCred: name not importable %s/%s", n, mechname);
free(n);
return false;
}
maj_stat = gss_acquire_cred(&min_stat, gname, GSS_C_INDEFINITE, oid ? &mechset : NULL, GSS_C_INITIATE, &cred, NULL, NULL);
gss_release_name(&min_stat, &gname);
if (maj_stat != GSS_S_COMPLETE) {
os_log(na_get_oslog(), "ChangeCred: cred name %s/%s not found", n, mechname);
free(n);
return false;
}
free(n);
{
gss_buffer_desc buffer;
maj_stat = gss_cred_label_get(&min_stat, cred, nah_created, &buffer);
if (maj_stat) {
gss_release_cred(&min_stat, &cred);
return false;
}
gss_release_buffer(&min_stat, &buffer);
}
if (count == 0) {
} else if (count > 0) {
gss_cred_hold(&min_stat, cred);
} else {
gss_cred_unhold(&min_stat, cred);
}
if (label) {
gss_buffer_desc buffer = {
.value = "1",
.length = 1
};
gss_cred_label_set(&min_stat, cred, label, &buffer);
}
gss_release_cred(&min_stat, &cred);
return true;
}
}
char *
NAHCreateRefLabelFromIdentifier(CFStringRef identifier)
{
CFStringRef str = CFStringCreateWithFormat(NULL, NULL, CFSTR("reference-label:%@"), identifier);
OSStatus ret;
char *label;
if (str == NULL)
return NULL;
ret = __KRBCreateUTF8StringFromCFString(str, &label);
CFRelease(str);
if (ret != noErr)
return NULL;
return label;
}
Boolean
NAHAddReferenceAndLabel(NAHSelectionRef selection,
CFStringRef identifier)
{
CFStringRef ref;
Boolean res;
char *ident;
ref = NAHCopyReferenceKey(selection);
if (ref == NULL)
return false;
os_log(na_get_oslog(), "NAHAddReferenceAndLabel: %@ label: %@", ref, identifier);
ident = NAHCreateRefLabelFromIdentifier(identifier);
if (ident == NULL) {
CFRelease(ref);
return false;
}
res = CredChange(ref, 1, ident);
CFRelease(ref);
__KRBReleaseUTF8String(ident);
return res;
}
CFStringRef
NAHCopyReferenceKey(NAHSelectionRef selection)
{
CFStringRef type;
if (selection->client == NULL)
return NULL;
switch (selection->mech) {
case GSS_KERBEROS:
type = CFSTR("krb5");
break;
case GSS_KERBEROS_PKU2U:
case GSS_KERBEROS_IAKERB:
type = CFSTR("uuid");
break;
case GSS_NTLM:
type = CFSTR("ntlm");
break;
default:
return NULL;
}
if (selection->clienttype && CFStringCompare(selection->clienttype, kNAHNTUUID, 0) == 0)
type = CFSTR("uuid");
return CFStringCreateWithFormat(NULL, 0, CFSTR("%@:%@"),
type, selection->client);
}
void
NAHFindByLabelAndRelease(CFStringRef identifier)
{
OM_uint32 junk;
char *str;
os_log(na_get_oslog(), "NAHFindByLabelAndRelease: looking for label %@", identifier);
str = NAHCreateRefLabelFromIdentifier(identifier);
if (str == NULL)
return;
(void)gss_iter_creds(&junk, 0, GSS_C_NO_OID, ^(gss_OID mech, gss_cred_id_t cred) {
OM_uint32 min_stat, maj_stat;
gss_buffer_desc buffer;
if (cred == NULL)
return;
maj_stat = gss_cred_label_get(&min_stat, cred, nah_created, &buffer);
if (maj_stat) {
gss_release_cred(&min_stat, &cred);
return;
}
gss_release_buffer(&min_stat, &buffer);
buffer.value = NULL;
buffer.length = 0;
maj_stat = gss_cred_label_get(&min_stat, cred, str, &buffer);
gss_release_buffer(&min_stat, &buffer);
if (maj_stat == GSS_S_COMPLETE) {
os_log(na_get_oslog(), "NAHFindByLabelAndRelease: found credential unholding");
gss_cred_label_set(&min_stat, cred, str, NULL);
gss_cred_unhold(&min_stat, cred);
}
gss_release_cred(&min_stat, &cred);
});
__KRBReleaseUTF8String(str);
}
Boolean
NAHCredAddReference(CFStringRef referenceKey)
{
return CredChange(referenceKey, 1, NULL);
}
Boolean
NAHCredRemoveReference(CFStringRef referenceKey)
{
return CredChange(referenceKey, -1, NULL);
}
static CFStringRef kDomainKey = CFSTR("domain");
static CFStringRef kUsername = CFSTR("user");
static CFStringRef kMech = CFSTR("mech");
static CFStringRef kClient = CFSTR("client");
static void
add_user_selections(NAHRef na)
{
CFArrayRef array;
enum NAHMechType mech = GSS_KERBEROS;
CFIndex n;
array = CFPreferencesCopyAppValue(CFSTR("UserSelections"),
CFSTR("com.apple.NetworkAuthenticationHelper"));
if (array == NULL || CFGetTypeID(array) != CFArrayGetTypeID()) {
CFRELEASE(array);
return;
}
for (n = 0; n < CFArrayGetCount(array); n++) {
CFDictionaryRef dict = CFArrayGetValueAtIndex(array, n);
CFStringRef server = NULL;
CFStringRef d, u, m, c;
if (CFGetTypeID(dict) != CFDictionaryGetTypeID())
continue;
m = CFDictionaryGetValue(dict, kMech);
d = CFDictionaryGetValue(dict, kDomainKey);
u = CFDictionaryGetValue(dict, kUsername);
c = CFDictionaryGetValue(dict, kClient);
if (c == NULL || CFGetTypeID(c) != CFStringGetTypeID())
continue;
if (m == NULL || CFGetTypeID(m) != CFStringGetTypeID())
continue;
if (d == NULL || CFGetTypeID(d) != CFStringGetTypeID())
continue;
if (u == NULL && CFGetTypeID(u) != CFStringGetTypeID())
continue;
if (CFStringCompare(d, na->hostname, kCFCompareCaseInsensitive) != kCFCompareEqualTo)
continue;
if (u == NULL) {
if (CFStringCompare(d, na->username, 0) != kCFCompareEqualTo)
continue;
}
mech = name2mech(m);
if (mech == NO_MECH)
continue;
server = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%@"),
na->service, na->hostname);
if (server && c)
addSelection(na, c, NULL, server, NULL, mech, NULL, true);
CFRELEASE(server);
}
CFRelease(array);
}
static gss_OID
ntstring2oid(CFStringRef name)
{
if (CFStringCompare(name, kNAHNTServiceBasedName, 0) == 0)
return GSS_C_NT_HOSTBASED_SERVICE;
else if (CFStringCompare(name, kNAHNTKRB5PrincipalReferral, 0) == 0)
return GSS_KRB5_NT_PRINCIPAL_NAME_REFERRAL;
else if (CFStringCompare(name, kNAHNTKRB5Principal, 0) == 0)
return GSS_KRB5_NT_PRINCIPAL_NAME;
else if (CFStringCompare(name, kNAHNTUUID, 0) == 0)
return GSS_C_NT_UUID;
return NULL;
}
gss_cred_id_t
NAHSelectionGetGSSCredential(NAHSelectionRef selection, CFErrorRef *error)
{
gss_buffer_desc buffer;
OM_uint32 minor_status, major_status, junk;
gss_name_t name;
gss_cred_id_t cred = NULL;
gss_OID nt;
if (error)
*error = NULL;
if (selection->client == NULL)
return NULL;
nt = ntstring2oid(selection->clienttype);
if (nt == NULL)
nt = GSS_C_NT_USER_NAME;
buffer.value = cf2cstring(selection->client);
if (buffer.value == NULL)
return NULL;
buffer.length = strlen((char *)buffer.value);
major_status = gss_import_name(&minor_status, &buffer, nt, &name);
free(buffer.value);
if (major_status) {
updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), selection->server);
return NULL;
}
major_status = gss_acquire_cred(&minor_status, name, GSS_C_INDEFINITE, NULL, GSS_C_INITIATE, &cred, NULL, NULL);
gss_release_name(&junk, &name);
if (major_status) {
updateError(NULL, error, major_status, CFSTR("Failed create credential for %@"), selection->server);
return NULL;
}
return cred;
}
gss_name_t
NAHSelectionGetGSSAcceptorName(NAHSelectionRef selection, CFErrorRef *error)
{
gss_buffer_desc buffer;
OM_uint32 minor_status, major_status;
gss_name_t name;
gss_OID nt;
if (error)
*error = NULL;
if (selection->server == NULL)
return GSS_C_NO_NAME;
buffer.value = cf2cstring(selection->server);
if (buffer.value == NULL)
return NULL;
buffer.length = strlen((char *)buffer.value);
nt = ntstring2oid(selection->servertype);
if (nt == NULL)
nt = GSS_C_NT_HOSTBASED_SERVICE;
major_status = gss_import_name(&minor_status, &buffer, nt, &name);
free(buffer.value);
if (major_status)
updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), selection->server);
return name;
}
gss_OID
NAHSelectionGetGSSMech(NAHSelectionRef selection)
{
return mech2oid(selection->mech);
}
gss_cred_id_t
NAHAuthenticationInfoCopyClientCredential(CFDictionaryRef authInfo, CFErrorRef *error)
{
gss_buffer_desc buffer;
OM_uint32 minor_status, major_status, junk;
gss_name_t name;
gss_cred_id_t cred = NULL;
gss_OID nt, mech;
if (error)
*error = NULL;
CFStringRef mechanism = CFDictionaryGetValue(authInfo, kNAHCredentialType);
CFStringRef clientName = CFDictionaryGetValue(authInfo, kNAHClientPrincipal);
CFStringRef clientNameType = CFDictionaryGetValue(authInfo, kNAHClientNameType);
if (mechanism == NULL || clientName == NULL || clientNameType == NULL) {
updateError(NULL, error, EINVAL, CFSTR("key missing from AuthenticationInfo"));
return NULL;
}
mech = name2oid(mechanism);
if (mech == NULL) {
updateError(NULL, error, EINVAL, CFSTR("unknown mech"));
return NULL;
}
nt = ntstring2oid(clientNameType);
if (nt == NULL)
nt = GSS_C_NT_USER_NAME;
buffer.value = cf2cstring(clientName);
if (buffer.value == NULL)
return NULL;
buffer.length = strlen((char *)buffer.value);
major_status = gss_import_name(&minor_status, &buffer, nt, &name);
free(buffer.value);
if (major_status) {
updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), clientName);
return NULL;
}
major_status = gss_acquire_cred(&minor_status, name, GSS_C_INDEFINITE, NULL, GSS_C_INITIATE, &cred, NULL, NULL);
gss_release_name(&junk, &name);
if (major_status) {
updateError(NULL, error, major_status, CFSTR("Failed create credential for %@"), clientName);
return NULL;
}
return cred;
}
gss_name_t
NAHAuthenticationInfoCopyServerName(CFDictionaryRef authInfo, CFErrorRef *error)
{
gss_buffer_desc buffer;
OM_uint32 minor_status, major_status;
gss_name_t name;
gss_OID nt;
if (error)
*error = NULL;
CFStringRef serverName = CFDictionaryGetValue(authInfo, kNAHServerPrincipal);
CFStringRef serverNameType = CFDictionaryGetValue(authInfo, kNAHServerNameType);
if (serverName == NULL || serverNameType == NULL) {
updateError(NULL, error, EINVAL, CFSTR("key missing from AuthenticationInfo"));
return NULL;
}
nt = ntstring2oid(serverNameType);
if (nt == NULL)
nt = GSS_C_NT_HOSTBASED_SERVICE;
buffer.value = cf2cstring(serverName);
if (buffer.value == NULL)
return NULL;
buffer.length = strlen((char *)buffer.value);
major_status = gss_import_name(&minor_status, &buffer, nt, &name);
free(buffer.value);
if (major_status) {
updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), serverName);
return NULL;
}
return name;
}
gss_OID
NAHAuthenticationInfoGetGSSMechanism(CFDictionaryRef authInfo, CFErrorRef *error)
{
CFStringRef mechanism = CFDictionaryGetValue(authInfo, kNAHMechanism);
if (mechanism == NULL) {
updateError(NULL, error, EINVAL, CFSTR("key missing from AuthenticationInfo"));
return NULL;
}
return name2oid(mechanism);
}
CFStringRef kGSSAPIMechNTLM = CFSTR("NTLM");
CFStringRef kGSSAPIMechKerberos = CFSTR("Kerberos");
CFStringRef kGSSAPIMechKerberosU2U = CFSTR("KerberosUser2User");
CFStringRef kGSSAPIMechKerberosMicrosoft = CFSTR("KerberosMicrosoft");
CFStringRef kGSSAPIMechIAKerb = CFSTR("IAKerb");
CFStringRef kGSSAPIMechPKU2U = CFSTR("PKU2U");
CFStringRef kGSSAPIMechSPNEGO = CFSTR("SPNEGO");