static krb5_error_code ads_secrets_verify_ticket(krb5_context context, krb5_auth_context auth_context, krb5_principal host_princ, const DATA_BLOB *ticket, krb5_ticket **pp_tkt, krb5_keyblock **keyblock, krb5_error_code *perr) { krb5_error_code ret = 0; bool auth_ok = False; char *password_s = NULL; krb5_data password; krb5_enctype enctypes[] = { #if defined(ENCTYPE_ARCFOUR_HMAC) ENCTYPE_ARCFOUR_HMAC, #endif ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5, ENCTYPE_NULL }; krb5_data packet; int i; *pp_tkt = NULL; *keyblock = NULL; *perr = 0; if (!secrets_init()) { DEBUG(1,("ads_secrets_verify_ticket: secrets_init failed\n")); *perr = KRB5_CONFIG_CANTOPEN; return False; } password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL); if (!password_s) { DEBUG(1,("ads_secrets_verify_ticket: failed to fetch machine password\n")); *perr = KRB5_LIBOS_CANTREADPWD; return False; } password.data = password_s; password.length = strlen(password_s); /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */ packet.length = ticket->length; packet.data = (char *)ticket->data; /* We need to setup a auth context with each possible encoding type in turn. */ for (i=0;enctypes[i];i++) { krb5_keyblock *key = NULL; if (!(key = SMB_MALLOC_P(krb5_keyblock))) { ret = ENOMEM; goto out; } if (create_kerberos_key_from_string(context, host_princ, &password, key, enctypes[i], false)) { SAFE_FREE(key); continue; } krb5_auth_con_setuseruserkey(context, auth_context, key); if (!(ret = krb5_rd_req(context, &auth_context, &packet, NULL, NULL, NULL, pp_tkt))) { DEBUG(10,("ads_secrets_verify_ticket: enc type [%u] decrypted message !\n", (unsigned int)enctypes[i] )); auth_ok = True; krb5_copy_keyblock(context, key, keyblock); krb5_free_keyblock(context, key); break; } DEBUG((ret != KRB5_BAD_ENCTYPE) ? 3 : 10, ("ads_secrets_verify_ticket: enc type [%u] failed to decrypt with error %s\n", (unsigned int)enctypes[i], error_message(ret))); /* successfully decrypted but ticket is just not valid at the moment */ if (ret == KRB5KRB_AP_ERR_TKT_NYV || ret == KRB5KRB_AP_ERR_TKT_EXPIRED || ret == KRB5KRB_AP_ERR_SKEW) { krb5_free_keyblock(context, key); break; } krb5_free_keyblock(context, key); } out: SAFE_FREE(password_s); *perr = ret; return auth_ok; }
/* * Serialize krb5_auth_context. */ static krb5_error_code ser_acontext_test(krb5_context kcontext, int verbose) { krb5_error_code kret; krb5_auth_context actx; krb5_address local_address; krb5_address remote_address; krb5_octet laddr_bytes[16]; krb5_octet raddr_bytes[16]; krb5_keyblock ukeyblock; krb5_octet keydata[8]; krb5_authenticator aent; char clname[128]; krb5_authdata *adatalist[3]; krb5_authdata adataent; actx = (krb5_auth_context) NULL; if (!(kret = krb5_auth_con_init(kcontext, &actx)) && !(kret = ser_data(verbose, "> Vanilla auth context", (krb5_pointer) actx, KV5M_AUTH_CONTEXT))) { memset(&local_address, 0, sizeof(local_address)); memset(&remote_address, 0, sizeof(remote_address)); memset(laddr_bytes, 0, sizeof(laddr_bytes)); memset(raddr_bytes, 0, sizeof(raddr_bytes)); local_address.addrtype = ADDRTYPE_INET; local_address.length = sizeof(laddr_bytes); local_address.contents = laddr_bytes; laddr_bytes[0] = 6; laddr_bytes[1] = 2; laddr_bytes[2] = 69; laddr_bytes[3] = 16; laddr_bytes[4] = 1; laddr_bytes[5] = 0; laddr_bytes[6] = 0; laddr_bytes[7] = 127; remote_address.addrtype = ADDRTYPE_INET; remote_address.length = sizeof(raddr_bytes); remote_address.contents = raddr_bytes; raddr_bytes[0] = 6; raddr_bytes[1] = 2; raddr_bytes[2] = 70; raddr_bytes[3] = 16; raddr_bytes[4] = 1; raddr_bytes[5] = 0; raddr_bytes[6] = 0; raddr_bytes[7] = 127; if (!(kret = krb5_auth_con_setaddrs(kcontext, actx, &local_address, &remote_address)) && !(kret = krb5_auth_con_setports(kcontext, actx, &local_address, &remote_address)) && !(kret = ser_data(verbose, "> Auth context with addrs/ports", (krb5_pointer) actx, KV5M_AUTH_CONTEXT))) { memset(&ukeyblock, 0, sizeof(ukeyblock)); memset(keydata, 0, sizeof(keydata)); ukeyblock.enctype = ENCTYPE_DES_CBC_MD5; ukeyblock.length = sizeof(keydata); ukeyblock.contents = keydata; keydata[0] = 0xde; keydata[1] = 0xad; keydata[2] = 0xbe; keydata[3] = 0xef; keydata[4] = 0xfe; keydata[5] = 0xed; keydata[6] = 0xf0; keydata[7] = 0xd; if (!(kret = krb5_auth_con_setuseruserkey(kcontext, actx, &ukeyblock)) && !(kret = ser_data(verbose, "> Auth context with user key", (krb5_pointer) actx, KV5M_AUTH_CONTEXT)) && !(kret = krb5_auth_con_initivector(kcontext, actx)) && !(kret = ser_data(verbose, "> Auth context with new vector", (krb5_pointer) actx, KV5M_AUTH_CONTEXT)) && (krb5_xfree(actx->i_vector), actx->i_vector) && !(kret = krb5_auth_con_setivector(kcontext, actx, (krb5_pointer) print_erep) ) && !(kret = ser_data(verbose, "> Auth context with set vector", (krb5_pointer) actx, KV5M_AUTH_CONTEXT))) { /* * Finally, add an authenticator. */ memset(&aent, 0, sizeof(aent)); aent.magic = KV5M_AUTHENTICATOR; snprintf(clname, sizeof(clname), "help/me/%[email protected]", (int) getpid()); actx->authentp = &aent; if (!(kret = krb5_parse_name(kcontext, clname, &aent.client)) && !(kret = ser_data(verbose, "> Auth context with authenticator", (krb5_pointer) actx, KV5M_AUTH_CONTEXT))) { adataent.magic = KV5M_AUTHDATA; adataent.ad_type = 123; adataent.length = 128; adataent.contents = (krb5_octet *) stuff; adatalist[0] = &adataent; adatalist[1] = &adataent; adatalist[2] = (krb5_authdata *) NULL; aent.authorization_data = adatalist; if (!(kret = ser_data(verbose, "> Auth context with full auth", (krb5_pointer) actx, KV5M_AUTH_CONTEXT))) { if (verbose) printf("* krb5_auth_context test succeeded\n"); } krb5_free_principal(kcontext, aent.client); } actx->authentp = (krb5_authenticator *) NULL; } } } if (actx) krb5_auth_con_free(kcontext, actx); if (kret) printf("* krb5_auth_context test failed\n"); return(kret); }
DWORD LwKrb5InitializeUserLoginCredentials( IN PCSTR pszUserPrincipalName, IN PCSTR pszPassword, IN uid_t uid, IN gid_t gid, IN LW_KRB5_LOGIN_FLAGS Flags, IN PCSTR pszServicePrincipal, IN PCSTR pszServiceRealm, IN PCSTR pszServicePassword, OUT PVOID* ppNdrPacInfo, OUT size_t* pNdrPacInfoSize, OUT PDWORD pdwGoodUntilTime ) { DWORD dwError = 0; krb5_error_code ret = 0; krb5_context ctx = NULL; krb5_ccache cc = NULL; // Free with krb5_free_cred_contents krb5_creds credsRequest = {0}; krb5_creds *pTgsCreds = NULL; krb5_ticket *pTgsTicket = NULL; krb5_ticket *pDecryptedTgs = NULL; krb5_auth_context authContext = NULL; krb5_data apReqPacket = {0}; krb5_keyblock serviceKey = {0}; krb5_data salt = {0}; // Do not free krb5_data machinePassword = {0}; krb5_flags flags = 0; krb5_int32 authcon_flags = 0; BOOLEAN bInLock = FALSE; PCSTR pszTempCacheName = NULL; PSTR pszTempCachePath = NULL; PVOID pNdrPacInfo = NULL; size_t ndrPacInfoSize = 0; DWORD dwGoodUntilTime = 0; ret = krb5_init_context(&ctx); BAIL_ON_KRB_ERROR(ctx, ret); /* Generates a new filed based credentials cache in /tmp. The file will * be owned by root and only accessible by root. */ ret = krb5_cc_new_unique( ctx, "FILE", "hint", &cc); BAIL_ON_KRB_ERROR(ctx, ret); if (Flags & LW_KRB5_LOGIN_FLAG_SMART_CARD) { dwError = LwKrb5GetTgtWithSmartCard( pszUserPrincipalName, pszPassword, krb5_cc_get_name(ctx, cc), &dwGoodUntilTime); } else { dwError = LwKrb5GetTgt( pszUserPrincipalName, pszPassword, krb5_cc_get_name(ctx, cc), &dwGoodUntilTime); } BAIL_ON_LW_ERROR(dwError); ret = krb5_parse_name(ctx, pszServicePrincipal, &credsRequest.server); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_get_principal(ctx, cc, &credsRequest.client); BAIL_ON_KRB_ERROR(ctx, ret); /* Get a TGS for our service using the tgt in the cache */ ret = krb5_get_credentials( ctx, 0, /*no options (not user to user encryption, and not only cached) */ cc, &credsRequest, &pTgsCreds); // Don't trust pTgsCreds on an unsuccessful return // This may be non-zero due to the krb5 libs following referrals // but has been freed in the krb5 libs themselves and any useful // tickets have already been cached. if (ret != 0) { pTgsCreds = NULL; } BAIL_ON_KRB_ERROR(ctx, ret); //No need to store the tgs in the cc. Kerberos does that automatically /* Generate an ap_req message, but don't send it anywhere. Just decode it * immediately. This is the only way to get kerberos to decrypt the tgs * using public APIs */ ret = krb5_mk_req_extended( ctx, &authContext, 0, /* no options necessary */ NULL, /* since this isn't a real ap_req, we don't have any supplemental data to send with it. */ pTgsCreds, &apReqPacket); BAIL_ON_KRB_ERROR(ctx, ret); /* Decode (but not decrypt) the tgs ticket so that we can figure out * which encryption type was used in it. */ ret = krb5_decode_ticket(&pTgsCreds->ticket, &pTgsTicket); /* The TGS ticket is encrypted with the machine password and salted with * the service principal. pszServicePrincipal could probably be used * directly, but it's safer to unparse pTgsCreds->server, because the KDC * sent that to us. */ salt.magic = KV5M_DATA; ret = krb5_unparse_name( ctx, pTgsCreds->server, &salt.data); BAIL_ON_KRB_ERROR(ctx, ret); salt.length = strlen(salt.data); machinePassword.magic = KV5M_DATA; machinePassword.data = (PSTR)pszServicePassword, machinePassword.length = strlen(pszServicePassword), /* Generate a key to decrypt the TGS */ ret = krb5_c_string_to_key( ctx, pTgsTicket->enc_part.enctype, &machinePassword, &salt, &serviceKey); BAIL_ON_KRB_ERROR(ctx, ret); /* Typically krb5_rd_req would decode the AP_REQ using the keytab, but * we don't want to depend on the keytab. As a side effect of kerberos' * user to user authentication support, if a key is explictly set on the * auth context, that key will be used to decrypt the TGS instead of the * keytab. * * By manually generating the key and setting it, we don't require * a keytab. */ if (authContext != NULL) { ret = krb5_auth_con_free(ctx, authContext); BAIL_ON_KRB_ERROR(ctx, ret); } ret = krb5_auth_con_init(ctx, &authContext); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_auth_con_setuseruserkey( ctx, authContext, &serviceKey); BAIL_ON_KRB_ERROR(ctx, ret); /* Disable replay detection which is unnecessary and * can fail when authenticating large numbers of users. */ krb5_auth_con_getflags(ctx, authContext, &authcon_flags); krb5_auth_con_setflags(ctx, authContext, authcon_flags & ~KRB5_AUTH_CONTEXT_DO_TIME); if (pszServiceRealm) { ret = krb5_set_default_realm(ctx, pszServiceRealm); BAIL_ON_KRB_ERROR(ctx, ret); } /* This decrypts the TGS. As a side effect it ensures that the KDC that * the user's TGT came from is in the same realm that the machine was * joined to (this prevents users from spoofing the KDC). */ ret = krb5_rd_req( ctx, &authContext, &apReqPacket, pTgsCreds->server, NULL, /* we're not using the keytab */ &flags, &pDecryptedTgs); BAIL_ON_KRB_ERROR(ctx, ret); dwError = LwKrb5FindPac( ctx, pDecryptedTgs, &serviceKey, &pNdrPacInfo, &ndrPacInfoSize); BAIL_ON_LW_ERROR(dwError); if (Flags & LW_KRB5_LOGIN_FLAG_UPDATE_CACHE) { /* 1. Copy old credentials from the existing user creds cache to * the temporary cache. * 2. Delete the existing creds cache. * 3. Move the temporary cache file into the final path. */ dwError = pthread_mutex_lock(&gLwKrb5State.UserCacheMutex); BAIL_ON_LW_ERROR(dwError); bInLock = TRUE; dwError = LwKrb5CopyFromUserCache( ctx, cc, uid ); BAIL_ON_LW_ERROR(dwError); pszTempCacheName = krb5_cc_get_name(ctx, cc); if (!strncasecmp(pszTempCacheName, "FILE:", sizeof("FILE:")-1)) { pszTempCacheName += sizeof("FILE:") - 1; } dwError = LwAllocateString(pszTempCacheName, &pszTempCachePath); BAIL_ON_LW_ERROR(dwError); krb5_cc_close(ctx, cc); // Just to make sure no one accesses this now invalid pointer cc = NULL; dwError = LwKrb5MoveCCacheToUserPath( ctx, pszTempCachePath, uid, gid); if (dwError != LW_ERROR_SUCCESS) { /* Let the user login, even if we couldn't create the ccache for * them. Possible causes are: * 1. /tmp is readonly * 2. Another user maliciously setup a weird file (such as a * directory) where the ccache would go. * 3. Someone created a ccache in the small window after we delete * the old one and before we move in the new one. */ LW_LOG_WARNING("Unable to set up credentials cache with tgt for uid %ld", (long)uid); dwError = LwRemoveFile(pszTempCachePath); BAIL_ON_LW_ERROR(dwError); } } error: if (dwError) { LW_SAFE_FREE_MEMORY(pNdrPacInfo); ndrPacInfoSize = 0; dwGoodUntilTime = 0; } if (ctx) { // This function skips fields which are NULL krb5_free_cred_contents(ctx, &credsRequest); if (pTgsCreds != NULL) { krb5_free_creds(ctx, pTgsCreds); } if (pTgsTicket != NULL) { krb5_free_ticket(ctx, pTgsTicket); } if (pDecryptedTgs != NULL) { krb5_free_ticket(ctx, pDecryptedTgs); } if (authContext != NULL) { krb5_auth_con_free(ctx, authContext); } krb5_free_data_contents(ctx, &apReqPacket); krb5_free_data_contents(ctx, &salt); krb5_free_keyblock_contents(ctx, &serviceKey); if (cc != NULL) { krb5_cc_destroy(ctx, cc); } krb5_free_context(ctx); } if (bInLock) { pthread_mutex_unlock(&gLwKrb5State.UserCacheMutex); } LW_SAFE_FREE_STRING(pszTempCachePath); *ppNdrPacInfo = pNdrPacInfo; *pNdrPacInfoSize = ndrPacInfoSize; *pdwGoodUntilTime = dwGoodUntilTime; return dwError; }
static krb5_error_code ads_secrets_verify_ticket(krb5_context context, krb5_auth_context auth_context, krb5_principal host_princ, const DATA_BLOB *ticket, krb5_ticket **pp_tkt, krb5_keyblock **keyblock, krb5_error_code *perr) { krb5_error_code ret = 0; bool auth_ok = False; bool cont = true; char *password_s = NULL; /* Let's make some room for 2 password (old and new)*/ krb5_data passwords[2]; krb5_enctype enctypes[] = { #ifdef HAVE_ENCTYPE_AES256_CTS_HMAC_SHA1_96 ENCTYPE_AES256_CTS_HMAC_SHA1_96, #endif #ifdef HAVE_ENCTYPE_AES128_CTS_HMAC_SHA1_96 ENCTYPE_AES128_CTS_HMAC_SHA1_96, #endif ENCTYPE_ARCFOUR_HMAC, ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD5, ENCTYPE_NULL }; krb5_data packet; int i, j; *pp_tkt = NULL; *keyblock = NULL; *perr = 0; ZERO_STRUCT(passwords); if (!secrets_init()) { DEBUG(1,("ads_secrets_verify_ticket: secrets_init failed\n")); *perr = KRB5_CONFIG_CANTOPEN; return False; } password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL); if (!password_s) { DEBUG(1,(__location__ ": failed to fetch machine password\n")); *perr = KRB5_LIBOS_CANTREADPWD; return False; } passwords[0].data = password_s; passwords[0].length = strlen(password_s); password_s = secrets_fetch_prev_machine_password(lp_workgroup()); if (password_s) { DEBUG(10, (__location__ ": found previous password\n")); passwords[1].data = password_s; passwords[1].length = strlen(password_s); } /* CIFS doesn't use addresses in tickets. This would break NAT. JRA */ packet.length = ticket->length; packet.data = (char *)ticket->data; /* We need to setup a auth context with each possible encoding type * in turn. */ for (j=0; j<2 && passwords[j].length; j++) { for (i=0;enctypes[i];i++) { krb5_keyblock *key = NULL; if (!(key = SMB_MALLOC_P(krb5_keyblock))) { ret = ENOMEM; goto out; } if (create_kerberos_key_from_string(context, host_princ, &passwords[j], key, enctypes[i], false)) { SAFE_FREE(key); continue; } krb5_auth_con_setuseruserkey(context, auth_context, key); if (!(ret = krb5_rd_req(context, &auth_context, &packet, NULL, NULL, NULL, pp_tkt))) { DEBUG(10, (__location__ ": enc type [%u] " "decrypted message !\n", (unsigned int)enctypes[i])); auth_ok = True; cont = false; krb5_copy_keyblock(context, key, keyblock); krb5_free_keyblock(context, key); break; } DEBUG((ret != KRB5_BAD_ENCTYPE) ? 3 : 10, (__location__ ": enc type [%u] failed to " "decrypt with error %s\n", (unsigned int)enctypes[i], error_message(ret))); /* successfully decrypted but ticket is just not * valid at the moment */ if (ret == KRB5KRB_AP_ERR_TKT_NYV || ret == KRB5KRB_AP_ERR_TKT_EXPIRED || ret == KRB5KRB_AP_ERR_SKEW) { krb5_free_keyblock(context, key); cont = false; break; } krb5_free_keyblock(context, key); } if (!cont) { /* If we found a valid pass then no need to try * the next one or we have invalid ticket so no need * to try next password*/ break; } } out: SAFE_FREE(passwords[0].data); SAFE_FREE(passwords[1].data); *perr = ret; return auth_ok; }