Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2776 lines
70 KiB

//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 2000
//
// File: credman.cxx
//
// Contents: Code for credentials APIs for the Kerberos package
//
//
// History: 23-Feb-2000 Created Jeffspel
//
//------------------------------------------------------------------------
#include <kerb.hxx>
#include <kerbp.h>
#if DBG
static TCHAR THIS_FILE[]=TEXT(__FILE__);
#endif
extern "C"
{
#include <des.h>
}
//+-------------------------------------------------------------------------
//
// Function: KerbFreeCredmanCred
//
// Synopsis: Frees credman cred
//
// Arguments:
//
// Requires:
//
// Returns: NTSTATUS, typically ignored, as failure to update the credman
// should not be fatal.
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbFreeCredmanCred(
IN PKERB_CREDMAN_CRED CredToFree
)
{
DsysAssert(CredToFree);
KerbFreePrimaryCredentials(CredToFree->SuppliedCredentials, TRUE);
KerbFreeString(&CredToFree->CredmanDomainName);
KerbFreeString(&CredToFree->CredmanUserName);
KerbFree(CredToFree);
}
//+-------------------------------------------------------------------------
//
// Function: KerbReferenceCredmanCred
//
// Synopsis: Frees credman cred
//
// Arguments:
//
// Requires:
//
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbReferenceCredmanCred(
IN PKERB_CREDMAN_CRED Cred,
IN PKERB_LOGON_SESSION LogonSession,
IN BOOLEAN Unlink
)
{
KerbReferenceListEntry(
&LogonSession->CredmanCredentials,
&Cred->ListEntry,
Unlink
);
}
//+-------------------------------------------------------------------------
//
// Function: KerbDereferenceCredmanCred
//
// Synopsis: Frees credman cred
//
// Arguments:
//
// Requires:
//
// Returns: NTSTATUS, typically ignored, as failure to update the credman
// should not be fatal.
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbDereferenceCredmanCred(
IN PKERB_CREDMAN_CRED Cred,
IN PKERBEROS_LIST CredmanList
)
{
if (KerbDereferenceListEntry(
&Cred->ListEntry,
CredmanList
))
{
KerbFreeCredmanCred(Cred);
}
}
//+-------------------------------------------------------------------------
//
// Function: KerbFreeCredmanList
//
// Synopsis: Free a credman list from a logon session...
//
// Arguments:
//
// Requires:
//
// Returns: NTSTATUS, typically ignored, as failure to update the credman
// should not be fatal.
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbFreeCredmanList(
KERBEROS_LIST CredmanList
)
{
PKERB_CREDMAN_CRED Cred;
KerbLockList(&CredmanList);
//
// Go through the list of credman creds and dereferences them all
//
while (!IsListEmpty(&CredmanList.List))
{
Cred = CONTAINING_RECORD(
CredmanList.List.Flink,
KERB_CREDMAN_CRED,
ListEntry.Next
);
// unlink cred from list
KerbReferenceListEntry(
&CredmanList,
&Cred->ListEntry,
TRUE
);
KerbDereferenceCredmanCred(
Cred,
&CredmanList
);
}
SafeDeleteCriticalSection(&CredmanList.Lock);
}
//+-------------------------------------------------------------------------
//
// Function: KerbNotifyCredentialManager
//
// Synopsis: This function is used to notify the credential manager of a
// password change event. Note: This will always be a MIT
// session.
//
// Arguments:
//
// Requires:
//
// Returns: NTSTATUS, typically ignored, as failure to update the credman
// should not be fatal.
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbNotifyCredentialManager(
IN PKERB_LOGON_SESSION LogonSession,
IN PKERB_CHANGEPASSWORD_REQUEST ChangeRequest,
IN PKERB_INTERNAL_NAME ClientName,
IN PUNICODE_STRING RealmName
)
{
UNICODE_STRING ClientNameU = {0};
KERBERR KerbErr;
// FESTER:
// We should only expect to get pwd change notification on
// an MIT Realm pwd change, in which case, there isn't a concept of a
// Netbios name ....
KerbErr = KerbConvertKdcNameToString(
&ClientNameU,
ClientName,
NULL
);
if (!KERB_SUCCESS(KerbErr))
{
return;
}
LsaINotifyPasswordChanged(
NULL,
&ClientNameU,
RealmName,
NULL,
&ChangeRequest->OldPassword,
&ChangeRequest->NewPassword,
ChangeRequest->Impersonating
);
KerbFreeString(&ClientNameU);
}
//+-------------------------------------------------------------------------
//
// Function: KerbComparePasswords
//
// Synopsis: Verifies that two stored credentials are identical, simply
// through comparison of KERB_ETYPE_RC4_HMAC_NT keys
//
// Arguments:
//
// Requires:
//
// Returns:
// NULL if the user name is not a marshalled cert, a pointer
// to the
//
// Notes:
//
//
//--------------------------------------------------------------------------
BOOL
KerbComparePasswords(
IN PKERB_STORED_CREDENTIAL PwdList1,
IN PKERB_STORED_CREDENTIAL PwdList2
)
{
PKERB_ENCRYPTION_KEY Key1 = NULL;
PKERB_ENCRYPTION_KEY Key2 = NULL;
ULONG Etype = KERB_ETYPE_RC4_HMAC_NT;
Key1 = KerbGetKeyFromList(
PwdList1,
KERB_ETYPE_RC4_HMAC_NT
);
if (NULL == Key1)
{
Etype = KERB_ETYPE_DES_CBC_MD5;
Key1 = KerbGetKeyFromList(
PwdList1,
Etype
);
if (NULL == Key1)
{
D_DebugLog((DEB_ERROR, "Cred1 missing DES and RC4 key!\n"));
DsysAssert(FALSE);
return FALSE;
}
}
Key2 = KerbGetKeyFromList(
PwdList2,
Etype
);
if (NULL == Key2)
{
D_DebugLog((DEB_ERROR, "Cred2 missing %x key!\n", Etype));
DsysAssert(FALSE);
return FALSE;
}
return (RtlEqualMemory(
Key1->keyvalue.value,
Key2->keyvalue.value,
Key1->keyvalue.length
));
}
//+-------------------------------------------------------------------------
//
// Function: KerbLogCredmanError
//
// Synopsis: Create an event log entry to help the user fixup their
// credman credential.
//
// Arguments:
//
// Requires:
//
// Returns:
// NULL if the user name is not a marshalled cert, a pointer
// to the
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbLogCredmanError(
IN PKERB_CREDMAN_CRED Cred,
IN NTSTATUS Status
)
{
BOOLEAN CardError = FALSE;
BOOLEAN Pkinit = (Cred->SuppliedCredentials->PublicKeyCreds != NULL);
switch ( Status )
{
case STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED:
case STATUS_SMARTCARD_SUBSYSTEM_FAILURE:
case STATUS_SMARTCARD_SILENT_CONTEXT:
CardError = TRUE;
case STATUS_NO_SUCH_USER:
case STATUS_SMARTCARD_WRONG_PIN:
case STATUS_WRONG_PASSWORD:
break;
default:
return;
}
//
// If this is a *Session, e.g. RAS connection, and we have a card error,
// ask the user to reconnect.
//
if ((( Cred->CredentialFlags & RAS_CREDENTIAL ) != 0) &&
( CardError ))
{
KerbReportRasCardError(Status);
return;
}
KerbReportCredmanError(
&Cred->SuppliedCredentials->UserName,
&Cred->SuppliedCredentials->DomainName,
Pkinit,
Status
);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCheckUserNameForCert
//
// Synopsis: Looks at the passed in user name and determines if that
// user name is a marshalled cert. If it is the function
// opens the user cert store and then attempts to find the
// cert in the store.
//
// Arguments:
//
// Requires:
//
// Returns:
// NULL if the user name is not a marshalled cert, a pointer
// to the
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCheckUserNameForCert(
IN PLUID ClientLogonId,
IN BOOLEAN fImpersonateClient,
IN UNICODE_STRING *pUserName,
OUT PCERT_CONTEXT *ppCertContext
)
{
CRED_MARSHAL_TYPE MarshalType;
PCERT_CREDENTIAL_INFO pCertCredInfo = NULL;
HCERTSTORE hCertStore = NULL;
CRYPT_HASH_BLOB HashBlob;
LPWSTR rgwszUserName;
WCHAR FastUserName[(UNLEN + 1) * sizeof(WCHAR)];
LPWSTR SlowUserName = NULL;
BOOLEAN fImpersonating = FALSE;
HANDLE ClientTokenHandle = NULL;
NTSTATUS Status = STATUS_SUCCESS;
*ppCertContext = NULL;
// Switch to stackalloc routine when available.
if( pUserName->Length+sizeof(WCHAR) <= sizeof(FastUserName) )
{
rgwszUserName = FastUserName;
}
else
{
SafeAllocaAllocate(SlowUserName, pUserName->Length+sizeof(WCHAR));
if( SlowUserName == NULL )
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
rgwszUserName = SlowUserName;
}
RtlCopyMemory(
rgwszUserName,
pUserName->Buffer,
pUserName->Length);
rgwszUserName[pUserName->Length / sizeof(WCHAR)] = L'\0';
//
// unmarshall the cert cred info from the user name field
// of the cred man cred
//
if (!CredUnmarshalCredentialW(
rgwszUserName,
&MarshalType,
(void**)&pCertCredInfo
))
{
goto Cleanup;
}
if (CertCredential != MarshalType)
{
goto Cleanup;
}
// first need to impersonate the user so that we can call the
// credential manager as that user
// TODO: check if this fails.
// don't do this until new ImpersonateLuid() is available.
//
if (NULL == ClientLogonId)
{
if (fImpersonateClient)
{
Status = LsaFunctions->ImpersonateClient();
if (!NT_SUCCESS (Status))
{
goto Cleanup;
}
}
else
{
goto Cleanup;
}
}
else
{
Status = LsaFunctions->OpenTokenByLogonId(
ClientLogonId,
&ClientTokenHandle
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Unable to get the client token handle.\n"));
goto Cleanup;
}
if(!SetThreadToken(NULL, ClientTokenHandle))
{
D_DebugLog((DEB_ERROR,"Unable to impersonate the client token handle.\n"));
Status = STATUS_CANNOT_IMPERSONATE;
goto Cleanup;
}
}
fImpersonating = TRUE;
// open a cert store if necessary
if (NULL == hCertStore)
{
hCertStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM_W,
0,
0,
CERT_SYSTEM_STORE_CURRENT_USER,
L"MY");
if (NULL == hCertStore)
{
Status = SEC_E_NO_CREDENTIALS;
D_DebugLog((DEB_ERROR,"Failed to open the user cert store even though a cert cred was found.\n"));
goto Cleanup;
}
}
// find the cert in the store which meets this hash
HashBlob.cbData = sizeof(pCertCredInfo->rgbHashOfCert);
HashBlob.pbData = pCertCredInfo->rgbHashOfCert;
*ppCertContext = (PCERT_CONTEXT)CertFindCertificateInStore(
hCertStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_HASH,
&HashBlob,
NULL);
if (NULL == *ppCertContext)
{
Status = SEC_E_NO_CREDENTIALS;
D_DebugLog((DEB_ERROR,"Failed to find cert in store even though a cert cred was found.\n"));
goto Cleanup;
}
Cleanup:
if (NULL != hCertStore)
{
CertCloseStore(hCertStore, 0);
}
if (fImpersonating)
{
RevertToSelf();
}
if (NULL != pCertCredInfo)
{
CredFree (pCertCredInfo);
}
if(ClientTokenHandle != NULL)
{
CloseHandle( ClientTokenHandle );
}
SafeAllocaFree(SlowUserName);
return Status;
}
// check username for domain/ or @ format
NTSTATUS
CredpParseUserName(
IN OUT LPWSTR ParseName,
OUT PUNICODE_STRING pUserName,
OUT PUNICODE_STRING pDomainName
)
/*++
Routine Description:
This routine separates a passed in user name into domain and username. A user name must have one
of the following two syntaxes:
<DomainName>\<UserName>
<UserName>@<DnsDomainName>
The name is considered to have the first syntax if the string contains an \.
A string containing a @ is ambiguous since <UserName> may contain an @.
For the second syntax, the last @ in the string is used since <UserName> may
contain an @ but <DnsDomainName> cannot.
NOTE - The function does not allocate the UNICODE_STRING buffers
so these should not be freed (RtlInitUnicodeString is used)
Arguments:
ParseName - Name of user to validate - will be modified
pUserName - Returned pointing to canonical name inside of ParseName
pDomainName - Returned pointing to domain name inside of ParseName
Return Values:
The following status codes may be returned:
STATUS_INVALID_ACCOUNT_NAME - The user name is not valid.
--*/
{
NTSTATUS Status;
LPWSTR SlashPointer;
LPWSTR AtPointer;
LPWSTR pTmpUserName = NULL;
LPWSTR pTmpDomainName = NULL;
//
// NULL is invalid
//
if ( ParseName == NULL ) {
Status = STATUS_INVALID_ACCOUNT_NAME;
goto Cleanup;
}
//
// Classify the input account name.
//
// The name is considered to be <DomainName>\<UserName> if the string
// contains an \.
//
SlashPointer = wcsrchr( ParseName, L'\\' );
if ( SlashPointer != NULL )
{
//
// point the output strings
//
pTmpDomainName = ParseName;
//
// Skip the backslash
//
*SlashPointer = L'\0';
SlashPointer ++;
pTmpUserName = SlashPointer;
//
// Otherwise the name must be a UPN
//
}
else
{
//
// A UPN has the syntax <AccountName>@<DnsDomainName>.
// If there are multiple @ signs,
// use the last one since an AccountName can have an @ in it.
//
//
AtPointer = wcsrchr( ParseName, L'@' );
if ( AtPointer == NULL )
{
// must be just <username>
pTmpUserName = ParseName;
}
else
{
pTmpUserName = ParseName;
*AtPointer = L'\0';
AtPointer ++;
pTmpDomainName = AtPointer;
}
}
RtlInitUnicodeString( pUserName, pTmpUserName );
RtlInitUnicodeString( pDomainName, pTmpDomainName );
Status = STATUS_SUCCESS;
//
// Cleanup
//
Cleanup:
return Status;
}
NTSTATUS
CredpExtractMarshalledTargetInfo(
IN PUNICODE_STRING TargetServerName,
OUT CREDENTIAL_TARGET_INFORMATIONW **pTargetInfo
)
{
NTSTATUS Status;
//
// LSA will set Length to include only the non-marshalled portion,
// with MaximumLength trailing data to include marshalled portion.
//
if( (TargetServerName == NULL) ||
(TargetServerName->Buffer == NULL) ||
(TargetServerName->Length >= TargetServerName->MaximumLength) ||
((TargetServerName->MaximumLength - TargetServerName->Length) < CRED_MARSHALED_TI_SIZE_SIZE )
)
{
return STATUS_SUCCESS;
}
//
// Unmarshal the target info
//
Status = CredUnmarshalTargetInfo (
TargetServerName->Buffer,
TargetServerName->MaximumLength,
pTargetInfo,
NULL );
if( !NT_SUCCESS(Status) )
{
if( Status == STATUS_INVALID_PARAMETER )
{
Status = STATUS_SUCCESS;
}
}
return Status ;
}
//+-------------------------------------------------------------------------
//
// Function: KerbCheckForPKINITEnhKeyUsage
//
// Synopsis: Checks if the passed in cert context contains the
// PKINIT enhanced key usage.
//
// Arguments: pCertContext - cert context to check for enh key usage
//
// Requires:
//
// Returns: TRUE is success, FALSE is failure
//
// Notes:
//
//
//--------------------------------------------------------------------------
BOOL
KerbCheckForPKINITEnhKeyUsage(
IN PCERT_CONTEXT pCertContext
)
{
LPSTR pszClientAuthUsage = KERB_PKINIT_CLIENT_CERT_TYPE;
PCERT_ENHKEY_USAGE pEnhKeyUsage = NULL;
ULONG cbEnhKeyUsage = 0;
ULONG i;
BOOLEAN fRet = FALSE;
if ( pCertContext == NULL )
{
return FALSE;
}
if (!CertGetEnhancedKeyUsage(
pCertContext,
CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG,
NULL,
&cbEnhKeyUsage))
{
goto Cleanup;
}
//
// Allocate space for the key usage structure
//
SafeAllocaAllocate(pEnhKeyUsage, cbEnhKeyUsage);
if (NULL == pEnhKeyUsage)
{
goto Cleanup;
}
if (!CertGetEnhancedKeyUsage(
pCertContext,
CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG,
pEnhKeyUsage,
&cbEnhKeyUsage))
{
goto Cleanup;
}
//
// Enumerate through the enh key usages looking for the PKINIT one
//
for (i=0;i<pEnhKeyUsage->cUsageIdentifier;i++)
{
if (0 == strcmp(pszClientAuthUsage, pEnhKeyUsage->rgpszUsageIdentifier[i]))
{
fRet = TRUE;
goto Cleanup;
}
}
Cleanup:
SafeAllocaFree(pEnhKeyUsage);
return fRet;
}
//+-------------------------------------------------------------------------
//
// Function: KerbAddCertCredToPrimaryCredential
//
// Synopsis: Adds cert context and Pin info to the kerb credential
// structure.
//
// Arguments: pCertContext - logon session
// pCertCredInfo - cert cred manager info
// pKerbCred - credential to be updated
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbAddCertCredToPrimaryCredential(
IN PKERB_LOGON_SESSION pLogonSession,
IN PUNICODE_STRING pTargetName,
IN PCERT_CONTEXT pCertContext,
IN PUNICODE_STRING pPin,
IN ULONG CredFlags,
IN OUT PKERB_PRIMARY_CREDENTIAL *ppCredMgrCred
)
{
UNICODE_STRING UserName = {0};
UNICODE_STRING DomainName = {0}; // get the domain from the UPN in the cert
PKERB_PRIMARY_CREDENTIAL pOldCred;
PKERB_PRIMARY_CREDENTIAL pNewCred = NULL;
NTSTATUS Status = STATUS_SUCCESS;
//
// Get the client name from the cert.
// Place it in the return location
//
Status = KerbGetPrincipalNameFromCertificate(pCertContext, &UserName);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// Initialize the primary credentials structure
//
Status = KerbInitPrimaryCreds(
pLogonSession,
&UserName,
&DomainName,
pTargetName,
pPin,
TRUE,
pCertContext,
&pNewCred
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
pNewCred->PublicKeyCreds->InitializationInfo |= CredFlags;
Status = KerbInitializePkCreds(
pNewCred->PublicKeyCreds
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
pOldCred = *ppCredMgrCred;
*ppCredMgrCred = pNewCred;
pNewCred = NULL;
KerbFreePrimaryCredentials(pOldCred, TRUE);
Cleanup:
KerbFreeString(&UserName);
KerbFreePrimaryCredentials(pNewCred, TRUE);
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbAddPasswordCredToPrimaryCredential
//
// Synopsis: Adds cert context and Pin info to the kerb credential
// structure.
//
// Arguments: pCertContext - logon session
// pCertCredInfo - cert cred manager info
// pKerbCred - credential to be updated
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbAddPasswordCredToPrimaryCredential(
IN PKERB_LOGON_SESSION pLogonSession,
IN PUNICODE_STRING pUserName,
IN PUNICODE_STRING pTargetDomainName,
IN PUNICODE_STRING pPassword,
IN OUT PKERB_PRIMARY_CREDENTIAL *ppCredMgrCred
)
{
PKERB_PRIMARY_CREDENTIAL pOldCred;
PKERB_PRIMARY_CREDENTIAL pNewCred = NULL;
UNICODE_STRING RevealedPassword;
NTSTATUS Status = STATUS_SUCCESS;
RtlZeroMemory(&RevealedPassword, sizeof(RevealedPassword));
Status = KerbDuplicatePassword(
&RevealedPassword,
pPassword
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
KerbRevealPassword( &RevealedPassword );
//
// Initialize the primary credentials structure
//
Status = KerbInitPrimaryCreds(
pLogonSession,
pUserName,
pTargetDomainName,
NULL,
&RevealedPassword,
FALSE,
NULL,
&pNewCred
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
pOldCred = *ppCredMgrCred;
*ppCredMgrCred = pNewCred;
pNewCred = NULL;
KerbFreePrimaryCredentials(pOldCred, TRUE);
Cleanup:
if ((0 != RevealedPassword.Length) && (NULL != RevealedPassword.Buffer))
{
RtlSecureZeroMemory(RevealedPassword.Buffer, RevealedPassword.Length);
KerbFreeString(&RevealedPassword);
}
//
// Don't leak password length
//
RevealedPassword.Length = RevealedPassword.MaximumLength = 0;
KerbFreePrimaryCredentials(pNewCred, TRUE);
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbCreateCredmanCred
//
// Synopsis: Goes to the credential manager to try and find
// credentials for the specific target
//
// Arguments:
// CredToAdd - PrimaryCredential to add to credman cred
// ppNewCred - IN OUT built cred, free w/ KerbFreeCredmanCred
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCreateCredmanCred(
IN PKERB_PRIMARY_CREDENTIAL CredToAdd,
IN ULONG AdditionalCredFlags,
IN OUT PKERB_CREDMAN_CRED * ppNewCred
)
{
NTSTATUS Status = STATUS_SUCCESS;
*ppNewCred = NULL;
*ppNewCred = (PKERB_CREDMAN_CRED) KerbAllocate(sizeof(KERB_CREDMAN_CRED));
if (NULL == *ppNewCred)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = KerbDuplicateStringEx(
&(*ppNewCred)->CredmanUserName,
&CredToAdd->UserName,
FALSE
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = KerbDuplicateStringEx(
&(*ppNewCred)->CredmanDomainName,
&CredToAdd->DomainName,
FALSE
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
(*ppNewCred)->SuppliedCredentials = CredToAdd;
(*ppNewCred)->CredentialFlags |= AdditionalCredFlags;
Cleanup:
if (!NT_SUCCESS(Status))
{
KerbFreeCredmanCred(*ppNewCred);
*ppNewCred = NULL;
}
return (Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbAddCredmanCredToLogonSession
//
// Synopsis: Goes to the credential manager to try and find
// credentials for the specific target
//
// Arguments: pLogonSession - logon session
// CredToMatch - PrimaryCredential to look for in logon session
//
// Requires: Hold logon session lock...
//
// Returns:
//
// Notes: CredToMatch freed in this function...
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbAddCredmanCredToLogonSession(
IN PKERB_LOGON_SESSION pLogonSession,
IN PKERB_PRIMARY_CREDENTIAL CredToMatch,
IN ULONG AdditionalCredFlags,
IN OUT PKERB_CREDMAN_CRED *NewCred
)
{
NTSTATUS Status = STATUS_SUCCESS;
PKERB_CREDMAN_CRED CredmanCred = NULL;
PLIST_ENTRY ListEntry;
BOOLEAN PublicKeyCred = FALSE;
BOOLEAN Found = FALSE;
*NewCred = NULL;
//
// First, make a determination if the cred's already listed
// Replace w/ new one if password has changed.
//
KerbLockList(&pLogonSession->CredmanCredentials);
//
// Go through the list of logon sessions looking for the correct
// credentials, if they exist...
//
for (ListEntry = pLogonSession->CredmanCredentials.List.Flink ;
(ListEntry != &pLogonSession->CredmanCredentials.List && !Found);
ListEntry = ListEntry->Flink )
{
CredmanCred = CONTAINING_RECORD(ListEntry, KERB_CREDMAN_CRED, ListEntry.Next);
// We only match on UserName / DomainName for credman creds
if(!RtlEqualUnicodeString(
&CredToMatch->UserName,
&CredmanCred->CredmanUserName,
TRUE
))
{
continue;
}
if(!RtlEqualUnicodeString(
&CredToMatch->DomainName,
&CredmanCred->CredmanDomainName,
TRUE
))
{
continue;
}
//
// Differentiate between pkiint & password based structures
//
if ((CredmanCred->SuppliedCredentials->PublicKeyCreds != NULL) &&
(CredToMatch->PublicKeyCreds != NULL))
{
if (!KerbComparePublicKeyCreds(
CredToMatch->PublicKeyCreds,
CredmanCred->SuppliedCredentials->PublicKeyCreds
))
{
continue;
}
PublicKeyCred = TRUE;
}
Found = TRUE;
*NewCred = CredmanCred;
} // FOR
if (Found)
{
KerbReferenceCredmanCred(
*NewCred,
pLogonSession,
FALSE
);
D_DebugLog((DEB_TRACE_CRED, "Found match %p\n", (*NewCred)));
//
// Found one. Now we've got to compare the pwd information, and
// change it, if needed...
//
if (!PublicKeyCred)
{
//
// Compare the password list, as the pwd may have changed...
// Note: This has the by-product of tossing old tickets, but
// that's desirable if the pwd's changed, so user knows the creds
// are bogus.
//
if (!KerbComparePasswords(
(*NewCred)->SuppliedCredentials->Passwords,
CredToMatch->Passwords
))
{
D_DebugLog((DEB_ERROR, "Changing credman cred password\n"));
PKERB_PRIMARY_CREDENTIAL OldPwds = (*NewCred)->SuppliedCredentials;
(*NewCred)->SuppliedCredentials = CredToMatch;
KerbFreePrimaryCredentials(OldPwds, TRUE);
(*NewCred)->CredentialFlags &= ~KERB_CRED_TGT_AVAIL;
}
else
{
KerbFreePrimaryCredentials(CredToMatch, TRUE);
}
}
else
{
//
// Free up the cred to match, since we already have a copy stored w/ our credential
//
KerbFreePrimaryCredentials(CredToMatch, TRUE);
}
}
else // new cred, so prepare CredmanCred to add to list...
{
Status = KerbCreateCredmanCred(
CredToMatch,
AdditionalCredFlags,
NewCred
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
KerbInsertListEntryTail(
&((*NewCred)->ListEntry),
&pLogonSession->CredmanCredentials
);
// add a ref for caller of this function.
KerbReferenceCredmanCred(
(*NewCred),
pLogonSession,
FALSE
);
}
//
// We need an initial TGT for this cred
//
if (((*NewCred)->CredentialFlags & KERB_CRED_TGT_AVAIL) == 0)
{
//
// Get an initial TGT for this cred.
//
Status = KerbGetTicketGrantingTicket(
pLogonSession,
NULL,
(*NewCred),
NULL,
NULL,
NULL
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "Failed to get TGT for credman cred - %x\n",Status));
if( Status == STATUS_NO_LOGON_SERVERS )
{
//
// negotiate treats NO_LOGON_SERVERS as a downgrade.
// Nego allows downgrade for explicit creds, but not default creds.
// Credman is basically explicit creds. So over-ride the error code.
//
Status = SEC_E_TARGET_UNKNOWN;
}
goto Cleanup;
}
(*NewCred)->CredentialFlags |= KERB_CRED_TGT_AVAIL;
}
Cleanup:
KerbUnlockList(&pLogonSession->CredmanCredentials);
return (Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbConvertCertCredential
//
// Synopsis: Converts a cert cred to a primary cred
//
// Arguments: pLogonSession - logon session
// pTargetName - service name
// pTargetDomainName - domain name
// pTargetForestName - forest name
// pKerbCred - credential to be allocated
//
// Requires: You've got to be impersonating when making this call.
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbConvertCertCredential(
IN PKERB_LOGON_SESSION LogonSession,
IN LPCWSTR MarshalledCredential,
IN PUNICODE_STRING TargetName,
IN OUT PKERB_PRIMARY_CREDENTIAL * PrimaryCredential
)
{
NTSTATUS Status;
CRED_MARSHAL_TYPE MarshalType;
PCERT_CREDENTIAL_INFO pCertCredInfo = NULL;
PKERB_PRIMARY_CREDENTIAL LocalCredential = NULL;
HCERTSTORE hCertStore = NULL;
CRYPT_HASH_BLOB HashBlob;
PCERT_CONTEXT pCertContext = NULL;
UNICODE_STRING Pin = {0};
*PrimaryCredential = NULL;
//
// unmarshal the cert cred info from the user name field
// of the cred man cred
//
if (!CredUnmarshalCredentialW(
MarshalledCredential,
&MarshalType,
(void**)&pCertCredInfo
))
{
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
if (CertCredential != MarshalType)
{
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
// open a cert store if necessary
hCertStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM_W,
0,
0,
CERT_SYSTEM_STORE_CURRENT_USER,
L"MY"
);
if (NULL == hCertStore)
{
D_DebugLog((DEB_ERROR,"Failed to open the user cert store even though a cert cred was found.\n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
// find the cert in the store which meets this hash
HashBlob.cbData = sizeof(pCertCredInfo->rgbHashOfCert);
HashBlob.pbData = pCertCredInfo->rgbHashOfCert;
pCertContext = (PCERT_CONTEXT)CertFindCertificateInStore(
hCertStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_HASH,
&HashBlob,
pCertContext);
if ( KerbCheckForPKINITEnhKeyUsage( pCertContext ) )
{
//
// add the cert credential to the Kerb credential
//
// Cred man will no longer give us a pin, only the CSP
// knows that information...
//
Status = KerbAddCertCredToPrimaryCredential(
LogonSession,
TargetName,
pCertContext,
&Pin, // essentially, a NULL string
CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS,
&LocalCredential
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_WARN,"Failed to add the cert cred to the credential.\n"));
goto Cleanup;
}
}
else
{
//
// Can't find the certificate
//
DebugLog((DEB_ERROR, "Can't find cert from credman\n"));
//
// TBD:
// Log Event
/*
KerbReportCredmanError(ID_MISSING_CERT);
*/
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
*PrimaryCredential = LocalCredential;
LocalCredential = NULL;
Cleanup:
if (NULL != pCertCredInfo)
{
CredFree(pCertCredInfo);
}
if (NULL != pCertContext)
{
CertFreeCertificateContext(pCertContext);
}
if ( LocalCredential )
{
KerbFree( LocalCredential );
}
if (NULL != hCertStore)
{
CertCloseStore(hCertStore, 0);
}
return (Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCheckCredMgrForGivenTarget
//
// Synopsis: Goes to the credential manager to try and find
// credentials for the specific target
//
// Arguments: pLogonSession - logon session
// pTargetName - service name
// pTargetDomainName - domain name
// pTargetForestName - forest name
// pKerbCred - credential to be allocated
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCheckCredMgrForGivenTarget(
IN PKERB_LOGON_SESSION LogonSession,
IN PKERB_CREDENTIAL Credential,
IN PUNICODE_STRING SuppliedTargetName,
IN PKERB_INTERNAL_NAME pTargetName,
IN ULONG TargetInfoFlags,
IN PUNICODE_STRING pTargetDomainName,
IN PUNICODE_STRING pTargetForestName,
IN OUT PKERB_CREDMAN_CRED *CredmanCred,
IN OUT PBYTE *pbMarshalledTargetInfo,
IN OUT ULONG *cbMarshalledTargetInfo
)
{
CREDENTIAL_TARGET_INFORMATIONW CredTargetInfo;
ULONG cCreds = 0;
PCREDENTIALW *rgpCreds = NULL;
PCREDENTIALW CertCred = NULL;
PCREDENTIALW PasswordCred = NULL;
PENCRYPTED_CREDENTIALW *rgpEncryptedCreds = NULL;
LPWSTR pwszTargetName = NULL;
LPWSTR pwszDomainName = NULL;
LPWSTR pwszForestName = NULL;
BOOLEAN Impersonating = FALSE;
ULONG i;
UNICODE_STRING CredManUserName = {0};
UNICODE_STRING CredManDomainName = {0};
UNICODE_STRING CredManTargetName = {0};
UNICODE_STRING Password = {0};
UNICODE_STRING RevealedPassword;
HANDLE ClientTokenHandle = NULL;
CREDENTIAL_TARGET_INFORMATIONW *pTargetInfo = NULL;
NTSTATUS Status = STATUS_SUCCESS;
PKERB_PRIMARY_CREDENTIAL pCredMgrCred = NULL;
HANDLE ImpersonationToken = NULL;
USHORT ClearBlobSize = 0;
ULONG AdditionalCredFlags = 0;
SECPKG_CALL_INFO CallInfo = {0};
RtlZeroMemory(&CredTargetInfo, sizeof(CredTargetInfo));
RtlZeroMemory(&RevealedPassword, sizeof(RevealedPassword));
*CredmanCred = NULL;
Status = CredpExtractMarshalledTargetInfo(
SuppliedTargetName,
&pTargetInfo
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
if (!LsaFunctions->GetCallInfo(&CallInfo))
{
Status = STATUS_INTERNAL_ERROR;
goto Cleanup;
}
//
// Allocate space for the names
//
if (NULL != pTargetName)
{
//
// want to use the second part of the SPN
//
if (pTargetName->NameCount > 1)
{
SafeAllocaAllocate(pwszTargetName, pTargetName->Names[1].Length + sizeof(WCHAR));
if (NULL == pwszTargetName)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory(
(PUCHAR)pwszTargetName,
pTargetName->Names[1].Buffer,
pTargetName->Names[1].Length);
pwszTargetName[pTargetName->Names[1].Length / sizeof(WCHAR)] = L'\0';
CredTargetInfo.DnsServerName = pwszTargetName;
RtlInitUnicodeString(&CredManTargetName, pwszTargetName);
}
}
if ((NULL != pTargetDomainName) && (0 != pTargetDomainName->Length) &&
(NULL != pTargetDomainName->Buffer))
{
SafeAllocaAllocate(pwszDomainName, pTargetDomainName->Length + sizeof(WCHAR));
if (NULL == pwszDomainName)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory(
(PUCHAR)pwszDomainName,
pTargetDomainName->Buffer,
pTargetDomainName->Length);
pwszDomainName[pTargetDomainName->Length / sizeof(WCHAR)] = L'\0';
CredTargetInfo.DnsDomainName = pwszDomainName;
}
if ((NULL != pTargetForestName) && (0 != pTargetForestName->Length) &&
(NULL != pTargetForestName->Buffer))
{
SafeAllocaAllocate(pwszForestName, pTargetForestName->Length + sizeof(WCHAR));
if (NULL == pwszForestName)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory(
(PUCHAR)pwszForestName,
pTargetForestName->Buffer,
pTargetForestName->Length);
pwszForestName[pTargetForestName->Length / sizeof(WCHAR)] = L'\0';
CredTargetInfo.DnsTreeName = pwszForestName;
}
CredTargetInfo.PackageName = KERBEROS_PACKAGE_NAME;
//
// if marshalled targetinfo supplied, use it instead.
//
if ( pTargetInfo )
{
CredTargetInfo.TargetName = pTargetInfo->TargetName;
CredTargetInfo.NetbiosServerName = pTargetInfo->NetbiosServerName;
CredTargetInfo.DnsServerName = pTargetInfo->DnsServerName;
CredTargetInfo.NetbiosDomainName = pTargetInfo->NetbiosDomainName;
CredTargetInfo.DnsDomainName = pTargetInfo->DnsDomainName;
CredTargetInfo.DnsTreeName = pTargetInfo->DnsTreeName;
CredTargetInfo.Flags |= pTargetInfo->Flags;
}
else
{
//
// copy the names in to the memory and set the names
// in the PCREDENTIAL_TARGET_INFORMATIONW struct
//
if (pwszTargetName)
{
CredTargetInfo.Flags |= CRED_TI_SERVER_FORMAT_UNKNOWN;
}
if (pwszDomainName)
{
CredTargetInfo.Flags |= CRED_TI_DOMAIN_FORMAT_UNKNOWN;
}
CredTargetInfo.Flags |= TargetInfoFlags;
}
// need to specify a flag to indicate that we don't know what we are
// doing and both types of names should be checked.
Status = LsaFunctions->CrediReadDomainCredentials(
&LogonSession->LogonId,
CREDP_FLAGS_IN_PROCESS, // Allow password to be returned
&CredTargetInfo,
0,
&cCreds,
&rgpEncryptedCreds );
rgpCreds = (PCREDENTIALW *) rgpEncryptedCreds;
//
// return a copy of the credential target info for kernel callers (MUP/DFS/RDR).
//
if (NT_SUCCESS(Status) || (CallInfo.Attributes & SECPKG_CALL_KERNEL_MODE))
{
CredMarshalTargetInfo(
&CredTargetInfo,
(PUSHORT*)pbMarshalledTargetInfo,
cbMarshalledTargetInfo
);
}
if (!NT_SUCCESS(Status))
{
// quiet these.
if ((Status == STATUS_NOT_FOUND) ||(Status == STATUS_NO_SUCH_LOGON_SESSION) )
{
D_DebugLog((DEB_TRACE, "No credentials from the cred mgr!\n", Status));
}
else
{
DebugLog((DEB_WARN, "Failed to read credentials from the cred mgr 0x%x.\n", Status));
}
// indicate success so we proceed with default creds
Status = STATUS_SUCCESS;
goto Cleanup;
}
//
// Look for cred types we understand.
//
for (i = 0; i < cCreds; i++)
{
if ((rgpCreds[i])->Type == CRED_TYPE_DOMAIN_CERTIFICATE)
{
CertCred = rgpCreds[i];
ClearBlobSize = (USHORT) (rgpEncryptedCreds[i])->ClearCredentialBlobSize;
}
else if ((rgpCreds[i])->Type == CRED_TYPE_DOMAIN_PASSWORD)
{
PasswordCred = rgpCreds[i];
ClearBlobSize = (USHORT) (rgpEncryptedCreds[i])->ClearCredentialBlobSize;
}
}
if (!(CertCred || PasswordCred))
{
DebugLog((DEB_ERROR, "Found no credman creds we understand\n"));
// indicate success so we proceed with default creds
Status = STATUS_SUCCESS;
goto Cleanup;
}
//
// now evaluate the creds which were returned to determine
// which one we should use.
//
// First choice is a certificate which may be
// used for PKINIT.
//
if ( CertCred )
{
// check for the prompt now flag
if (CertCred->Flags & CRED_FLAGS_PROMPT_NOW)
{
DebugLog((DEB_ERROR, "Asking for prompt on credman cred \n"));
Status = STATUS_SMARTCARD_SILENT_CONTEXT;
goto Cleanup;
}
if (!lstrcmpW(CRED_SESSION_WILDCARD_NAME_W, CertCred->TargetName))
{
AdditionalCredFlags |= RAS_CREDENTIAL;
}
if( !Impersonating )
{
//
// Save off the old token, if it exists.
//
Status = NtOpenThreadToken(
NtCurrentThread(),
TOKEN_QUERY | TOKEN_IMPERSONATE,
TRUE,
&ImpersonationToken
);
if (!NT_SUCCESS( Status ) && Status != STATUS_NO_TOKEN )
{
DebugLog((DEB_ERROR, "NtOpenThreadToken failed %x\n", Status));
goto Cleanup;
}
Status = LsaFunctions->OpenTokenByLogonId(
&LogonSession->LogonId,
&ClientTokenHandle
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Unable to get the client token handle.\n"));
goto Cleanup;
}
if(!SetThreadToken(NULL, ClientTokenHandle))
{
D_DebugLog((DEB_ERROR,"Unable to impersonate the client token handle.\n"));
Status = STATUS_CANNOT_IMPERSONATE;
goto Cleanup;
}
Impersonating = TRUE;
}
Status = KerbConvertCertCredential(
LogonSession,
CertCred->UserName,
&CredManTargetName,
&pCredMgrCred
);
if (!NT_SUCCESS( Status ))
{
DebugLog((DEB_ERROR, "KerbConvertCertCredential failed %x\n", Status));
goto Cleanup;
}
}
else if ( PasswordCred )
{
// check for the prompt now flag
if ( PasswordCred->Flags & CRED_FLAGS_PROMPT_NOW)
{
DebugLog((DEB_ERROR, "Asking for prompt on credman cred \n"));
Status = SEC_E_LOGON_DENIED;
goto Cleanup;
}
if (!lstrcmpW(CRED_SESSION_WILDCARD_NAME_W, PasswordCred->TargetName))
{
AdditionalCredFlags |= RAS_CREDENTIAL;
}
//
// get the user name and domain name from the credential manager info
//
// NOTE - CredpParseUserName does not allocate the UNICODE_STRING
// buffers so these should not be freed (RtlInitUnicodeString is used)
//
Status = CredpParseUserName(
PasswordCred->UserName,
&CredManUserName,
&CredManDomainName);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_WARN,"Failed to parse the add the cert cred to the credential.\n"));
goto Cleanup;
}
Password.Buffer = (LPWSTR)(PasswordCred->CredentialBlob);
Password.MaximumLength = (USHORT)PasswordCred->CredentialBlobSize;
Password.Length = ClearBlobSize;
// add the cert credential to the Kerb credential
Status = KerbAddPasswordCredToPrimaryCredential(
LogonSession,
&CredManUserName,
&CredManDomainName,
&Password,
&pCredMgrCred
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_WARN,"Failed to add the cred mgr password to the credential.\n"));
goto Cleanup;
}
}
else
{
//
// NO creds found we can use.
//
DebugLog((DEB_ERROR, "No valid creds in credman\n"));
Status = STATUS_NOT_FOUND;
goto Cleanup;
}
//
// We've built the credman cred, now go ahead and add it to the logon.
//
Status = KerbAddCredmanCredToLogonSession(
LogonSession,
pCredMgrCred, // note: freed by this fn
AdditionalCredFlags,
CredmanCred
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "Failed to add credman cred to logon session\n"));
goto Cleanup;
}
Cleanup:
if (Impersonating)
{
if (ImpersonationToken != NULL)
{
SetThreadToken(NULL, ImpersonationToken);
}
else
{
RevertToSelf();
}
}
if ( ImpersonationToken )
{
CloseHandle(ImpersonationToken);
}
if ( ClientTokenHandle )
{
CloseHandle( ClientTokenHandle );
}
if( pTargetInfo != NULL )
{
LocalFree( pTargetInfo );
}
SafeAllocaFree(pwszTargetName);
SafeAllocaFree(pwszDomainName);
SafeAllocaFree(pwszForestName);
if (NULL != rgpCreds)
{
//
// Free the returned credentials
//
LsaFunctions->CrediFreeCredentials(
cCreds,
rgpEncryptedCreds );
}
return Status;
}
NTSTATUS
CopyCredManCredentials(
IN PLUID LogonId,
CREDENTIAL_TARGET_INFORMATIONW* pTargetInfo,
IN OUT PUNICODE_STRING pUserName,
IN OUT PUNICODE_STRING pDomainName,
IN OUT PUNICODE_STRING pPassword
)
/*++
Routine Description:
Look for a keyring credential entry for the specified domain, and copy to Context handle if found
Arguments:
LogonId -- LogonId of the calling process.
pTargetInfo -- Information on target to search for creds.
Context - Points to the ContextHandle of the Context
to be referenced.
Return Value:
STATUS_SUCCESS -- All OK
STATUS_NOT_FOUND - Credential couldn't be found.
All others are real failures and should be returned to the caller.
--*/
{
NTSTATUS Status;
PCREDENTIALW *Credentials = NULL;
PENCRYPTED_CREDENTIALW *EncryptedCredentials = NULL;
ULONG CredentialCount;
ULONG CredIndex;
RtlInitUnicodeString(pUserName, NULL);
RtlInitUnicodeString(pDomainName, NULL);
RtlInitUnicodeString(pPassword, NULL);
Status = LsaFunctions->CrediReadDomainCredentials(
LogonId,
CREDP_FLAGS_IN_PROCESS, // Allow password to be returned
pTargetInfo,
0, // no flags
&CredentialCount,
&EncryptedCredentials );
Credentials = (PCREDENTIALW *) EncryptedCredentials;
if(!NT_SUCCESS(Status))
{
//
// Ideally, only STATUS_NO_SUCH_LOGON_SESSION should be converted to
// STATUS_NOT_FOUND. However, swallowing all failures and asserting
// these specific two works around a bug in CrediReadDomainCredentials
// which returns invalid parameter if the target is a user account name.
// Eventually, CrediReadDomainCredentials should return a more appropriate
// error in this case.
//
return STATUS_NOT_FOUND;
}
//
// Loop through the list of credentials
//
for ( CredIndex=0; CredIndex<CredentialCount; CredIndex++ ) {
UNICODE_STRING UserName;
UNICODE_STRING DomainName;
UNICODE_STRING TempString;
//
// only supports password credentials
//
if ( Credentials[CredIndex]->Type != CRED_TYPE_DOMAIN_PASSWORD ) {
continue;
}
if ( Credentials[CredIndex]->Flags & CRED_FLAGS_PROMPT_NOW ) {
Status = SEC_E_LOGON_DENIED;
goto Cleanup;
}
//
// Sanity check the credential
//
if ( Credentials[CredIndex]->UserName == NULL ) {
Status = STATUS_NOT_FOUND;
goto Cleanup;
}
//
// Convert the UserName to domain name and user name
//
Status = CredpParseUserName(
Credentials[CredIndex]->UserName,
&UserName,
&DomainName
);
if(!NT_SUCCESS(Status))
{
goto Cleanup;
}
if( DomainName.Buffer )
{
Status = KerbDuplicateString(pDomainName, &DomainName);
if ( !NT_SUCCESS( Status ) )
{
goto Cleanup;
}
}
if( UserName.Buffer )
{
Status = KerbDuplicateString(pUserName, &UserName);
if ( !NT_SUCCESS( Status ) )
{
goto Cleanup;
}
}
//
// Free the existing password and add the new one
//
TempString.Buffer = (LPWSTR)Credentials[CredIndex]->CredentialBlob;
TempString.MaximumLength = (USHORT) Credentials[CredIndex]->CredentialBlobSize;
TempString.Length = (USHORT) EncryptedCredentials[CredIndex]->ClearCredentialBlobSize;
// zero length password must be treated as blank or will assume it should use the
// password of the currently logged in user.
if ( TempString.Length == 0 )
{
TempString.Buffer = L"";
}
Status = KerbDuplicatePassword(pPassword, &TempString);
if ( !NT_SUCCESS( Status ) )
{
goto Cleanup;
}
goto Cleanup;
}
Status = STATUS_NOT_FOUND;
Cleanup:
if(!NT_SUCCESS(Status))
{
KerbFreeString( pUserName );
KerbFreeString( pDomainName );
KerbFreeString( pPassword );
pUserName->Buffer = NULL;
pDomainName->Buffer = NULL;
pPassword->Buffer = NULL;
}
//
// Free the returned credentials
//
LsaFunctions->CrediFreeCredentials(
CredentialCount,
EncryptedCredentials );
return Status;
}
NTSTATUS
KerbProcessUserNameCredential(
IN PUNICODE_STRING MarshalledUserName,
OUT PUNICODE_STRING UserName,
OUT PUNICODE_STRING DomainName,
OUT PUNICODE_STRING Password
)
{
WCHAR FastUserName[ UNLEN+1 ];
LPWSTR SlowUserName = NULL;
LPWSTR TempUserName;
CRED_MARSHAL_TYPE CredMarshalType;
PUSERNAME_TARGET_CREDENTIAL_INFO pCredentialUserName = NULL;
CREDENTIAL_TARGET_INFORMATIONW TargetInfo;
ULONG CredTypes;
SECPKG_CLIENT_INFO ClientInfo;
NTSTATUS Status = STATUS_NOT_FOUND;
if( (MarshalledUserName->Length+sizeof(WCHAR)) <= sizeof(FastUserName) )
{
TempUserName = FastUserName;
}
else
{
SafeAllocaAllocate(SlowUserName, MarshalledUserName->Length + sizeof(WCHAR));
if( SlowUserName == NULL )
{
return STATUS_INSUFFICIENT_RESOURCES;
}
TempUserName = SlowUserName;
}
//
// copy the input to a NULL terminated string, then attempt to unmarshal it.
//
RtlCopyMemory( TempUserName,
MarshalledUserName->Buffer,
MarshalledUserName->Length
);
TempUserName[ MarshalledUserName->Length / sizeof(WCHAR) ] = L'\0';
if(!CredUnmarshalCredentialW(
TempUserName,
&CredMarshalType,
(VOID**)&pCredentialUserName
))
{
goto Cleanup;
}
if( (CredMarshalType != UsernameTargetCredential) )
{
goto Cleanup;
}
//
// now query credential manager for a match.
//
Status = LsaFunctions->GetClientInfo(&ClientInfo);
if(!NT_SUCCESS(Status))
{
goto Cleanup;
}
ZeroMemory( &TargetInfo, sizeof(TargetInfo) );
CredTypes = CRED_TYPE_DOMAIN_PASSWORD;
TargetInfo.Flags = CRED_TI_USERNAME_TARGET;
TargetInfo.TargetName = pCredentialUserName->UserName;
TargetInfo.PackageName = KERBEROS_PACKAGE_NAME;
TargetInfo.CredTypeCount = 1;
TargetInfo.CredTypes = &CredTypes;
Status = CopyCredManCredentials(
&ClientInfo.LogonId,
&TargetInfo,
UserName,
DomainName,
Password
);
if(!NT_SUCCESS(Status))
{
goto Cleanup;
}
KerbRevealPassword( Password );
Cleanup:
if( pCredentialUserName != NULL )
{
CredFree( pCredentialUserName );
}
SafeAllocaFree( SlowUserName );
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbMarshallMSVCredential
//
// Synopsis: Takes a SECPKG_SUPPLEMENTAL_CRED and bundles it up as a
// PCREDENTIAL structure.
//
// Arguments: NtlmCred - Supplemental cred from PAC
// MarshalledCred - credential being created.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbMarshallMSVCredential(
IN PSECPKG_SUPPLEMENTAL_CRED NtlmCred,
IN PUNICODE_STRING UserName,
IN PUNICODE_STRING TargetName,
IN OUT PKERB_QUERY_SUPPLEMENTAL_CREDS_RESPONSE * MarshalledCred,
IN OUT PULONG Size
)
{
PKERB_QUERY_SUPPLEMENTAL_CREDS_RESPONSE LocalCred = NULL;
NTSTATUS Status = STATUS_SUCCESS;
PBYTE Where;
ULONG LocalSize;
LocalSize = (sizeof(KERB_QUERY_SUPPLEMENTAL_CREDS_RESPONSE) +
ROUND_UP_COUNT( UserName->MaximumLength, ALIGN_LPDWORD ) +
ROUND_UP_COUNT( TargetName->MaximumLength, ALIGN_LPDWORD ) +
ROUND_UP_COUNT( NtlmCred->CredentialSize, DESX_BLOCKLEN));
LocalCred = (PKERB_QUERY_SUPPLEMENTAL_CREDS_RESPONSE) KerbAllocate(LocalSize);
if (LocalCred == NULL)
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
Where = (PBYTE) LocalCred + sizeof(KERB_QUERY_SUPPLEMENTAL_CREDS_RESPONSE);
RtlCopyMemory(
Where,
UserName->Buffer,
UserName->MaximumLength
);
LocalCred->ReturnedCreds.Cred.UserName = (LPWSTR) Where;
Where += ROUND_UP_COUNT(UserName->MaximumLength, ALIGN_LPDWORD);
RtlCopyMemory(
Where,
TargetName->Buffer,
TargetName->MaximumLength
);
LocalCred->ReturnedCreds.Cred.TargetName = (LPWSTR) Where;
Where += ROUND_UP_COUNT(TargetName->MaximumLength, ALIGN_LPDWORD);
RtlCopyMemory(
Where,
NtlmCred->Credentials,
NtlmCred->CredentialSize
);
LocalCred->ReturnedCreds.ClearCredentialBlobSize = NtlmCred->CredentialSize;
LocalCred->ReturnedCreds.Cred.CredentialBlob = Where;
LocalCred->ReturnedCreds.Cred.CredentialBlobSize = NtlmCred->CredentialSize;
LsaFunctions->LsaProtectMemory(
LocalCred->ReturnedCreds.Cred.CredentialBlob,
(ROUND_UP_COUNT(NtlmCred->CredentialSize, DESX_BLOCKLEN))
);
LocalCred->ReturnedCreds.Cred.Flags = CRED_FLAGS_OWF_CRED_BLOB;
LocalCred->ReturnedCreds.Cred.Type = CRED_TYPE_DOMAIN_PASSWORD;
LocalCred->ReturnedCreds.Cred.Persist = CRED_PERSIST_SESSION;
*MarshalledCred = LocalCred;
LocalCred = NULL;
*Size = LocalSize;
Cleanup:
if (LocalCred)
{
KerbFree(LocalCred);
}
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbRetrieveOWF
//
// Synopsis: Converts a smartcard credential into a credential containing
// the NT_OWF.
//
// Arguments: NtlmCred - Supplemental cred from PAC
// MarshalledCred - credential being created.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbRetrieveOWF(
IN PKERB_LOGON_SESSION LogonSession,
IN PKERB_CREDENTIAL Credential,
IN PKERB_CREDMAN_CRED CredmanCred,
IN PUNICODE_STRING CredTargetName,
IN OUT PKERB_QUERY_SUPPLEMENTAL_CREDS_RESPONSE * Response,
IN OUT PULONG ResponseSize
)
{
NTSTATUS Status;
ULONG i;
PSECPKG_SUPPLEMENTAL_CRED_ARRAY PacCreds = NULL;
PSECPKG_SUPPLEMENTAL_CRED NtlmCred = NULL;
PKERB_TICKET_CACHE_ENTRY NewTicket = NULL;
PKERB_TICKET_CACHE_ENTRY Tgt = NULL;
UNICODE_STRING TempName = {0};
UNICODE_STRING UserName = {0};
UNICODE_STRING Package = {0};
BOOLEAN CrossRealm = FALSE;
LPWSTR tmp = NULL;
KERB_TGT_REPLY TgtReply = {0};
PKERB_INTERNAL_NAME TargetName = NULL;
PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo = NULL;
ULONG Size;
PKERB_QUERY_SUPPLEMENTAL_CREDS_RESPONSE LocalResponse = NULL;
*Response = NULL;
*ResponseSize = 0;
//
// First get a TGT for U2U
//
KerbReadLockLogonSessions( LogonSession );
Status = KerbGetTgtForService(
LogonSession,
Credential,
CredmanCred,
NULL,
&TempName, // no target realm
KERB_TICKET_CACHE_PRIMARY_TGT,
&Tgt,
&CrossRealm
);
KerbUnlockLogonSessions( LogonSession );
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "KerbRetrieveOWF failed to get TGT: %#x\n", Status));
goto Cleanup;
}
if (CrossRealm)
{
DsysAssert(CrossRealm == FALSE);
Status = STATUS_INTERNAL_ERROR;
goto Cleanup;
}
TgtReply.version = KERBEROS_VERSION;
TgtReply.message_type = KRB_TGT_REP;
TgtReply.ticket = Tgt->Ticket;
DsysAssert(CredmanCred->CredmanDomainName.Length == 0);
if (!KERB_SUCCESS(KerbConvertStringToKdcName(
&TargetName,
&CredmanCred->CredmanUserName
)))
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
//
// HACK HACK
// KerbConvertStringToKdcName builds a bogus name type.
//
TargetName->NameType = KRB_NT_ENTERPRISE_PRINCIPAL;
Status = KerbGetServiceTicket(
LogonSession,
Credential,
CredmanCred,
TargetName, // should be a UPN
&Tgt->ClientDomainName,
NULL,
0,
0,
0,
NULL,
NULL,
&TgtReply,
&NewTicket,
NULL
);
if (!NT_SUCCESS( Status ))
{
DebugLog((DEB_ERROR, "KerbGetServiceTicket failed - %x\n", Status ));
goto Cleanup;
}
//
// Pull the supplemental creds from the ticket.
//
Status = KerbGetCredsFromU2UTicket(
NewTicket,
Tgt,
&PacCreds,
&ValidationInfo
);
if (!NT_SUCCESS( Status))
{
DebugLog((DEB_ERROR, "KerbGetCredsFromTicket failed %x\n", Status));
goto Cleanup;
}
RtlInitUnicodeString(
&Package,
NTLMSP_NAME
);
for ( i = 0; i < PacCreds->CredentialCount; i++ )
{
if (RtlEqualUnicodeString(
&PacCreds->Credentials[i].PackageName,
&Package,
TRUE
))
{
NtlmCred = &PacCreds->Credentials[i];
break;
}
}
if (NtlmCred == NULL || ValidationInfo == NULL)
{
DebugLog((DEB_ERROR, "No NTLM creds %p or ValidationInfo %p found in PAC\n", NtlmCred, ValidationInfo));
Status = STATUS_NOT_FOUND;
DsysAssert(FALSE);
goto Cleanup;
}
UserName.MaximumLength = ValidationInfo->EffectiveName.Length + ValidationInfo->LogonDomainName.Length + (2 * sizeof(WCHAR));
UserName.Length = UserName.MaximumLength - sizeof(WCHAR);
SafeAllocaAllocate(UserName.Buffer, UserName.MaximumLength);
if (UserName.Buffer == NULL)
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlZeroMemory(UserName.Buffer, UserName.MaximumLength);
//
// ntlm accepts empty domain name, therefore "\username" is valid
//
tmp = UserName.Buffer;
RtlCopyMemory(
tmp,
ValidationInfo->LogonDomainName.Buffer,
ValidationInfo->LogonDomainName.Length
);
tmp += (ValidationInfo->LogonDomainName.Length / sizeof(WCHAR));
*tmp = L'\\';
tmp++;
RtlCopyMemory(
tmp,
ValidationInfo->EffectiveName.Buffer,
ValidationInfo->EffectiveName.Length
);
//
// Build the resultant CREDENTIAL for MSV.
//
Status = KerbMarshallMSVCredential(
NtlmCred,
&UserName,
CredTargetName,
&LocalResponse,
&Size
);
if (!NT_SUCCESS( Status ))
{
DebugLog((DEB_ERROR, "KerbMarshalMSVCredential failed %x\n", Status));
goto Cleanup;
}
*Response = LocalResponse;
LocalResponse = NULL;
*ResponseSize = Size;
Cleanup:
if ( PacCreds )
{
MIDL_user_free( PacCreds );
}
if ( ValidationInfo )
{
MIDL_user_free( ValidationInfo );
}
SafeAllocaFree( UserName.Buffer );
KerbFreeString( &TempName );
if ( TargetName )
{
KerbFreeKdcName( &TargetName );
}
if ( NewTicket )
{
KerbDereferenceTicketCacheEntry( NewTicket );
}
if ( LocalResponse )
{
KerbFree(LocalResponse);
}
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbTicklePackage
//
// Synopsis:
//
// Effects:
//
// Arguments:
//
// Requires: Readlock logon session
//
// Returns:
//
// Notes: In order to optimize perf, we order this 1. current password,
// 2. "extra" credentials, 3. old passwords
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbTicklePackage(
IN PKERB_LOGON_SESSION LogonSession,
IN PUNICODE_STRING CredentialBlob
)
{
NTSTATUS Status;
PKERB_PRIMARY_CREDENTIAL CertCred = NULL;
PKERB_CREDMAN_CRED CredmanCred = NULL;
UNICODE_STRING TargetName;
LPWSTR CredBlob = NULL;
HANDLE OldToken = NULL, ClientToken = NULL;
//
// Fester:
// If we ever extend this, we may need to change the value here.
// For now, its just the *session
//
RtlInitUnicodeString(
&TargetName,
CRED_SESSION_WILDCARD_NAME_W
);
//
// Make sure we pass a NULL terminated cred
//
SafeAllocaAllocate( CredBlob, ( CredentialBlob->MaximumLength + sizeof(WCHAR)));
if ( CredBlob == NULL )
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlZeroMemory( CredBlob, ( CredentialBlob->MaximumLength + sizeof(WCHAR)));
RtlCopyMemory( CredBlob, CredentialBlob->Buffer, CredentialBlob->Length );
//
// Got to be impersonating to make this call...
//
Status = NtOpenThreadToken(
NtCurrentThread(),
TOKEN_QUERY | TOKEN_IMPERSONATE,
TRUE,
&OldToken
);
if (!NT_SUCCESS( Status ) && Status != STATUS_NO_TOKEN )
{
DebugLog((DEB_ERROR, "NtOpenThreadToken failed %x\n", Status));
goto Cleanup;
}
Status = LsaFunctions->OpenTokenByLogonId(
&LogonSession->LogonId,
&ClientToken
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Unable to get the client token handle.\n"));
goto Cleanup;
}
if(!SetThreadToken(NULL, ClientToken))
{
D_DebugLog((DEB_ERROR,"Unable to impersonate the client token handle.\n"));
Status = STATUS_CANNOT_IMPERSONATE;
goto Cleanup;
}
Status = KerbConvertCertCredential(
LogonSession,
CredBlob,
&TargetName,
&CertCred
);
if (!NT_SUCCESS( Status ))
{
D_DebugLog((DEB_ERROR, "Failed to convert cert cred %x\n", Status));
goto Cleanup;
}
KerbReadLockLogonSessions(LogonSession);
Status = KerbAddCredmanCredToLogonSession(
LogonSession,
CertCred, // note: freed by this fn, if necessary...
RAS_CREDENTIAL,
&CredmanCred
);
KerbUnlockLogonSessions(LogonSession);
if (!NT_SUCCESS( Status ))
{
D_DebugLog((DEB_ERROR, "Failed to add credman cred %x\n", Status));
goto Cleanup;
}
Cleanup:
if ( OldToken )
{
if (!SetThreadToken( NULL, OldToken ))
{
D_DebugLog((DEB_ERROR,"Unable to impersonate the client token handle.\n"));
Status = STATUS_CANNOT_IMPERSONATE;
}
CloseHandle( OldToken );
}
else
{
RevertToSelf();
}
if ( ClientToken )
{
CloseHandle( ClientToken );
}
SafeAllocaFree( CredBlob );
if ( CredmanCred )
{
KerbDereferenceCredmanCred(
CredmanCred,
&LogonSession->CredmanCredentials
);
}
return Status;
}