/////////////////////////////////////////////////////////////////////////////// // // 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 #include #include #include #include #include #include #include #include // 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); }