DWORD ADUKerb5DestroyCache( PSTR pszCachePath ) { DWORD dwError = 0; krb5_error_code ret = 0; krb5_context ctx = NULL; krb5_ccache cc = NULL; ret = krb5_init_context(&ctx); BAIL_ON_KRB_ERROR(ctx, ret); /* use krb5_cc_resolve to get an alternate cache */ ret = krb5_cc_resolve(ctx, pszCachePath, &cc); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_destroy(ctx, cc); if (ret != 0) { if (ret != KRB5_FCC_NOFILE) { BAIL_ON_KRB_ERROR(ctx, ret); } else { ret = 0; } } error: if (ctx) { krb5_free_context(ctx); } return(dwError); }
DWORD ADUKrb5GetPrincipalName( PCSTR pszCachePath, PSTR* ppszPrincipalName ) { DWORD dwError = 0; krb5_error_code ret = 0; krb5_context ctx = NULL; krb5_ccache cc = NULL; krb5_principal pKrb5Principal = NULL; PSTR pszKrb5PrincipalName = NULL; PSTR pszPrincipalName = NULL; ret = krb5_init_context(&ctx); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_resolve(ctx, pszCachePath, &cc); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_get_principal(ctx, cc, &pKrb5Principal); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_unparse_name(ctx, pKrb5Principal, &pszKrb5PrincipalName); BAIL_ON_KRB_ERROR(ctx, ret); dwError = LwAllocateString(pszKrb5PrincipalName, &pszPrincipalName); BAIL_ON_MAC_ERROR(dwError); *ppszPrincipalName = pszPrincipalName; cleanup: if (ctx) { if (pszKrb5PrincipalName) { krb5_free_unparsed_name(ctx, pszKrb5PrincipalName); } if (pKrb5Principal) { krb5_free_principal(ctx, pKrb5Principal); } if (cc) { krb5_cc_close(ctx, cc); } krb5_free_context(ctx); } return dwError; error: *ppszPrincipalName = NULL; goto cleanup; }
DWORD ADUKrb5GetDefaultCachePath( PSTR* ppszPath ) { DWORD dwError = 0; krb5_error_code ret = 0; PCSTR pszCurrentPath = NULL; PSTR pszPath = NULL; krb5_context ctx = NULL; ret = krb5_init_context(&ctx); BAIL_ON_KRB_ERROR(ctx, ret); pszCurrentPath = krb5_cc_default_name(ctx); if (IsNullOrEmptyString(pszCurrentPath)) { dwError = ENOENT; BAIL_ON_MAC_ERROR(dwError); } dwError = LwAllocateString( pszCurrentPath, &pszPath); BAIL_ON_MAC_ERROR(dwError); *ppszPath = pszPath; cleanup: if (ctx) { krb5_free_context(ctx); } return dwError; error: *ppszPath = NULL; goto cleanup; }
DWORD LwKrb5GetDefaultCachePath( OUT PSTR* ppszCachePath ) { DWORD dwError = 0; PSTR pszCachePath = NULL; krb5_context ctx = NULL; const char *pszKrbDefault = NULL; krb5_error_code ret = 0; ret = krb5_init_context(&ctx); BAIL_ON_KRB_ERROR(ctx, ret); pszKrbDefault = krb5_cc_default_name(ctx); dwError = LwAllocateString( pszKrbDefault, &pszCachePath); BAIL_ON_LW_ERROR(dwError); *ppszCachePath = pszCachePath; cleanup: if (ctx) { krb5_free_context(ctx); } return dwError; error: *ppszCachePath = NULL; goto cleanup; }
DWORD LsaSetSMBCreds( IN PCSTR pszUserPrincipalName, IN PCSTR pszPassword, IN BOOLEAN bSetDefaultCachePath, OUT PLSA_CREDS_FREE_INFO* ppFreeInfo ) { DWORD dwError = 0; krb5_error_code ret = 0; PSTR pszNewCachePath = NULL; PCSTR pszCacheName = NULL; PCSTR pszCacheType = NULL; krb5_context ctx = 0; krb5_ccache cc = 0; LW_PIO_CREDS pNewCreds = NULL; LW_PIO_CREDS pOldCreds = NULL; PLSA_CREDS_FREE_INFO pFreeInfo = NULL; PSTR pszOldCachePath = NULL; BOOLEAN bSwitchedPath = FALSE; BAIL_ON_INVALID_POINTER(ppFreeInfo); BAIL_ON_INVALID_STRING(pszUserPrincipalName); 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); pszCacheType = krb5_cc_get_type(ctx, cc); pszCacheName = krb5_cc_get_name(ctx, cc); dwError = LwAllocateStringPrintf(&pszNewCachePath, "%s:%s", pszCacheType, pszCacheName); BAIL_ON_LSA_ERROR(dwError); dwError = LwKrb5GetTgt( pszUserPrincipalName, pszPassword, pszNewCachePath, NULL); BAIL_ON_LSA_ERROR(dwError); if (bSetDefaultCachePath) { LSA_LOG_DEBUG("Switching default credentials path for new access token"); dwError = LwKrb5SetThreadDefaultCachePath( pszNewCachePath, &pszOldCachePath); BAIL_ON_LSA_ERROR(dwError); bSwitchedPath = TRUE; } dwError = LwIoCreateKrb5CredsA( pszUserPrincipalName, pszNewCachePath, &pNewCreds); BAIL_ON_LSA_ERROR(dwError); dwError = LwAllocateMemory(sizeof(*pFreeInfo), (PVOID*)&pFreeInfo); BAIL_ON_LSA_ERROR(dwError); dwError = LwIoGetThreadCreds(&pOldCreds); BAIL_ON_LSA_ERROR(dwError); dwError = LwIoSetThreadCreds(pNewCreds); BAIL_ON_LSA_ERROR(dwError); pFreeInfo->ctx = ctx; pFreeInfo->cc = cc; pFreeInfo->pRestoreCreds = pOldCreds; pFreeInfo->pszRestoreCache = pszOldCachePath; pFreeInfo->bKrbCreds = TRUE; pOldCreds = NULL; cleanup: *ppFreeInfo = pFreeInfo; if (pOldCreds != NULL) { LwIoDeleteCreds(pOldCreds); } if (pNewCreds != NULL) { LwIoDeleteCreds(pNewCreds); } LW_SAFE_FREE_STRING(pszNewCachePath); return dwError; error: if (ctx != NULL) { if (cc != NULL) { krb5_cc_destroy(ctx, cc); } krb5_free_context(ctx); } if (pFreeInfo) { LwFreeMemory(pFreeInfo); pFreeInfo = NULL; } if (bSwitchedPath) { LwKrb5SetThreadDefaultCachePath( pszOldCachePath, NULL); LW_SAFE_FREE_STRING(pszOldCachePath); } goto cleanup; }
DWORD LwTaskAcquireCredsA( PCSTR pszUsername, /* IN */ PCSTR pszPassword, /* IN */ PLW_TASK_CREDS* ppCreds /* IN OUT */ ) { DWORD dwError = 0; krb5_error_code ret = 0; PSTR pszNewCachePath = NULL; PLW_TASK_CREDS pCreds = NULL; BAIL_ON_INVALID_POINTER(ppCreds); BAIL_ON_INVALID_STRING(pszUsername); dwError = LwAllocateMemory(sizeof(*pCreds), (PVOID*)&pCreds); BAIL_ON_LW_TASK_ERROR(dwError); ret = krb5_init_context(&pCreds->ctx); BAIL_ON_KRB_ERROR(pCreds->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(pCreds->ctx, "FILE", "hint", &pCreds->cc); BAIL_ON_KRB_ERROR(pCreds->ctx, ret); dwError = LwAllocateStringPrintf( &pszNewCachePath, "%s:%s", krb5_cc_get_type(pCreds->ctx, pCreds->cc), krb5_cc_get_name(pCreds->ctx, pCreds->cc)); BAIL_ON_LW_TASK_ERROR(dwError); dwError = LwKrb5GetTgt(pszUsername, pszPassword, pszNewCachePath, NULL); BAIL_ON_LW_TASK_ERROR(dwError); dwError = LwKrb5SetDefaultCachePath( pszNewCachePath, &pCreds->pszRestoreCache); BAIL_ON_LW_TASK_ERROR(dwError); dwError = LwIoCreateKrb5CredsA( pszUsername, pszNewCachePath, &pCreds->pKrb5Creds); BAIL_ON_LW_TASK_ERROR(dwError); *ppCreds = pCreds; cleanup: LW_SAFE_FREE_STRING(pszNewCachePath); return dwError; error: *ppCreds = NULL; if (pCreds) { LwTaskFreeCreds(pCreds); } goto cleanup; }
DWORD LwKrb5VerifyPac( krb5_context ctx, const krb5_ticket *pTgsTicket, const struct berval *pPacBerVal, const krb5_keyblock *serviceKey, char** ppchLogonInfo, size_t* psLogonInfo ) { krb5_error_code ret = 0; PAC_DATA *pPacData = NULL; DWORD i; char *pchPacCopy = NULL; //Do not free krb5_data krbPacData = {0}; //Do not free krb5_checksum checksum = {0}; //Do not free PAC_SIGNATURE_DATA *pServerSig = NULL; PAC_LOGON_NAME *pLogonName = NULL; size_t sServerSig = 0; //Do not free char *pchLogonInfoStart = NULL; size_t sLogonInfoLen = 0; krb5_boolean bHasGoodChecksum = FALSE; uint64_t qwNtAuthTime; DWORD dwError = LW_ERROR_SUCCESS; //Free with krb5_free_unparsed_name PSTR pszClientPrincipal = NULL; PSTR pszLogonName = NULL; char* pchLogonInfo = NULL; #if defined(WORDS_BIGENDIAN) WORD * pwNameLocal = NULL; DWORD dwCount = 0; #endif dwError = LwAllocateMemory( pPacBerVal->bv_len, OUT_PPVOID(&pPacData)); BAIL_ON_LW_ERROR(dwError); memcpy(pPacData, pPacBerVal->bv_val, pPacBerVal->bv_len); #if defined(WORDS_BIGENDIAN) pPacData->dwBufferCount = LW_ENDIAN_SWAP32(pPacData->dwBufferCount); pPacData->dwVersion = LW_ENDIAN_SWAP32(pPacData->dwVersion); #endif // We only know about version 0 if (pPacData->dwVersion != 0) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } // Make sure that the last buffer in the pac data doesn't go out of bounds // of the parent buffer if ((void *)&pPacData->buffers[pPacData->dwBufferCount] - (void *)pPacData > pPacBerVal->bv_len) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } // Make sure the data associated with each buffer doesn't go out of // bounds for (i = 0; i < pPacData->dwBufferCount; i++) { #if defined(WORDS_BIGENDIAN) pPacData->buffers[i].dwType = LW_ENDIAN_SWAP32(pPacData->buffers[i].dwType); pPacData->buffers[i].dwSize = LW_ENDIAN_SWAP32(pPacData->buffers[i].dwSize); pPacData->buffers[i].qwOffset = LW_ENDIAN_SWAP64(pPacData->buffers[i].qwOffset); #endif if (pPacData->buffers[i].qwOffset + pPacData->buffers[i].dwSize < pPacData->buffers[i].qwOffset) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } if (pPacData->buffers[i].qwOffset + pPacData->buffers[i].dwSize > pPacBerVal->bv_len) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } } dwError = LwAllocateMemory( pPacBerVal->bv_len, OUT_PPVOID(&pchPacCopy)); BAIL_ON_LW_ERROR(dwError); memcpy(pchPacCopy, pPacBerVal->bv_val, pPacBerVal->bv_len); krbPacData.magic = KV5M_DATA; krbPacData.length = pPacBerVal->bv_len; krbPacData.data = pchPacCopy; for (i = 0; i < pPacData->dwBufferCount; i++) { switch (pPacData->buffers[i].dwType) { case PAC_TYPE_LOGON_INFO: pchLogonInfoStart = (char *)pPacData + pPacData->buffers[i].qwOffset; sLogonInfoLen = pPacData->buffers[i].dwSize; break; case PAC_TYPE_SRV_CHECKSUM: pServerSig = (PAC_SIGNATURE_DATA *)((char *)pPacData + pPacData->buffers[i].qwOffset); #if defined(WORDS_BIGENDIAN) pServerSig->dwType = LW_ENDIAN_SWAP32(pServerSig->dwType); #endif sServerSig = pPacData->buffers[i].dwSize - (size_t)&((PAC_SIGNATURE_DATA *)0)->pchSignature; /* The checksum is calculated with the signatures zeroed out. */ memset(pchPacCopy + pPacData->buffers[i].qwOffset + (size_t)&((PAC_SIGNATURE_DATA *)0)->pchSignature, 0, pPacData->buffers[i].dwSize - (size_t)&((PAC_SIGNATURE_DATA *)0)->pchSignature); break; case PAC_TYPE_KDC_CHECKSUM: /* The checksum is calculated with the signatures zeroed out. */ memset(pchPacCopy + pPacData->buffers[i].qwOffset + (size_t)&((PAC_SIGNATURE_DATA *)0)->pchSignature, 0, pPacData->buffers[i].dwSize - (size_t)&((PAC_SIGNATURE_DATA *)0)->pchSignature); break; case PAC_TYPE_LOGON_NAME: pLogonName = (PAC_LOGON_NAME *)((char *)pPacData + pPacData->buffers[i].qwOffset); #if defined(WORDS_BIGENDIAN) pLogonName->ticketTime = LW_ENDIAN_SWAP64(pLogonName->ticketTime); pLogonName->wAccountNameLen = LW_ENDIAN_SWAP16(pLogonName->wAccountNameLen); pwNameLocal = pLogonName->pwszName; for ( dwCount = 0 ; dwCount < pLogonName->wAccountNameLen / 2 ; dwCount++ ) { pwNameLocal[dwCount] = LW_ENDIAN_SWAP16(pwNameLocal[dwCount]); } #endif if ((char *)&pLogonName->pwszName + pLogonName->wAccountNameLen > (char *)pPacData + pPacData->buffers[i].qwOffset + pPacData->buffers[i].dwSize) { // The message is invalid because the terminating null // of the name lands outside of the buffer. dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } break; default: break; } } if (pServerSig == NULL) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } if (pLogonName == NULL) { //We need the logon name to verify the pac is for the right user dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } if (pchLogonInfoStart == NULL) { /* The buffer we really care about isn't in the pac. */ dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } checksum.magic = KV5M_CHECKSUM; checksum.checksum_type = pServerSig->dwType; checksum.length = sServerSig; checksum.contents = (unsigned char *)pServerSig->pchSignature; ret = krb5_c_verify_checksum( ctx, serviceKey, KRB5_KEYUSAGE_APP_DATA_CKSUM, &krbPacData, &checksum, &bHasGoodChecksum); BAIL_ON_KRB_ERROR(ctx, ret); if (!bHasGoodChecksum) { dwError = LW_ERROR_INVALID_MESSAGE; BAIL_ON_LW_ERROR(dwError); } // Make sure the pac was issued with this ticket, not an old ticket qwNtAuthTime = pTgsTicket->enc_part2->times.authtime; qwNtAuthTime += 11644473600LL; qwNtAuthTime *= 1000*1000*10; if (pLogonName->ticketTime != qwNtAuthTime) { dwError = LW_ERROR_CLOCK_SKEW; BAIL_ON_LW_ERROR(dwError); } ret = krb5_unparse_name( ctx, pTgsTicket->enc_part2->client, &pszClientPrincipal); BAIL_ON_KRB_ERROR(ctx, ret); // Strip off the domain name if (strchr(pszClientPrincipal, '@') != NULL) { strchr(pszClientPrincipal, '@')[0] = '\0'; } dwError = LwWc16snToMbs( pLogonName->pwszName, &pszLogonName, pLogonName->wAccountNameLen / 2); BAIL_ON_LW_ERROR(dwError); if (strcasecmp(pszClientPrincipal, pszLogonName)) { // The pac belongs to a different user dwError = LW_ERROR_INVALID_LOGIN_ID; BAIL_ON_LW_ERROR(dwError); } dwError = LwAllocateMemory( sLogonInfoLen, OUT_PPVOID(&pchLogonInfo)); BAIL_ON_LW_ERROR(dwError); memcpy(pchLogonInfo, pchLogonInfoStart, sLogonInfoLen); *ppchLogonInfo = pchLogonInfo; *psLogonInfo = sLogonInfoLen; cleanup: LW_SAFE_FREE_STRING(pszLogonName); LW_SAFE_FREE_MEMORY(pPacData); LW_SAFE_FREE_MEMORY(pchPacCopy); if (pszClientPrincipal != NULL) { krb5_free_unparsed_name(ctx, pszClientPrincipal); } return dwError; error: LW_SAFE_FREE_MEMORY(pchLogonInfo); *ppchLogonInfo = NULL; goto cleanup; }
DWORD LwKrb5CopyFromUserCache( krb5_context ctx, krb5_ccache destCC, uid_t uid ) { DWORD dwError = LW_ERROR_SUCCESS; PSTR pszCachePath = NULL; krb5_ccache srcCC = NULL; krb5_cc_cursor srcPos = NULL; krb5_cc_cursor destPos = NULL; // Free with krb5_free_cred_contents krb5_creds srcCreds = {0}; // Free with krb5_free_cred_contents krb5_creds destCreds = {0}; krb5_error_code ret = 0; krb5_principal destClient = 0; BOOLEAN bIncludeTicket = TRUE; DWORD dwTime = 0; ret = krb5_cc_get_principal( ctx, destCC, &destClient); BAIL_ON_KRB_ERROR(ctx, ret); dwError = LwKrb5GetUserCachePath( uid, KRB5_File_Cache, &pszCachePath); BAIL_ON_LW_ERROR(dwError); ret = krb5_cc_resolve( ctx, pszCachePath, &srcCC); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_start_seq_get( ctx, srcCC, &srcPos); if (ret == KRB5_FCC_NOFILE) { // The cache file does not exist ret = 0; goto cleanup; } if (ret == KRB5_CC_FORMAT) { // Some other user put a bad cc in place - don't copy anything // from it. ret = 0; goto cleanup; } BAIL_ON_KRB_ERROR(ctx, ret); dwTime = time(NULL); while (1) { krb5_free_cred_contents( ctx, &srcCreds); ret = krb5_cc_next_cred( ctx, srcCC, &srcPos, &srcCreds); if (ret == KRB5_CC_FORMAT) { break; } else if (ret == KRB5_CC_END) { break; } else { BAIL_ON_KRB_ERROR(ctx, ret); } if (!krb5_principal_compare(ctx, destClient, srcCreds.client)) { /* Can't keep these creds. The client principal doesn't * match. */ continue; } if ( srcCreds.times.endtime < dwTime ) { /* Credentials are too old. */ continue; } if (destPos != NULL) { krb5_cc_end_seq_get( ctx, destCC, &destPos); destPos = NULL; } ret = krb5_cc_start_seq_get( ctx, destCC, &destPos); BAIL_ON_KRB_ERROR(ctx, ret); bIncludeTicket = TRUE; while(bIncludeTicket) { krb5_free_cred_contents( ctx, &destCreds); ret = krb5_cc_next_cred( ctx, destCC, &destPos, &destCreds); if (ret == KRB5_CC_END) { break; } else { BAIL_ON_KRB_ERROR(ctx, ret); } if (krb5_principal_compare( ctx, destCreds.server, srcCreds.server)) { /* These credentials are already in the dest cache */ bIncludeTicket = FALSE; } } if (bIncludeTicket) { // These creds can go in the new cache ret = krb5_cc_store_cred(ctx, destCC, &srcCreds); BAIL_ON_KRB_ERROR(ctx, ret); } } cleanup: LW_SAFE_FREE_STRING(pszCachePath); if (ctx != NULL) { if (srcPos != NULL) { krb5_cc_end_seq_get( ctx, srcCC, &srcPos); } if (destPos != NULL) { krb5_cc_end_seq_get( ctx, destCC, &destPos); } if (srcCC != NULL) { krb5_cc_close(ctx, srcCC); } krb5_free_cred_contents(ctx, &srcCreds); krb5_free_cred_contents(ctx, &destCreds); if (destClient != NULL) { krb5_free_principal(ctx, destClient); } } return dwError; error: goto cleanup; }
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 DWORD ADUKerb5GetTGTFromKeytab( char *szUserName, char *szPassword, char *pszCachePath, PDWORD pdwGoodUntilTime ) { DWORD dwError = 0; krb5_error_code ret = 0; krb5_context ctx = NULL; krb5_creds creds = { 0 }; krb5_ccache cc = NULL; krb5_keytab keytab = 0; krb5_principal client_principal = NULL; dwError = ADUKerb5DestroyCache(pszCachePath); BAIL_ON_MAC_ERROR(dwError); dwError = LWNetExtendEnvironmentForKrb5Affinity(TRUE); BAIL_ON_MAC_ERROR(dwError); ret = krb5_init_context(&ctx); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_parse_name(ctx, szUserName, &client_principal); BAIL_ON_KRB_ERROR(ctx, ret); /* use krb5_cc_resolve to get an alternate cache */ ret = krb5_cc_resolve(ctx, pszCachePath, &cc); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_kt_default(ctx, &keytab); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_get_init_creds_keytab( ctx, &creds, client_principal, keytab, 0, /* start time */ NULL, /* in_tkt_service */ NULL /* options */ ); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_initialize(ctx, cc, client_principal); BAIL_ON_KRB_ERROR(ctx, ret); ret = krb5_cc_store_cred(ctx, cc, &creds); BAIL_ON_KRB_ERROR(ctx, ret); *pdwGoodUntilTime = creds.times.endtime; error: if (creds.client == client_principal) { creds.client = NULL; } if (ctx) { if (client_principal) { krb5_free_principal(ctx, client_principal); } if (keytab) { krb5_kt_close(ctx, keytab); } if (cc) { krb5_cc_close(ctx, cc); } krb5_free_cred_contents(ctx, &creds); krb5_free_context(ctx); } return(dwError); }