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.
703 lines
18 KiB
703 lines
18 KiB
/*++
|
|
|
|
Copyright (c) 1996, 1997 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
winpw.c
|
|
|
|
Abstract:
|
|
|
|
This module contains routines for retrieving and verification of the
|
|
Windows NT and Windows 95 password associated with the client calling
|
|
protected storage.
|
|
|
|
Author:
|
|
|
|
Scott Field (sfield) 12-Dec-96
|
|
|
|
--*/
|
|
|
|
#include <windows.h>
|
|
#include <lmcons.h>
|
|
#include <sha.h>
|
|
#include "lnklist.h"
|
|
#include "winpw.h"
|
|
#include "module.h"
|
|
#include "unicode.h"
|
|
#include "unicode5.h"
|
|
#include "debug.h"
|
|
|
|
#include "secmisc.h"
|
|
|
|
#define MPR_PROCESS "MPREXE.EXE"
|
|
#define MPRSERV_MODULE "MPRSERV.DLL"
|
|
|
|
#define GLOBAL_USERNAME 0x0E8
|
|
#define PWL_USERNAME 0x170
|
|
#define GLOBAL_PASSWORD 0x188
|
|
#define PWL_PASSWORD 0x210
|
|
|
|
//
|
|
// this one comes and goes only when needed
|
|
//
|
|
|
|
typedef DWORD (WINAPI *WNETVERIFYPASSWORD)(
|
|
LPCSTR lpszPassword,
|
|
BOOL *pfMatch
|
|
);
|
|
|
|
typedef DWORD (WINAPI *WNETGETUSERA)(
|
|
LPCSTR lpName,
|
|
LPSTR lpUserName,
|
|
LPDWORD lpnLength
|
|
);
|
|
|
|
WNETGETUSERA _WNetGetUserA = NULL;
|
|
|
|
//
|
|
// global Win95 password buffer. Only need one entry because Win95
|
|
// only allows one user logged on at a time.
|
|
//
|
|
|
|
static WIN95_PASSWORD g_Win95Password;
|
|
|
|
BOOL
|
|
VerifyWindowsPasswordNT(
|
|
LPCWSTR Password
|
|
);
|
|
|
|
BOOL
|
|
GetTokenLogonType(
|
|
HANDLE hToken,
|
|
LPDWORD lpdwLogonType
|
|
);
|
|
|
|
BOOL
|
|
GetTokenLogonType(
|
|
HANDLE hToken,
|
|
LPDWORD lpdwLogonType
|
|
)
|
|
/*++
|
|
This function retrieves the logon type associated with the
|
|
access token specified by the hToken parameter. On success,
|
|
the DWORD buffer provided by the dwLogonType parameter is
|
|
filled with the logon type which corresponds to the currently
|
|
known logon types supported by the LogonUser() Windows NT
|
|
API call.
|
|
|
|
The token specified by the hToken parameter must have been
|
|
opened with at least TOKEN_QUERY access.
|
|
|
|
This function is only relevant on Windows NT and should not
|
|
be called on Windows 95, as it will always return FALSE.
|
|
|
|
--*/
|
|
{
|
|
UCHAR InfoBuffer[1024];
|
|
DWORD dwInfoBufferSize = sizeof(InfoBuffer);
|
|
PTOKEN_GROUPS SlowBuffer = NULL;
|
|
PTOKEN_GROUPS ptgGroups = (PTOKEN_GROUPS)InfoBuffer;
|
|
PSID psidInteractive = NULL;
|
|
SID_IDENTIFIER_AUTHORITY siaNtAuthority = SECURITY_NT_AUTHORITY;
|
|
BOOL bSuccess;
|
|
|
|
bSuccess = GetTokenInformation(
|
|
hToken,
|
|
TokenGroups,
|
|
ptgGroups,
|
|
dwInfoBufferSize,
|
|
&dwInfoBufferSize
|
|
);
|
|
|
|
//
|
|
// if fast buffer wasn't big enough, allocate enough storage
|
|
// and try again.
|
|
//
|
|
|
|
if(!bSuccess && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
|
SlowBuffer = (PTOKEN_GROUPS)SSAlloc(dwInfoBufferSize);
|
|
if(SlowBuffer != NULL) {
|
|
|
|
ptgGroups = SlowBuffer;
|
|
bSuccess = GetTokenInformation(
|
|
hToken,
|
|
TokenGroups,
|
|
ptgGroups,
|
|
dwInfoBufferSize,
|
|
&dwInfoBufferSize
|
|
);
|
|
|
|
if(!bSuccess) {
|
|
SSFree(SlowBuffer);
|
|
SlowBuffer = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!bSuccess)
|
|
return FALSE;
|
|
|
|
//
|
|
// initialize a single well-known logon Sid, since
|
|
// we only compare the prefix and then the Rid
|
|
// note that if performance were of utmost importance, we should
|
|
// use InitializeSid + GetSidSubAuthority (to _set_ the Rid).
|
|
// also note that we could do a simple memcmp against just the sid
|
|
// identifier authority, but this assumes that Sid versions/layouts don't
|
|
// change
|
|
//
|
|
|
|
bSuccess = AllocateAndInitializeSid(
|
|
&siaNtAuthority,
|
|
1,
|
|
SECURITY_INTERACTIVE_RID,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
&psidInteractive
|
|
);
|
|
|
|
if(bSuccess) {
|
|
UINT x;
|
|
|
|
bSuccess = FALSE; // assume no match
|
|
|
|
//
|
|
// loop through groups checking for equality against
|
|
// the well-known logon Sids.
|
|
//
|
|
|
|
for(x = 0 ; x < ptgGroups->GroupCount ; x++)
|
|
{
|
|
DWORD Rid;
|
|
|
|
//
|
|
// first, see if subauthority count matches, since
|
|
// not too many sids have only one subauthority.
|
|
//
|
|
|
|
if(*GetSidSubAuthorityCount(ptgGroups->Groups[x].Sid) != 1)
|
|
continue;
|
|
|
|
//
|
|
// next, see if the Sid prefix matches, since
|
|
// all the logon Sids have the same prefix
|
|
// "S-1-5"
|
|
//
|
|
|
|
if(!EqualPrefixSid(psidInteractive, ptgGroups->Groups[x].Sid))
|
|
continue;
|
|
|
|
//
|
|
// if it's a logon sid prefix, just compare the Rid
|
|
// to the known values.
|
|
//
|
|
|
|
Rid = *GetSidSubAuthority(ptgGroups->Groups[x].Sid, 0);
|
|
switch (Rid) {
|
|
case SECURITY_INTERACTIVE_RID:
|
|
*lpdwLogonType = LOGON32_LOGON_INTERACTIVE;
|
|
break;
|
|
|
|
case SECURITY_BATCH_RID:
|
|
*lpdwLogonType = LOGON32_LOGON_BATCH;
|
|
break;
|
|
|
|
case SECURITY_SERVICE_RID:
|
|
*lpdwLogonType = LOGON32_LOGON_SERVICE;
|
|
break;
|
|
|
|
case SECURITY_NETWORK_RID:
|
|
*lpdwLogonType = LOGON32_LOGON_NETWORK;
|
|
break;
|
|
|
|
default:
|
|
continue; // ignore unknown logon type and continue
|
|
}
|
|
|
|
bSuccess = TRUE; // indicate success and bail
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(SlowBuffer)
|
|
SSFree(SlowBuffer);
|
|
|
|
if(psidInteractive)
|
|
FreeSid(psidInteractive);
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
BOOL
|
|
SetPasswordNT(
|
|
PLUID LogonID,
|
|
BYTE HashedPassword[A_SHA_DIGEST_LEN]
|
|
)
|
|
|
|
/*++
|
|
|
|
This function adds the hashed password that is referenced by the specified
|
|
Logon ID.
|
|
|
|
--*/
|
|
|
|
{
|
|
#if 0
|
|
return AddNTPassword(LogonID, HashedPassword);
|
|
#else
|
|
return TRUE; // do nothing, just return success
|
|
#endif
|
|
}
|
|
|
|
|
|
BOOL
|
|
GetPasswordNT(
|
|
BYTE HashedPassword[A_SHA_DIGEST_LEN]
|
|
)
|
|
/*++
|
|
|
|
This function retrieves the hashed password associated with the calling
|
|
thread access token. This requires that the calling thread is impersonating
|
|
the user associated with the password request. The credentials associated
|
|
with the authentication ID are returned. This is done because WinNT
|
|
supports multiple logged on users, and we must return the correct credentials.
|
|
|
|
--*/
|
|
{
|
|
#if 0
|
|
LUID AuthenticationId;
|
|
|
|
if(!GetThreadAuthenticationId(
|
|
GetCurrentThread(),
|
|
&AuthenticationId
|
|
)) return FALSE;
|
|
|
|
return FindNTPassword(&AuthenticationId, HashedPassword);
|
|
#else
|
|
|
|
return FALSE; // no cache to search, just return FALSE
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
BOOL
|
|
GetSpecialCasePasswordNT(
|
|
BYTE HashedPassword[A_SHA_DIGEST_LEN], // derived bits when fSpecialCase == TRUE
|
|
LPBOOL fSpecialCase // legal special case encountered?
|
|
)
|
|
/*++
|
|
|
|
This routine determines if the calling thread's access token is eligible
|
|
to recieve a special case hashed password.
|
|
|
|
If an legal special case is encountered (Local System Account), we
|
|
fill the HashPassword buffer with an consistent hash, set fSpecialCase to
|
|
TRUE, and return TRUE.
|
|
|
|
If an illegal special case is encountered (Network SID), fSpecialCase is
|
|
set FALSE, and we return FALSE.
|
|
|
|
If we encounter an access token that appears to have valid credentials,
|
|
but we have no way to get at them (Interactive, Batch, Service ... ),
|
|
fSpecialCase is set FALSE and we return TRUE.
|
|
|
|
The calling thread MUST be imperonsating the client in question prior to
|
|
making this call.
|
|
|
|
--*/
|
|
{
|
|
HANDLE hToken = NULL;
|
|
DWORD dwLogonType;
|
|
A_SHA_CTX context;
|
|
BOOL fSuccess = FALSE;
|
|
|
|
*fSpecialCase = FALSE;
|
|
|
|
if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
|
|
return FALSE;
|
|
|
|
//
|
|
// first, get the token logon type.
|
|
//
|
|
|
|
fSuccess = GetTokenLogonType(hToken, &dwLogonType);
|
|
|
|
//
|
|
// if we got the token logon type ok, check if it's an appropriate type.
|
|
// otherwise, check for the local system special case.
|
|
//
|
|
|
|
if(fSuccess) {
|
|
|
|
//
|
|
// we only indicate success for the interactive logon type.
|
|
// note default is fSuccess == TRUE when going to cleanup
|
|
//
|
|
|
|
if(dwLogonType != LOGON32_LOGON_INTERACTIVE)
|
|
fSuccess = FALSE;
|
|
|
|
goto cleanup;
|
|
} else {
|
|
|
|
SID_IDENTIFIER_AUTHORITY sia = SECURITY_NT_AUTHORITY;
|
|
PSID pSystemSid;
|
|
PSID pTokenSid;
|
|
|
|
fSuccess = GetTokenUserSid(hToken, &pTokenSid);
|
|
if(!fSuccess)
|
|
goto cleanup;
|
|
|
|
|
|
//
|
|
// build local system sid and compare.
|
|
//
|
|
|
|
fSuccess = AllocateAndInitializeSid(
|
|
&sia,
|
|
1,
|
|
SECURITY_LOCAL_SYSTEM_RID,
|
|
0, 0, 0, 0, 0, 0, 0,
|
|
&pSystemSid
|
|
);
|
|
|
|
if( fSuccess ) {
|
|
|
|
//
|
|
// check sid equality. If so, hash and tell the caller about it.
|
|
//
|
|
|
|
if( EqualSid(pSystemSid, pTokenSid) ) {
|
|
|
|
//
|
|
// hash the special case user Sid
|
|
//
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, (LPBYTE)pTokenSid, GetLengthSid(pTokenSid));
|
|
A_SHAFinal(&context, HashedPassword);
|
|
|
|
*fSpecialCase = TRUE;
|
|
}
|
|
|
|
FreeSid(pSystemSid);
|
|
}
|
|
|
|
SSFree(pTokenSid);
|
|
}
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
if(hToken)
|
|
CloseHandle(hToken);
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL
|
|
SetPassword95(
|
|
BYTE HashedUsername[A_SHA_DIGEST_LEN],
|
|
BYTE HashedPassword[A_SHA_DIGEST_LEN]
|
|
)
|
|
/*++
|
|
|
|
This function adds the hashed password that is referenced by the hashed
|
|
user name.
|
|
|
|
Set HashedUsername and HashedPassword to NULL when calling to zero-out
|
|
the single password entry.
|
|
|
|
--*/
|
|
{
|
|
if(HashedUsername == NULL || HashedPassword == NULL) {
|
|
g_Win95Password.bValid = FALSE;
|
|
RtlSecureZeroMemory(g_Win95Password.HashedPassword, A_SHA_DIGEST_LEN);
|
|
RtlSecureZeroMemory(g_Win95Password.HashedUsername, A_SHA_DIGEST_LEN);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
memcpy(g_Win95Password.HashedUsername, HashedUsername, A_SHA_DIGEST_LEN);
|
|
memcpy(g_Win95Password.HashedPassword, HashedPassword, A_SHA_DIGEST_LEN);
|
|
|
|
g_Win95Password.bValid = TRUE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
GetPassword95(
|
|
BYTE HashedPassword[A_SHA_DIGEST_LEN]
|
|
)
|
|
/*++
|
|
|
|
This function retrieves the hashed password associated with the calling
|
|
thread. In Win95, only one user is logged on, so this operation is
|
|
a simple copy from global memory, once the hash of the current user
|
|
matches that which was stored with the hashed credential.
|
|
|
|
--*/
|
|
{
|
|
A_SHA_CTX context;
|
|
BYTE HashUsername[A_SHA_DIGEST_LEN];
|
|
CHAR Username[UNLEN+1];
|
|
DWORD cchUsername = UNLEN;
|
|
|
|
//
|
|
// don't release credential unless hash of username matches
|
|
// sfield: use WNetGetUser() instead of GetUserName() as WNetGetUser()
|
|
// will correspond to the password associated with what the network
|
|
// provider gave us.
|
|
//
|
|
|
|
if(_WNetGetUserA(NULL, Username, &cchUsername) != NO_ERROR) {
|
|
|
|
//
|
|
// for Win95, if nobody is logged on, empty user name + password
|
|
//
|
|
|
|
if(GetLastError() != ERROR_NOT_LOGGED_ON)
|
|
return FALSE;
|
|
|
|
Username[0] = '\0'; // not really necessary
|
|
cchUsername = 1;
|
|
} else {
|
|
|
|
// arg, WNetGetUserA() doesn't fill in cchUsername
|
|
cchUsername = lstrlenA(Username) + 1; // include terminal NULL
|
|
if(g_Win95Password.bValid == FALSE)
|
|
return FALSE;
|
|
}
|
|
|
|
cchUsername--; // do not include terminal NULL
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, Username, cchUsername);
|
|
A_SHAFinal(&context, HashUsername);
|
|
|
|
//
|
|
// non empty username, may not be empty password
|
|
//
|
|
|
|
if(cchUsername) {
|
|
if(memcmp(HashUsername, g_Win95Password.HashedUsername, A_SHA_DIGEST_LEN) != 0) {
|
|
//
|
|
// rare case on Win95: if we didn't automatically flush the entry
|
|
// during a logoff (this can occur if network provider not hooked),
|
|
// flush it now because we know the entry cannot possibly be valid.
|
|
//
|
|
g_Win95Password.bValid = FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
memcpy(HashedPassword, g_Win95Password.HashedPassword, A_SHA_DIGEST_LEN);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// empty user name == empty password
|
|
//
|
|
|
|
memcpy(HashedPassword, HashUsername, A_SHA_DIGEST_LEN);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL
|
|
VerifyWindowsPassword(
|
|
LPCWSTR Password
|
|
)
|
|
/*++
|
|
This function verifies that the specified password matches that of the
|
|
current user.
|
|
|
|
On Windows 95, the current user equates to the user is currently logged
|
|
onto the machine.
|
|
|
|
On Windows NT, the current user equates to the user which is being
|
|
impersonated during the call. On Windows NT, the caller MUST be
|
|
impersonating the user associated with the validation.
|
|
|
|
On Windows NT, a side effect of the validation is notification of
|
|
a new logon to the credential manager. This is ignored because the
|
|
authentication ID present in the new logon does not match the
|
|
authentication ID present in the impersonated access token.
|
|
|
|
--*/
|
|
{
|
|
return VerifyWindowsPasswordNT(Password);
|
|
}
|
|
|
|
BOOL
|
|
VerifyWindowsPasswordNT(
|
|
LPCWSTR Password
|
|
)
|
|
{
|
|
HANDLE hPriorToken = NULL;
|
|
HANDLE hToken;
|
|
HANDLE hLogonToken = NULL;
|
|
PTOKEN_USER pTokenInfo = NULL;
|
|
DWORD cbTokenInfoSize;
|
|
WCHAR User[UNLEN+1];
|
|
WCHAR Domain[DNLEN+1];
|
|
DWORD cchUser = UNLEN;
|
|
DWORD cchDomain = DNLEN;
|
|
SID_NAME_USE peUse;
|
|
BOOL bSuccess = FALSE;
|
|
|
|
//
|
|
// find out domain and user name associated with current user
|
|
//
|
|
|
|
if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
|
|
return FALSE;
|
|
|
|
cbTokenInfoSize = 512;
|
|
pTokenInfo = (PTOKEN_USER)SSAlloc(cbTokenInfoSize);
|
|
if(pTokenInfo == NULL)
|
|
goto cleanup;
|
|
|
|
if(!GetTokenInformation(
|
|
hToken,
|
|
TokenUser,
|
|
pTokenInfo,
|
|
cbTokenInfoSize,
|
|
&cbTokenInfoSize
|
|
)) {
|
|
|
|
//
|
|
// realloc and try again
|
|
//
|
|
|
|
if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
|
|
|
SSFree(pTokenInfo);
|
|
|
|
pTokenInfo = (PTOKEN_USER)SSAlloc(cbTokenInfoSize);
|
|
if(pTokenInfo == NULL)
|
|
goto cleanup;
|
|
|
|
if(!GetTokenInformation(
|
|
hToken,
|
|
TokenUser,
|
|
pTokenInfo,
|
|
cbTokenInfoSize,
|
|
&cbTokenInfoSize
|
|
)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
} else {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if(!LookupAccountSidW(
|
|
NULL, // default lookup logic
|
|
pTokenInfo->User.Sid,
|
|
User,
|
|
&cchUser,
|
|
Domain,
|
|
&cchDomain,
|
|
&peUse
|
|
))
|
|
goto cleanup;
|
|
|
|
|
|
//
|
|
// WinNT:
|
|
// first try network logon type, if that fails, grovel the token
|
|
// and try the same logon type which is associated with the impersonation
|
|
// token.
|
|
//
|
|
|
|
|
|
//
|
|
// arg! LogonUser() fails in some cases if we are impersonating!
|
|
// so save off impersonation token, revert, and put it back later.
|
|
//
|
|
|
|
if(!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hPriorToken)) {
|
|
hPriorToken = NULL;
|
|
} else {
|
|
RevertToSelf();
|
|
}
|
|
|
|
//
|
|
// network logon type is fastest, and in default NT install, everyone
|
|
// has the SeNetworkLogonRight, so it's very likely to pass the logon right
|
|
// test
|
|
//
|
|
|
|
if(!LogonUserW(
|
|
User,
|
|
Domain,
|
|
(LPWSTR)Password,
|
|
LOGON32_LOGON_NETWORK,
|
|
LOGON32_PROVIDER_DEFAULT,
|
|
&hLogonToken
|
|
)) {
|
|
|
|
DWORD dwLastError = GetLastError();
|
|
DWORD dwLogonType;
|
|
|
|
//
|
|
// retry with different logon type if necessary.
|
|
// note: ERROR_LOGON_TYPE_NOT_GRANTED currently only occurs
|
|
// if the password matches but user didn't have specified logon
|
|
// type. So, currently, we could treat this as a successful validation
|
|
// without retrying, but this is subject to change in future, so retry
|
|
// anyway.
|
|
//
|
|
|
|
if( dwLastError == ERROR_LOGON_TYPE_NOT_GRANTED &&
|
|
GetTokenLogonType(hPriorToken, &dwLogonType)
|
|
) {
|
|
|
|
bSuccess = LogonUserW(
|
|
User,
|
|
Domain,
|
|
(LPWSTR)Password,
|
|
dwLogonType,
|
|
LOGON32_PROVIDER_DEFAULT,
|
|
&hLogonToken
|
|
);
|
|
}
|
|
|
|
if(!bSuccess)
|
|
hLogonToken = NULL; // LogonUser() has tendency to leave garbage in hToken
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
bSuccess = TRUE;
|
|
|
|
cleanup:
|
|
|
|
if(hPriorToken != NULL) {
|
|
SetThreadToken(NULL, hPriorToken);
|
|
CloseHandle(hPriorToken);
|
|
}
|
|
|
|
CloseHandle(hToken);
|
|
|
|
if(hLogonToken)
|
|
CloseHandle(hLogonToken);
|
|
|
|
if(pTokenInfo)
|
|
SSFree(pTokenInfo);
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
|