dev.nlited.com

>>

CryptKM

<<<< prev
next >>>>

2017-09-02 06:23:23 chip Page 2009 📢 PUBLIC

CryptKM has migrated into ChipLib.

All low-level Crypt code now lives in the ChipLib project and has been removed from CryptDisk. Any changes to CryptLib need to be made in ChipLib and the libraries (ChipLib.lib, CryptKM.lib) copied into the LibExt directory. bin\Update_ChipLib.bat will update everything I need.

Although this makes the CryptDisk build/debug process a bit more complicated, it is necessary because CryptLib is now being used by other projects. Accidentally forking CryptLib could easily turn into an epic disaster; the code needs to exist in only one place. CryptKM cannot be its own independent project because most of its source code is shared with CryptUM.

October 16 2023

0730

I am taking a swing at ChipLibKM ...

I switched CryptDisk to the work_chiplib branch, merged everything from work, rebuilt, and submitted it to Microsoft to be counter-signed. Everything seemed to work, which is a relief. It has been a while since I have done any serious work on CryptDisk.

I created a new work_CryptKM branch for ChipLib, forked from work, and rebuilt the Combined project. This also seemed to work. It appears I left everything in working order for once, a huge relief! The CryptKM project also built without errors, I may be closer than I thought...

The next step is to compare the code in ChipLib and CryptDisk projects to make sure they are the same... It has been a long time since I used Araxis, I hope it still works and I still remember how to use it... Well, that was promising. The CryptKM project contains only a couple of files, everything else is pulled from the common Crypt directory that is shared between CryptUM and CryptKM -- exactly as it should. CryptKM is just a container for the kernel build environment. This may turn out to be nothing more than adding CryptKM.lib to ChipLib_Update.bat and changing the linker inputs.

I spent half an hour trying to figure out why the CryptDisk project was complaining about missing pdb files, only to realize I was copying ChipLib.lib from the wrong directory. I need to copy from ChipLib\Out\.

0908: Well... that went much smoother than expected. Driver2 built and linked using the CryptKM.lib copied from ChipLib. Now I need to try using it.

0951: And it works! CryptKM is now officially integrated into ChipLib. For bonus points, I should migrate WinKM...

August 4 2023

I migrated the Crypt4 library to the ChipLib solution on February 5 2020 so I could use encryption in other projects. The low-level crypt files in the CryptDisk project are no longer the definitive versions! CryptDisk now relies on ChipLib to provide the encryption functions. This migration was a "damned if I do, damned if I don't" situation. I needed to use Crypt4 in other projects, but I did not want to have multiple forks of the library popping up like mushrooms. ChipLib was the logical place for Crypt4's new home, as ChipLib has become my go-to source for all my common code and this makes sure it receives enough attention to make sure it always compiles and has all the bug fixes. However, this also meant that the core encryption code at the heart of CryptDisk would no longer be found in the CryptDisk project.

Unfortunately, I did not document this migration as well as I should have and now, three years later I am very confused about which files are being used. It appears I bailed out of the migration halfway. Both the user and kernel mode code was migrated to ChipLib, but only the user mode version is actually built and included in the final ChipLib library. The ChipLib project never builds the kernel version, which was left behind in the CryptDisk project. The user-mode functions are being pulled from ChipLib, but the kernel-mode functions are still using the CryptKM library built in CryptDisk. This creates a disturbing source code fork.

I now want to modify how the cipher streams are initialized. (See ) This change needs to apply to both the user and kernel versions of the Crypt4 library or CryptDisk will break. I spent a lot of time developing a common API that abstracted the user and kernel versions, so I thought I could make a single change in Crypt4.cpp. Then I realized there were two versions of Crypt4.cpp being used -- the kernel build uses the version in CryptDisk while the user build pulls it from ChipLib. This is exactly the sort of fracking forking that causes huge problems!

So before I can begin to update Crypt4, I first need to deal with this forking problem. ChipLib needs to have a new WinKM build configuration to build a ChipLibKM library that contains only the kernel functions, including CryptKM. CryptDisk/Driver2 then needs to link with ChipLibKM instead of CryptKM. Then CryptKM needs to be expunged from the CryptDisk solution.

September 1 2017

I have the "Disk" part working, now I can move on to adding the "Crypt" part.

I have a working and tested Crypt class in my user-mode Support library. Microsoft claims that the bcrypt API is fully supported in kernel mode as well, so hopefully this will be a relatively simple matter of migrating the Crypt code into the WinKM library and building it for kernel-mode.

I created a new code file in the WinKM project named "CryptKM.cpp" and created the standard Create/Destroy interface to the nCrypt class. I then added just enough bcrypt code to prove I could compile and link some BCrypt functions.

The bcrypt.h file is in the Windows SDK in the shared folder:
C:\Program Files (x86)\Windows Kits\8.0\include\shared\bcrypt.h
The BCrypt functions are found in the library:
C:\Program Files (x86)\Windows Kits\8.0\Lib\win8\km\x64\cng.lib

The initial source code:


CryptKM.cpp: OsKernel.h: /*************************************************************************/ /** CryptKM.cpp **/ /*************************************************************************/ #ifdef __cplusplus extern NTSTATUS CryptCreate(class nCrypt *&pCrypt); extern NTSTATUS CryptDestroy(class nCrypt *&pCrypt); #endif
CryptKM.cpp: /*************************************************************************/ /** CryptKM.cpp: Encryption and decryption for kernel mode. **/ /** (C)2017 nlited systems inc, chip doran **/ /*************************************************************************/ #include <new> #include <ntifs.h> #include <WinDef.h> #include <ntstrsafe.h> #include <bcrypt.h> #include "StdTypes.h" #include "Errors.h" #include "OsKernel.h" //#pragma message(__FILE__": Optimizer disabled.") //#pragma optimize("",off) // Link with the Windows crypto libraries... #pragma comment(lib,"cng.lib") class nCrypt { public: static nCrypt *Ptr(void *pObj); static NTSTATUS Create(nCrypt *&pCrypt); NTSTATUS Destroy(void); //Data DWORD Signature_nCrypt; private: nCrypt(void); ~nCrypt(void); NTSTATUS Create2(void); //Data BCRYPT_ALG_HANDLE hECC; //Used to manage the private/public keys BCRYPT_ALG_HANDLE hAES; //Used for the actual symmetric encrypt/decrypt operations. BCRYPT_KEY_HANDLE hUserKey; //The AES key used to encrypt/decrypt user data. DWORD UserKeySz; //Bytes in pUserKey BYTE *pUserKey; //The raw bytes of the UserKey, used as the IV for user data transcryption. DWORD KeyBits; //Bits in the AES key. DWORD SymKeySz; //Size (bytes) of the symmetric key. UINT BlockLen; //Size (bytes) of each cipher block BYTE *pIV; //Buffer for initialization vector (salt) (contents will be modified) UINT StreamCt; //Stream bytes }; /*************************************************************************/ /** Public interface **/ /*************************************************************************/ NTSTATUS CryptCreate(class nCrypt *&pCrypt) { return(nCrypt::Create(pCrypt)); } NTSTATUS CryptDestroy(class nCrypt *&pCrypt) { NTSTATUS Status= STATUS_SUCCESS; if(pCrypt= nCrypt::Ptr(pCrypt)) { Status= pCrypt->Destroy(); pCrypt= 0; } return(Status); } /*************************************************************************/ /** nCrypt **/ /*************************************************************************/ nCrypt::nCrypt(void) { Signature_nCrypt= SIGNATURE_NCRYPT; hECC= hAES= 0; hUserKey= 0; pUserKey= 0; UserKeySz= 0; KeyBits= 128; SymKeySz= 16; BlockLen= 160; pIV= 0; } nCrypt::~nCrypt(void) { Signature_nCrypt|= SIGNATURE_INVALID; if(hUserKey) BCryptDestroyKey(hUserKey); if(hECC) BCryptCloseAlgorithmProvider(hECC,0); if(hAES) BCryptCloseAlgorithmProvider(hAES,0); MemFree(pIV); MemFree(pUserKey); } nCrypt *nCrypt::Ptr(void *pObj) { nCrypt *pCrypt= (nCrypt*)pObj; if(!pObj || pCrypt->Signature_nCrypt!=SIGNATURE_NCRYPT) pCrypt= 0; return(pCrypt); } NTSTATUS nCrypt::Create(class nCrypt *&pCrypt) { NTSTATUS Status= STATUS_SUCCESS; if(!(pCrypt= new nCrypt())) { Status= Warn(STATUS_NO_MEMORY,"nCrypt:Create: NoMem(%d)",sizeof(*pCrypt)); } else if(!NT_SUCCESS(Status= pCrypt->Create2())) { CryptDestroy(pCrypt); } return(Status); } NTSTATUS nCrypt::Create2(void) { NTSTATUS Status= STATUS_SUCCESS; DWORD ByteCt; BCRYPT_KEY_LENGTHS_STRUCT KeyLengths; if(!NT_SUCCESS(Status= BCryptOpenAlgorithmProvider(&hECC,BCRYPT_ECDH_P384_ALGORITHM,0,0))) { Status= Warn(Status,"Crype:Create2: Unable to open ECDH_P384 provider."); } else if(!NT_SUCCESS(Status= BCryptOpenAlgorithmProvider(&hAES,BCRYPT_AES_ALGORITHM,0,0))) { Status= Warn(Status,"Crypt:Create2: Unable to open AES provider."); } else if(!NT_SUCCESS(Status= BCryptGetProperty(hAES,BCRYPT_KEY_LENGTHS,(BYTE*)&KeyLengths,sizeof(KeyLengths),&ByteCt,0))) { Status= Warn(Status,"Crypt:Create2: GetProperty(OBJECT_LENGTH) failed."); } else { //KeyBits= KeyLengths.dwMaxLength; //BUG: Using 256 bits fails during decryption. KeyBits= 192; SymKeySz= KeyBits/8; //Print(PRINT_INFO,"Using AES-%u encrpyption.",SymKeySz*8); //NOTE: Key size is set in BCryptGenerateSymmetricKey() } return(Status); } NTSTATUS nCrypt::Destroy(void) { NTSTATUS Status= STATUS_SUCCESS; delete this; return(Status); } //EOF: CRYPTKM.CPP
DrvEntry.cpp: /*************************************************************************/ /** DrvEntry.c: Windows kernel driver entry point. **/ /** (C)2017 nlited systems inc, Chip Doran **/ /*************************************************************************/ #include "Globals.h" #include "VerID.h" //#pragma message(__FILE__": Optimizer disabled.") //#pragma optimize("",off) DWORD DbgFilter= DBG_INIT|DBG_MEDIA|DBG_TRACE; class IrpTrace *gIrpTrace; class nCrypt *gCrypt; DRIVER_INITIALIZE DriverEntry; static void DriverNtUnload(DRIVER_OBJECT *pDriver); NTSTATUS DriverEntry(DRIVER_OBJECT *pDriver, UNICODE_STRING *RegPath) { NTSTATUS Status= STATUS_SUCCESS; //NOTE: By default, only error messages are printed by Windows. DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"CryptDriver has arrived!\r\n"); DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"CryptDriver version %s %s\r\n",gVerID.BuildStr,gVerID.Builder); DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"DriverEntry=%llX pDriver=%llX\r\n",DriverEntry,pDriver); if(KD_DEBUGGER_ENABLED && !KD_DEBUGGER_NOT_PRESENT) DbgBreakPoint(); pDriver->DriverUnload= DriverNtUnload; if(!NT_SUCCESS(Status= TraceCreate(gIrpTrace,10000))) Warn(Status,"DriverEntry: TraceCreate() failed."); TraceControl(gIrpTrace,TRACE_DEBUG,DBG_TRACE); TraceControl(gIrpTrace,TRACE_WRAP,1); if(!NT_SUCCESS(Status= CryptCreate(gCrypt))) Warn(Status,"DriverEntry: CryptCreate() failed."); if(!NT_SUCCESS(Status= DeviceCtrlCreate(pDriver))) { Warn(STATUS_FAILED_DRIVER_ENTRY,"CryptDriver: CreateCtrlDevice() failed."); } else { // Map all the IRP requests to a common handler. for(UINT n1=0;n1<IRP_MJ_MAXIMUM_FUNCTION;pDriver->MajorFunction[n1++]= DeviceIrp); } return(STATUS_SUCCESS); } static void DriverNtUnload(DRIVER_OBJECT *pDriver) { CryptDestroy(gCrypt); //I need to destroy all my devices. DEVICE_OBJECT *pDev,*pNext; for(pDev= pDriver->DeviceObject;pDev;pDev=pNext) { pNext= pDev->NextDevice; DeviceDestroy(pDriver->DeviceObject); } TraceDestroy(gIrpTrace); DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"CryptDriver has left the building.\r\n"); } //EOF: DRVENTRY.C

SUCCESS! The driver compiles and links. The #pragma comment(lib,"cng.lib") line lets me link in the library without making any changes to the project definition. I like this method of specifying libraries because it keeps the library names close to the code where it is used, rather than buried in the project settings. It is also more portable since the library goes with the source code.

UPDATE: The #pragma doesn't work if the linker options have "Ignore standard libraries" set.

I don't need to migrate everything from Crypt.cpp, I just need the code dealing with binary data streams and key management. The migration process is mostly replacing "int" error codes with NTSTATUS, IsErr() with NT_SUCCESS(), and TXT with NtString.

I finished the rough draft of CryptKM.cpp in about 90 minutes of stupifying transliteration. It appears about 90% of the code should be a straight recompile. The Base64 conversion routines may be missing.

Everything compiles and links except for the two Base64 converter functions. Adding crypto is looking good so far, and it has only been 2 hours.

Crypt Keys

September 2 2017

I wrote my own Base64 converters, it seemed to be the shortest path and only took an hour.

I am hopeful that the core CryptKM functions will just work, especially since I will need to write a lot of support code before I can try using it. Managing the crypt keys is the next task.

The app will need to have a user interface to generate and load keys. Then it will need an internal API to pass the keys to the driver. The driver will then need to add a disk information block to the media file for additional state info.

The first step will be writing a dialog box to generate and load keys. I can use this to run test code as well.

The current version of Crypt was written to use internally generated public and private keys, and does not use user input (passwords). I need to expand the Crypt class to use passwords and symmetric (AES) keys.

KeyFile

CryptDisk will use a "KeyFile", which is a private data structure that is never used outside the Crypt class. The KeyFile contains some identifying information plus the raw symmetric key that use for the actual data encryption. This internal key is a very large (512 byte) purely random key that has no dependence on user input. The KeyFile is itself encrypted with a hash generated from user input (password).

enum KEY_TYPES { KEY_SYM, KEY_PRIVATE, KEY_PUBLIC }; #define KEYDATA_SIGNATURE 0xCD struct KeyData_s { //Container for the raw key data. BYTE Signature; //Must be KEYDATA_SIGNATURE BYTE Version; //Must be KEYDATA_VERSION BYTE Type; //One of KEY_PUBLIC, KEY_PRIVATE, or KEY_SYM. BYTE Reserved1; //Future use WORD KeySz; //Size (bytes) of actual key data. DWORD ServerID; //Server that created this key. DWORD KeyID; //Unique ID for this key. FILETIME Created; //Creation FileTime() FILETIME Expires; //Expiration date BYTE Reserved2[16]; //Reserved for future use. BYTE Data[2]; //Variable length key data };

Creating a new Key:

  1. Allocate a new KEYFILE struct and fill in the info.
  2. Set KeySz and fill the Data[] block with random bytes. This is the actual encryption key.
    KeySz= 512 (4096 bits)
    BCryptGenRandom()
  3. User provides a plain-text password.
    Repeat or truncate the password to 16 characters.
  4. Transform the password to a 128bit (16 byte) SHA256 hash.
    BCryptOpenAlgorithmProvider(&hSHA256,BCRYPT_SHA256_ALGORITHM,0,0); BCryptGetProperty(hSHA256,BCRYPT_OBJECT_LENGTH,(BYTE*)&ObjSz,sizeof(ObjSz),&ByteCt,0); pSHA256= MemAlloc("SHA256",ObjSz); BCryptGetProperty(hSHA256,BCRYPT_HASH_LENGTH,(BYTE*)&HashSz,sizeof(HashSz),&ByteCt,0); pHash= MemAlloc("Hash",HashSz); BCryptCreateHash(hSHA256,&hHash,pSHA256,ObjSz,0,0,0); BCryptHashData(hHash,pPassword,strlen(pPassword),0); BCryptFinishHash(hHash,pHash,HashSz,0); BCryptDestroyHash(hHash); BCryptCloseAlgorithmProvider(hSHA256,0); MemFree(pSHA256);
  5. Generate a symmetric AES key using the password hash as the salt.
    BCryptGenerateSymmetricKey(hAES,&hKey,0,0,pHash,SymKeySz,0); BCryptGetProperty(hKey,BCRYPT_BLOCK_LENGTH,(BYTE*)&BlockLen,sizeof(BlockLen),&ByteCt); hKey now contains the symmetric key.
  6. Encrypt BlockLen random bytes to a byte stream.
  7. Encrypt the KEYFILE to the byte stream.
    BCryptEncrypt(hKey,pSrc,SrcSz,0,pIV,BlockLen,0,0,&ByteCt,BFlags);
  8. Encode byte stream to Base64 text.
  9. Save the encrypted Base64 text to a key file.

Using an Existing Key:

  1. The user enters a plain-text password.
    Repeat or truncate the password to 16 characters.
  2. Transform the password to a 128bit (16 byte) hash.
  3. Generate a symmetric AES KeyFile key from the password hash.
  4. Decode the KEYFILE from Base64 text to bytes.
  5. Decrypt the first BlockLen bytes and discard.
  6. Decrypt the KEYFILE bytes using the KeyFile key.
  7. Verify the integrity of the KEYFILE signature.

Keys in the Kernel

The kernel-mode CryptKM does not need to know anything about KeyFiles, it just needs the raw KeyFile.Data[] bytes. The app passes the raw key data to the driver via a CRYPTDISK_SET_KEY ioctl. (NOTE: The key data should be obfuscated (ie trivial xor encryption) to prevent it being visible to debuggers and monitors.)

CryptKM generates the AES session key from the key bytes. When a volume is mounted, CryptDriver decrypts the volume header using the session key and validates the volume signature. If the signature is not valid, the mount operation fails.

The driver retains the session key until the volume is unmounted.

Data Encryption

Bulk data transcryption uses the Session key, which was generated from the KEYFILE data. The Session key does not change.

The disk device should always read and write full sectors. I rely on this to treat each sector as a distinct byte stream.

I want the encrypted sectors to always appear random, even when the cleartext for sectors may be identical. I can do this by feeding a block (BlockLen, 16 bytes) of random data into the encryption engine before each sector. Then I discard the first 16 bytes decrypted from each sector.



WebV7 (C)2018 nlited | Rendered by tikope in 37.192ms | 18.117.75.6