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.
669 lines
15 KiB
669 lines
15 KiB
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (c) Microsoft Corp. All rights reserved.
|
|
//
|
|
// FILE
|
|
//
|
|
// cleartxt.c
|
|
//
|
|
// SYNOPSIS
|
|
//
|
|
// Defines functions for storing and retrieving cleartext passwords.
|
|
//
|
|
// MODIFICATION HISTORY
|
|
//
|
|
// 08/31/1998 Original version.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <ntlsa.h>
|
|
#include <windows.h>
|
|
#include <wincrypt.h>
|
|
|
|
#include <rassfmhp.h>
|
|
#include <usrprop.h>
|
|
#include <cleartxt.h>
|
|
|
|
// Name of the private key stored as LSA private data.
|
|
UNICODE_STRING PRIVATE_KEY_NAME = { 34, 36, L"G$MSRADIUSPRIVKEY" };
|
|
|
|
// Length of the private key.
|
|
#define PRIVATE_KEY_LENGTH 256
|
|
|
|
// Length of the user-specific key.
|
|
#define USER_KEY_LENGTH 16
|
|
|
|
// Properties stored in UserParameters.
|
|
#define PROPERTY_USER_KEY L"G$RADIUSCHAPKEY"
|
|
#define PROPERTY_PASSWORD L"G$RADIUSCHAP"
|
|
|
|
// Fixed key used for decrypting the private key.
|
|
BYTE FIXED_KEY[] =
|
|
{
|
|
0x05, 0x56, 0xF6, 0x07, 0xC6, 0x56, 0x02, 0x94, 0x02,
|
|
0xC6, 0xF6, 0x67, 0x56, 0x02, 0xC6, 0x96, 0xB6, 0x56,
|
|
0x02, 0x34, 0x86, 0x16, 0xE6, 0x46, 0x27, 0x16, 0xC2,
|
|
0x02, 0x14, 0x46, 0x96, 0x47, 0x96, 0xC2, 0x02, 0x74,
|
|
0x27, 0x56, 0x47, 0x16, 0x02, 0x16, 0x27, 0x56, 0x02,
|
|
0x47, 0x86, 0x56, 0x02, 0x07, 0x56, 0xF6, 0x07, 0xC6,
|
|
0x56, 0x02, 0x94, 0x02, 0x47, 0x27, 0x57, 0x37, 0x47
|
|
};
|
|
|
|
// Shared handle to the cryptographic provider.
|
|
HCRYPTPROV theContext;
|
|
|
|
// Private key used for encrypting/decrypting cleartext passwords.
|
|
PLSA_UNICODE_STRING thePrivateKey;
|
|
|
|
// TRUE if this API has been successfully initialized.
|
|
static BOOL theInitFlag;
|
|
|
|
// Non-zero if the API is locked.
|
|
static LONG theLock;
|
|
|
|
//////////
|
|
// Macros to lock/unlock the API during intialization.
|
|
//////////
|
|
#define API_LOCK() \
|
|
while (InterlockedExchange(&theLock, 1)) Sleep(5)
|
|
|
|
#define API_UNLOCK() \
|
|
InterlockedExchange(&theLock, 0)
|
|
|
|
//////////
|
|
// Macro that ensures the API has been initialized and bails on failure.
|
|
//////////
|
|
#define CHECK_INIT() \
|
|
if (!theInitFlag) { \
|
|
status = IASParmsInitialize(); \
|
|
if (status != NO_ERROR) { return status; } \
|
|
}
|
|
|
|
//////////
|
|
// Creates the private key. Should only be called if the key doesn't exist.
|
|
//////////
|
|
DWORD
|
|
WINAPI
|
|
IASCreatePrivateKey(
|
|
IN LSA_HANDLE hPolicy
|
|
)
|
|
{
|
|
DWORD status;
|
|
BYTE newKey[PRIVATE_KEY_LENGTH];
|
|
LSA_UNICODE_STRING privateData;
|
|
|
|
//////////
|
|
// Generate a random key.
|
|
//////////
|
|
|
|
if (!CryptGenRandom(
|
|
theContext,
|
|
sizeof(newKey),
|
|
newKey
|
|
))
|
|
{ return GetLastError(); }
|
|
|
|
//////////
|
|
// Store it as LSA private data.
|
|
//////////
|
|
|
|
privateData.Length = sizeof(newKey);
|
|
privateData.MaximumLength = sizeof(newKey);
|
|
privateData.Buffer = (PWSTR)newKey;
|
|
|
|
status = LsaStorePrivateData(
|
|
hPolicy,
|
|
&PRIVATE_KEY_NAME,
|
|
&privateData
|
|
);
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
status = LsaRetrievePrivateData(
|
|
hPolicy,
|
|
&PRIVATE_KEY_NAME,
|
|
&thePrivateKey
|
|
);
|
|
}
|
|
|
|
return NT_SUCCESS(status) ? NO_ERROR : RtlNtStatusToDosError(status);
|
|
}
|
|
|
|
//////////
|
|
// Derives a cryptographic key from an octet string.
|
|
//////////
|
|
BOOL
|
|
WINAPI
|
|
IASDeriveUserCryptKey(
|
|
IN PBYTE pbUserKey,
|
|
OUT HCRYPTKEY *phKey
|
|
)
|
|
{
|
|
BOOL success;
|
|
HCRYPTHASH hHash;
|
|
|
|
success = CryptCreateHash(
|
|
theContext,
|
|
CALG_MD5,
|
|
0,
|
|
0,
|
|
&hHash
|
|
);
|
|
if (!success) { goto exit; }
|
|
|
|
success = CryptHashData(
|
|
hHash,
|
|
(PBYTE)thePrivateKey->Buffer,
|
|
thePrivateKey->Length,
|
|
0
|
|
);
|
|
if (!success) { goto destroy_hash; }
|
|
|
|
success = CryptHashData(
|
|
hHash,
|
|
pbUserKey,
|
|
USER_KEY_LENGTH,
|
|
0
|
|
);
|
|
if (!success) { goto destroy_hash; }
|
|
|
|
success = CryptDeriveKey(
|
|
theContext,
|
|
CALG_RC4,
|
|
hHash,
|
|
CRYPT_EXPORTABLE,
|
|
phKey
|
|
);
|
|
|
|
destroy_hash:
|
|
CryptDestroyHash(hHash);
|
|
|
|
exit:
|
|
return success;
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
IASParmsInitialize( VOID )
|
|
{
|
|
DWORD status, nbyte;
|
|
OBJECT_ATTRIBUTES objAttribs;
|
|
LSA_HANDLE hPolicy;
|
|
HCRYPTHASH hHash;
|
|
HCRYPTKEY hKey;
|
|
|
|
API_LOCK();
|
|
|
|
// If we've already been initialized, there's nothing to do.
|
|
if (theInitFlag)
|
|
{
|
|
status = NO_ERROR;
|
|
goto exit;
|
|
}
|
|
|
|
/////////
|
|
// Acquire a cryptographic context.
|
|
/////////
|
|
|
|
if (!CryptAcquireContext(
|
|
&theContext,
|
|
NULL,
|
|
NULL,
|
|
PROV_RSA_FULL,
|
|
CRYPT_VERIFYCONTEXT
|
|
))
|
|
{
|
|
status = GetLastError();
|
|
goto exit;
|
|
}
|
|
|
|
/////////
|
|
// Open a handle to the LSA.
|
|
/////////
|
|
|
|
InitializeObjectAttributes(
|
|
&objAttribs,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
status = LsaOpenPolicy(
|
|
NULL,
|
|
&objAttribs,
|
|
POLICY_ALL_ACCESS,
|
|
&hPolicy
|
|
);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
status = RtlNtStatusToDosError(status);
|
|
goto exit;
|
|
}
|
|
|
|
/////////
|
|
// Retrieve the private key.
|
|
/////////
|
|
|
|
status = LsaRetrievePrivateData(
|
|
hPolicy,
|
|
&PRIVATE_KEY_NAME,
|
|
&thePrivateKey
|
|
);
|
|
if ( (status == STATUS_OBJECT_NAME_NOT_FOUND) ||
|
|
(NT_SUCCESS(status) &&
|
|
(
|
|
(thePrivateKey != NULL && thePrivateKey->Length == 0) ||
|
|
(thePrivateKey == NULL)
|
|
)
|
|
)
|
|
)
|
|
{
|
|
// If it doesn't exist, create a new one.
|
|
status = IASCreatePrivateKey(
|
|
hPolicy
|
|
);
|
|
}
|
|
else if (!NT_SUCCESS(status))
|
|
{
|
|
status = RtlNtStatusToDosError(status);
|
|
}
|
|
|
|
if (status != NO_ERROR) { goto close_policy; }
|
|
|
|
/////////
|
|
// Derive a crypto key from the fixed key.
|
|
/////////
|
|
|
|
if (!CryptCreateHash(
|
|
theContext,
|
|
CALG_MD5,
|
|
0,
|
|
0,
|
|
&hHash
|
|
))
|
|
{
|
|
status = GetLastError();
|
|
goto close_policy;
|
|
}
|
|
|
|
if (!CryptHashData(
|
|
hHash,
|
|
FIXED_KEY,
|
|
sizeof(FIXED_KEY),
|
|
0
|
|
))
|
|
{
|
|
status = GetLastError();
|
|
goto destroy_hash;
|
|
}
|
|
|
|
if (!CryptDeriveKey(
|
|
theContext,
|
|
CALG_RC4,
|
|
hHash,
|
|
CRYPT_EXPORTABLE,
|
|
&hKey
|
|
))
|
|
{
|
|
status = GetLastError();
|
|
goto destroy_hash;
|
|
}
|
|
|
|
/////////
|
|
// Decrypt the private key.
|
|
/////////
|
|
|
|
nbyte = thePrivateKey->Length;
|
|
|
|
if (!CryptDecrypt(
|
|
hKey,
|
|
0,
|
|
TRUE,
|
|
0,
|
|
(PBYTE)thePrivateKey->Buffer,
|
|
&nbyte
|
|
))
|
|
{
|
|
status = GetLastError();
|
|
goto destroy_key;
|
|
}
|
|
|
|
thePrivateKey->Length = (USHORT)nbyte;
|
|
|
|
destroy_key:
|
|
CryptDestroyKey(hKey);
|
|
|
|
destroy_hash:
|
|
CryptDestroyHash(hHash);
|
|
|
|
close_policy:
|
|
LsaClose(hPolicy);
|
|
|
|
exit:
|
|
if (status == NO_ERROR)
|
|
{
|
|
// We succeeded, so set theInitFlag.
|
|
theInitFlag = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// We failed, so clean up.
|
|
if (thePrivateKey)
|
|
{
|
|
LsaFreeMemory(thePrivateKey);
|
|
thePrivateKey = NULL;
|
|
}
|
|
|
|
if (theContext)
|
|
{
|
|
CryptReleaseContext(theContext, 0);
|
|
theContext = 0;
|
|
}
|
|
}
|
|
|
|
API_UNLOCK();
|
|
return status;
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
IASParmsClearUserPassword(
|
|
IN PCWSTR szUserParms,
|
|
OUT PWSTR *pszNewUserParms
|
|
)
|
|
{
|
|
DWORD status;
|
|
UNICODE_STRING property;
|
|
PWSTR tempUserParms;
|
|
BOOL updateKey, updatePwd;
|
|
|
|
// Check the in parameters.
|
|
if (pszNewUserParms == NULL) { return ERROR_INVALID_PARAMETER; }
|
|
|
|
/////////
|
|
// Write a null string to the relevant properties.
|
|
/////////
|
|
|
|
memset(&property, 0, sizeof(property));
|
|
|
|
status = NetpParmsSetUserProperty(
|
|
(PWSTR)szUserParms,
|
|
PROPERTY_PASSWORD,
|
|
property,
|
|
0,
|
|
&tempUserParms,
|
|
&updatePwd
|
|
);
|
|
if (!NT_SUCCESS(status)) { return RtlNtStatusToDosError(status); }
|
|
|
|
status = NetpParmsSetUserProperty(
|
|
tempUserParms,
|
|
PROPERTY_USER_KEY,
|
|
property,
|
|
0,
|
|
pszNewUserParms,
|
|
&updateKey
|
|
);
|
|
|
|
NetpParmsUserPropertyFree(tempUserParms);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
if (!updatePwd && !updateKey)
|
|
{
|
|
// Nothing changed so don't return the NewUserParms.
|
|
NetpParmsUserPropertyFree(*pszNewUserParms);
|
|
*pszNewUserParms = NULL;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
return RtlNtStatusToDosError(status);
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
IASParmsGetUserPassword(
|
|
IN PCWSTR szUserParms,
|
|
OUT PWSTR *pszPassword
|
|
)
|
|
{
|
|
DWORD status, nbyte;
|
|
UNICODE_STRING userKey, encryptedPwd;
|
|
WCHAR propFlag;
|
|
HCRYPTKEY hKey;
|
|
|
|
// Check the in parameters.
|
|
if (pszPassword == NULL) { return ERROR_INVALID_PARAMETER; }
|
|
|
|
// Make sure we're initialized.
|
|
CHECK_INIT();
|
|
|
|
// Read the user key.
|
|
status = NetpParmsQueryUserProperty(
|
|
(PWSTR)szUserParms,
|
|
PROPERTY_USER_KEY,
|
|
&propFlag,
|
|
&userKey
|
|
);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
status = RtlNtStatusToDosError(status);
|
|
goto exit;
|
|
}
|
|
|
|
// Read the encrypted password.
|
|
status = NetpParmsQueryUserProperty(
|
|
(PWSTR)szUserParms,
|
|
PROPERTY_PASSWORD,
|
|
&propFlag,
|
|
&encryptedPwd
|
|
);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
status = RtlNtStatusToDosError(status);
|
|
goto free_key;
|
|
}
|
|
|
|
// If they're both NULL, it's not an error. It just means the cleartext
|
|
// password was never set.
|
|
if (userKey.Buffer == NULL && encryptedPwd.Buffer == NULL)
|
|
{
|
|
*pszPassword = NULL;
|
|
goto exit;
|
|
}
|
|
|
|
// Make sure the user key is the correct length.
|
|
if (userKey.Length != USER_KEY_LENGTH)
|
|
{
|
|
status = ERROR_INVALID_DATA;
|
|
goto free_password;
|
|
}
|
|
|
|
// Convert the user key to a cryptographic key.
|
|
if (!IASDeriveUserCryptKey(
|
|
(PBYTE)userKey.Buffer,
|
|
&hKey
|
|
))
|
|
{
|
|
status = GetLastError();
|
|
goto free_password;
|
|
}
|
|
|
|
// Decrypt the password.
|
|
nbyte = encryptedPwd.Length;
|
|
if (!CryptDecrypt(
|
|
hKey,
|
|
0,
|
|
TRUE,
|
|
0,
|
|
(PBYTE)encryptedPwd.Buffer,
|
|
&nbyte
|
|
))
|
|
{
|
|
status = GetLastError();
|
|
goto destroy_key;
|
|
}
|
|
|
|
// We encrypted the terminating null, so it should still be there.
|
|
if (encryptedPwd.Buffer[nbyte / sizeof(WCHAR) - 1] != L'\0')
|
|
{
|
|
status = ERROR_INVALID_DATA;
|
|
goto destroy_key;
|
|
}
|
|
|
|
// Return the cleartext password to the caller.
|
|
*pszPassword = encryptedPwd.Buffer;
|
|
encryptedPwd.Buffer = NULL;
|
|
|
|
destroy_key:
|
|
CryptDestroyKey(hKey);
|
|
|
|
free_password:
|
|
LocalFree(encryptedPwd.Buffer);
|
|
|
|
free_key:
|
|
LocalFree(userKey.Buffer);
|
|
|
|
exit:
|
|
return status;
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
IASParmsSetUserPassword(
|
|
IN PCWSTR szUserParms,
|
|
IN PCWSTR szPassword,
|
|
OUT PWSTR *pszNewUserParms
|
|
)
|
|
{
|
|
DWORD status;
|
|
BYTE userKey[USER_KEY_LENGTH];
|
|
HCRYPTKEY hKey;
|
|
DWORD nbyte;
|
|
PBYTE encryptedPwd;
|
|
UNICODE_STRING property;
|
|
PWSTR tempUserParms;
|
|
BOOL update;
|
|
|
|
// Check the in parameters.
|
|
if (szPassword == NULL) { return ERROR_INVALID_PARAMETER; }
|
|
|
|
// Make sure we're initialized.
|
|
CHECK_INIT();
|
|
|
|
// Generate a user key.
|
|
if (!CryptGenRandom(
|
|
theContext,
|
|
USER_KEY_LENGTH,
|
|
userKey
|
|
))
|
|
{
|
|
status = GetLastError();
|
|
goto exit;
|
|
}
|
|
|
|
// Convert the user key to a cryptographic key.
|
|
if (!IASDeriveUserCryptKey(
|
|
userKey,
|
|
&hKey
|
|
))
|
|
{
|
|
status = GetLastError();
|
|
goto exit;
|
|
}
|
|
|
|
// Allocate a buffer for the encrypted password.
|
|
nbyte = sizeof(WCHAR) * (lstrlenW(szPassword) + 1);
|
|
encryptedPwd = RtlAllocateHeap(
|
|
RasSfmHeap(),
|
|
0,
|
|
nbyte
|
|
);
|
|
if (encryptedPwd == NULL)
|
|
{
|
|
status = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto destroy_key;
|
|
}
|
|
|
|
memcpy(encryptedPwd, szPassword, nbyte);
|
|
|
|
// Encrypt the password.
|
|
if (!CryptEncrypt(
|
|
hKey,
|
|
0,
|
|
TRUE,
|
|
0,
|
|
encryptedPwd,
|
|
&nbyte,
|
|
nbyte
|
|
))
|
|
{
|
|
status = GetLastError();
|
|
goto free_encrypted_password;
|
|
}
|
|
|
|
/////////
|
|
// Store the encrypted password.
|
|
/////////
|
|
|
|
property.Buffer = (PWCHAR)encryptedPwd;
|
|
property.Length = (USHORT)nbyte;
|
|
property.MaximumLength = (USHORT)nbyte;
|
|
|
|
status = NetpParmsSetUserProperty(
|
|
(PWSTR)szUserParms,
|
|
PROPERTY_PASSWORD,
|
|
property,
|
|
0,
|
|
&tempUserParms,
|
|
&update
|
|
);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
status = RtlNtStatusToDosError(status);
|
|
goto free_encrypted_password;
|
|
}
|
|
|
|
/////////
|
|
// Store the user key.
|
|
/////////
|
|
|
|
property.Buffer = (PWSTR)userKey;
|
|
property.Length = USER_KEY_LENGTH;
|
|
property.MaximumLength = USER_KEY_LENGTH;
|
|
|
|
status = NetpParmsSetUserProperty(
|
|
tempUserParms,
|
|
PROPERTY_USER_KEY,
|
|
property,
|
|
0,
|
|
pszNewUserParms,
|
|
&update
|
|
);
|
|
if (!NT_SUCCESS(status)) { status = RtlNtStatusToDosError(status); }
|
|
|
|
NetpParmsUserPropertyFree(tempUserParms);
|
|
|
|
free_encrypted_password:
|
|
RtlFreeHeap(RasSfmHeap(), 0, encryptedPwd);
|
|
|
|
destroy_key:
|
|
CryptDestroyKey(hKey);
|
|
|
|
exit:
|
|
return status;
|
|
}
|
|
|
|
VOID
|
|
WINAPI
|
|
IASParmsFreeUserParms(
|
|
IN LPWSTR szNewUserParms
|
|
)
|
|
{
|
|
NetpParmsUserPropertyFree(szNewUserParms);
|
|
}
|