// =========================================================================
// Cypher driver cleardown function
// driverInfo - The structure to be cleared down
NTSTATUS
ImpCypherDriverExtDetailsCleardown_v3(    
    IN OUT CYPHER_DRIVER_INFO_v3* driverInfo
)
{
    NTSTATUS status = STATUS_SUCCESS;

    DEBUGOUTCYPHERIMPL(DEBUGLEV_ENTER, (TEXT("ImpCypherDriverExtDetailsCleardown_v3\n")));

    if (driverInfo->CypherDetails != NULL)
        {
        FREEOTFE_FREE(driverInfo->CypherDetails);
        }

    driverInfo->CypherDetails = NULL;
    driverInfo->CypherCount = 0;

    DEBUGOUTCYPHERIMPL(DEBUGLEV_EXIT, (TEXT("ImpCypherDriverExtDetailsCleardown_v3\n")));

    return status;
}
// =========================================================================
// This is the PBKDF2 PRF function "F"  
// The PRF used is HMAC
NTSTATUS
PBKDF2_F(
    IN      PDataHashFn FnHash,
    IN      GUID HashGUID,
    IN      HASH HashDetails,
    IN      unsigned int PLength,  // In bits
    IN      unsigned char* P,
    IN      unsigned int SLength,  // In bits
    IN      unsigned char* S,
    IN      unsigned int c,
    IN      unsigned int i,
    OUT     unsigned char* T_
)
{
    NTSTATUS status;
    unsigned char* U_;
    unsigned char* tmpU_Buffer;
    unsigned char* saltAndCount;
    unsigned int hLen;  // In *bytes*
    unsigned int hLenBits;  // In *Bits*
    unsigned int actualHLenBits;  // In *bits*
    unsigned int saltAndCountSizeBytes;  // In *bytes*
    unsigned int j, k;


    DEBUGOUTKDFDRV(DEBUGLEV_ENTER, ("PBKDF2_F\n"));


    status = STATUS_SUCCESS;

    U_ = NULL;
    tmpU_Buffer = NULL;
    saltAndCount = NULL;


    // From PKCS#5:

    // function F is defined as the exclusive-or sum of the
    // first c iterates of the underlying pseudorandom function PRF
    // applied to the password P and the concatenation of the salt S
    // and the block index i:
    //
    //           F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
    //
    // where
    //           U_1 = PRF (P, S || INT (i)) ,
    //           U_2 = PRF (P, U_1) ,
    //           ...
    //           U_c = PRF (P, U_{c-1}) .
    //
    // Here, INT (i) is a four-octet encoding of the integer i, most
    // significant octet first.


    DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("Iterations requested: %d\n", c));

    // This is PBKDF2 based on HMAC - the HMAC function returns the same
    // number of bytes as is in the hash it uses
    hLen = (HashDetails.Length / 8);
    hLenBits = HashDetails.Length;

    // Convert bits to bytes, and add 4 (specified in PKCS #5) for the "i"
    saltAndCountSizeBytes = ((SLength / 8) + 4);
    saltAndCount = FREEOTFE_MEMALLOC(saltAndCountSizeBytes);
    FREEOTFE_MEMCPY(
                    saltAndCount,
                    S,
                    (SLength / 8)
                    );
    // Concatenate the salt
    // Note: *Bitwise* AND :)
    saltAndCount[ (SLength / 8)   ] = ((i & 0xFF000000) / 0x01000000);
    saltAndCount[((SLength / 8)+1)] = ((i & 0x00FF0000) / 0x00010000);
    saltAndCount[((SLength / 8)+2)] = ((i & 0x0000FF00) / 0x00000100);
    saltAndCount[((SLength / 8)+3)] = ((i & 0x000000FF) / 0x00000001);

    U_ = FREEOTFE_MEMALLOC(hLen);
    tmpU_Buffer = FREEOTFE_MEMALLOC(hLen);

    // Process U_1
    if (NT_SUCCESS(status))
        {
        actualHLenBits = hLenBits;
        DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("First HMAC: SLength = %d\n", SLength));
        DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("First HMAC: actualHLenBits = %d\n", actualHLenBits));
        if (!(NT_SUCCESS(ImplMACHMAC(
                                    FnHash,
                                    HashGUID,
                                    HashDetails,
                                    -1,  // Retrieve full HMAC
                                    PLength,
                                    P,
                                    (saltAndCountSizeBytes * 8), // In *bits*
                                    saltAndCount,

                                    &actualHLenBits,  // In bits
                                    U_
                                ))))
            {
            DEBUGOUTKDFDRV(DEBUGLEV_ERROR, ("First call to HMAC function \"F\" failed.\n"));
            status = STATUS_INTERNAL_ERROR;
            }
        else
            {
            // Sanity check
            if (actualHLenBits != hLenBits)
                {
                DEBUGOUTKDFDRV(DEBUGLEV_ERROR, ("HMAC function didn't return expected number of bits?!\n"));
                status = STATUS_INTERNAL_ERROR;
                }
            else
                {
                // Copy to output buffer - note that this will be XORd if
                // there's more c>1
                FREEOTFE_MEMCPY(
                                T_,
                                U_,
                                (actualHLenBits / 8)
                                );
                }
            }
        }


    // Process subsequent U_n
    if (NT_SUCCESS(status))
        {
        for(j = 2; j <= c; j++)
            {
            actualHLenBits = hLenBits;
            DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("Subsequent HMAC: actualHLenBits = %d\n", actualHLenBits));
            if (!(NT_SUCCESS(ImplMACHMAC(
                                        FnHash,
                                        HashGUID,
                                        HashDetails,
                                        -1,  // Retrieve full HMAC
                                        PLength,
                                        P,
                                        hLenBits,  // In bits
                                        U_,

                                        &actualHLenBits,  // In bits
                                        tmpU_Buffer
                                    ))))
                {
                DEBUGOUTKDFDRV(DEBUGLEV_ERROR, ("Call to HMAC function \"F\" failed.\n"));
                status = STATUS_INTERNAL_ERROR;
                break;
                }
            else
                {
                // Move from temp buffer to U_ buffer
                DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("Copying (%d)\n", hLen));
                FREEOTFE_MEMCPY(
                            U_,
                            tmpU_Buffer,
                            hLen
                            );

                // XOR with previous iteration
                DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("XORing...\n"));
                for(k = 0; k < hLen; k++)
                    {
                    T_[k] ^= U_[k];
                    }
                DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("OK.\n"));
                }
            }
        }


    // Note: No need to set the output "T_" explicitly, as this is done during
    //       processing


    if (tmpU_Buffer != NULL)
        {
        SecZeroMemory(tmpU_Buffer, hLen);
        FREEOTFE_FREE(tmpU_Buffer);
        }
    if (U_ != NULL)
        {
        SecZeroMemory(U_, hLen);
        FREEOTFE_FREE(U_);
        }
    if (saltAndCount != NULL)
        {
        SecZeroMemory(saltAndCount, saltAndCountSizeBytes);
        FREEOTFE_FREE(saltAndCount);
        }



    DEBUGOUTKDFDRV(DEBUGLEV_EXIT, ("PBKDF2_F\n"));

    return status;
}
// =========================================================================
// Generate PBKDF2 key based on HMAC
NTSTATUS
ImplKDFPBKDF2(
    IN      PDataHashFn FnHash,
    IN      GUID HashGUID,
    IN      HASH HashDetails,
    IN      unsigned int PLength,  // In bits
    IN      unsigned char* P,
    IN      unsigned int SLength,  // In bits
    IN      unsigned char* S,
    IN      unsigned int c,  // Iterations
    IN      int dkLenBits,  // In *bits*

    IN OUT  unsigned int* DerivedKeyLength,  // In bits
    OUT     unsigned char* DerivedKey
)
{
    NTSTATUS status;

    unsigned int i;

    unsigned int l;
//    unsigned int r;  // In *bytes*
    unsigned int dkLen;  // In *bytes*
    unsigned int hLen;  // In *bytes*

    unsigned char* T_;
    unsigned int T_SizeBytes; // In *bytes*


    DEBUGOUTKDFDRV(DEBUGLEV_ENTER, ("ImplKDFPBKDF2\n"));

    status = STATUS_SUCCESS;

    T_ = NULL;
    T_SizeBytes = 0;


    // Sanity check
    // This should be picked up by the caller; this is belt & braces
    if (
        (HashDetails.BlockSize <= 0) ||
        (HashDetails.Length <= 0)
       )
        {
        DEBUGOUTKDFDRV(DEBUGLEV_ERROR, ("PBKDF2 implementation called with a hash that has an undefined/zero length/blocksize\n"));
        status = STATUS_INTERNAL_ERROR;
        }
    else
        {
        // This is PBKDF2 based on HMAC - the HMAC function returns the same
        // number of bytes as is in the hash it uses
        hLen = (HashDetails.Length / 8);
        }


    if (dkLenBits < 0)
        {
        dkLenBits = DEFAULT_PBKDF2_KEY_SIZE;
        }
    dkLen = (dkLenBits / 8);
    DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("dkLenBits = %d bits\n", dkLenBits));
    DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("dkLen = %d bytes\n", dkLen));


    // Sanity check
    // This should be picked up by the caller; this is belt & braces
    if ((unsigned int)dkLenBits > *DerivedKeyLength)
        {
        // The output buffer can store the requested number of bits, right?
        // Note that dkLenBits can be -ve to indicate the full key length is to be
        // returned
        DEBUGOUTKDFDRV(DEBUGLEV_ERROR, ("Requested number of bits is larger than supplied buffer\n"));
        status = STATUS_BUFFER_TOO_SMALL;
        }


    // Step 1
    // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and
    //    stop.

    DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("PBKDF2 Step 1: n/a; skipped...\n"));
    // (Step skipped - because dkLen is an integer, it can never be more
    // than 2^(31-1) - far less than (2^32 - 1) * hLen)


    // Step 2
    // 2. Let l be the number of hLen-octet blocks in the derived key,
    //    rounding up, and let r be the number of octets in the last
    //    block:
    //
    //              l = CEIL (dkLen / hLen) ,
    //              r = dkLen - (l - 1) * hLen .
    //
    //    Here, CEIL (x) is the "ceiling" function, i.e. the smallest
    //    integer greater than, or equal to, x.
    if (NT_SUCCESS(status))
        {
        DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("PBKDF2 Step 2: Executing...\n"));
        // Note: div always rounds down towards zero
        l = (dkLen / hLen);
        // Because div always rounds down towards zero, we may need to increment it
        // by one
        if (dkLen > (l * hLen))
            {
            l++;
            }


        DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("dkLen = %d bytes\n", dkLen));
        DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("hLen = %d bytes\n", hLen));
        DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("l = %d\n", l));
//        r = dkLen - ((l - 1) * hLen);
//        DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("r = %d bytes\n", r));
        }




    // Step 3
    // 3. For each block of the derived key apply the function F
    if (NT_SUCCESS(status))
        {
        DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("PBKDF2 Step 3: Executing...\n"));
        T_SizeBytes = (hLen * l);
        T_ = FREEOTFE_MEMALLOC(T_SizeBytes);

        for(i = 1; i<=l; i++)
            {
            DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("Loop: i = %d\n", i));
            if (!(NT_SUCCESS(PBKDF2_F(
                                    FnHash,
                                    HashGUID,
                                    HashDetails,

                                    PLength,
                                    P,
                                    SLength,
                                    S,
                                    c,
                                    i,

                                    // This is correct (-1), as our loop starts
                                    // from 1
                                    &T_[((i-1) * hLen)]
                                ))))
                {
                DEBUGOUTKDFDRV(DEBUGLEV_ERROR, ("Call to PBKDF2 function \"F\" failed.\n"));
                status = STATUS_INTERNAL_ERROR;
                break;
                }
            }
        }



    // Step 4
    // 4. Concatenate the blocks and extract the first dkLen octets to
    //    produce a derived key DK:
    //
    //              DK = T_1 || T_2 ||  ...  || T_l<0..r-1>
    if (NT_SUCCESS(status))
        {
        DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("PBKDF2 Step 4: Executing...\n"));
        FREEOTFE_MEMCPY(
                      DerivedKey,
                      T_,
                      (dkLenBits / 8)
                     );
        }


    // Step 5
    // 5. Output the derived key DK.
    if (NT_SUCCESS(status))
        {
        DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("PBKDF2 Step 5: Executing...\n"));
        *DerivedKeyLength = dkLenBits;
        }


    // Cleanup...
    DEBUGOUTKDFDRV(DEBUGLEV_INFO, ("Freeing off any used buffers...\n"));

    if (T_ != NULL)
        {
        SecZeroMemory(T_, T_SizeBytes);
        FREEOTFE_FREE(T_);
        }

    DEBUGOUTKDFDRV(DEBUGLEV_EXIT, ("ImplKDFPBKDF2\n"));

    return status;
}
// =========================================================================
// Decryption function
// Note: PlaintextLength must be set to the size of the PlaintextData buffer on
//       entry; on exit, this will be set to the size of the buffer used.
NTSTATUS
ImpCypherDecryptSectorData(
    IN      GUID* CypherGUID,
    IN      LARGE_INTEGER SectorID,  // Indexed from zero
    IN      int SectorSize, // In bytes
    IN      int KeyLength,  // In bits
    IN      FREEOTFEBYTE* Key,
    IN      char* KeyASCII,  // ASCII representation of "Key"
    IN      int IVLength,  // In bits
    IN      FREEOTFEBYTE* IV,
    IN      int CyphertextLength,  // In bytes
    IN      FREEOTFEBYTE* CyphertextData,
    OUT     FREEOTFEBYTE* PlaintextData
)
{
    NTSTATUS status = STATUS_SUCCESS;
    // libtomcrypt can't handle NULL IVs in CBC mode - it ASSERTs that IV != NULL
    char ltcNullIV[FREEOTFE_MAX_CYPHER_BLOCKSIZE];
    int cipher;
    symmetric_CBC *cbc;
    int errnum;

    DEBUGOUTCYPHERIMPL(DEBUGLEV_ENTER, (TEXT("ImpCypherDecryptData\n")));

	if (!(
		  (IsEqualGUID(&CIPHER_GUID_CAST5, CypherGUID))
		 ))
		{
		DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unsupported cipher GUID passed in.\n")));
        status = STATUS_INVALID_PARAMETER;
        }

    // libtomcrypt can't handle NULL IVs in CBC mode - it ASSERTs that IV != NULL
    if ( (IVLength == 0) || (IV == NULL) )
        {
        FREEOTFE_MEMZERO(&ltcNullIV, sizeof(ltcNullIV));
        IV = (char*)&ltcNullIV;
        }

    cbc = FREEOTFE_MEMALLOC(sizeof(symmetric_CBC));    
    FREEOTFE_MEMZERO(cbc, sizeof(symmetric_CBC));

  	if NT_SUCCESS(status) 
    		{
	    	status = InitLTCCypher(&cipher);
  		  }

    if NT_SUCCESS(status)
        {
        // Start a CBC session
        if ((errnum = cbc_start(
                                cipher, 
                                IV, 
                                Key, 
                                (KeyLength/8), 
                                0, 
                                cbc
                               )) != CRYPT_OK)
            {
            status = STATUS_UNSUCCESSFUL;
            DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to start CBC session (errnum: %d)\n"), errnum));
            }
        else
            {
            if ((errnum = cbc_decrypt(
                                      CyphertextData, 
                                      PlaintextData, 
                                      CyphertextLength, 
                                      cbc
                                     )) != CRYPT_OK)
                {
                DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to encrypt/decrypt block (errnum: %d)\n"), errnum));
                status = STATUS_UNSUCCESSFUL;
                }

            cbc_done(cbc);
            }
        }

    SecZeroMemory(cbc, sizeof(symmetric_CBC));
    FREEOTFE_FREE(cbc);

    DEBUGOUTCYPHERIMPL(DEBUGLEV_EXIT, (TEXT("ImpCypherDecryptData\n")));

    return status;
}
// =========================================================================
// Generate HMAC
NTSTATUS
ImplMACHMAC(
    IN      PDataHashFn FnHash,
    IN      GUID HashGUID,
    IN      HASH HashDetails,
    IN      int tBits,  // In *bits*
    IN      unsigned int KLength,  // In bits
    IN      unsigned char* K,
    IN      unsigned int textLength,  // In bits
    IN      unsigned char* text,

    IN OUT  unsigned int* MACLength,  // In bits
    OUT     unsigned char* MAC
)
{
    NTSTATUS status;

    unsigned int KLengthBytes;  // In *bytes*

    unsigned int B;  // In *bytes*
    unsigned int i;
    unsigned char* Kzero;
    
    unsigned char* step2TmpBuf;
    unsigned char* step4OutBuf;
    unsigned char* step5OutBuf;
    unsigned char* step6OutBuf;
    unsigned char* step7OutBuf;
    unsigned char* step8OutBuf;
    unsigned char* step9OutBuf;

    unsigned int step2TmpBufSize;  // In *bytes*
    unsigned int step5OutBufSize;  // In *bytes*
    unsigned int step6OutBufSize;  // In *bytes*
    unsigned int step8OutBufSize;  // In *bytes*
    unsigned int step9OutBufSize;  // In *bytes*

    unsigned int step6ValidBits;
    unsigned int step9ValidBits;

    unsigned int hashValidBits;
    unsigned int hashUseLenBytes;

    unsigned int outputBits;


    DEBUGOUTMACDRV(DEBUGLEV_ENTER, ("ImplMACHMAC\n"));

    status = STATUS_SUCCESS;

    Kzero = NULL;

    step4OutBuf = NULL;
    step5OutBuf = NULL;
    step6OutBuf = NULL;
    step7OutBuf = NULL;
    step8OutBuf = NULL;
    step9OutBuf = NULL;

    // Sanity checks
    // This should be picked up by the caller; this is belt & braces
    if (
        (HashDetails.BlockSize <= 0) ||
        (HashDetails.Length <= 0)
       )
        {
        DEBUGOUTMACDRV(DEBUGLEV_ERROR, ("HMAC implementation called with a hash that has an undefined blocksize/output\n"));
        status = STATUS_INTERNAL_ERROR;
        }


    B = (HashDetails.BlockSize / 8);
    KLengthBytes = (KLength / 8);


    // Compute the HMAC...

    // ---------
    // FIPS-198 Steps 1-3
    // i.e. Set Kzero to be a "B" length string
    if (NT_SUCCESS(status))
        {
        Kzero = FREEOTFE_MEMALLOC(B);    

        if (KLengthBytes == B)
            {
            // Step 1
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 1: Executing...\n"));
            // Set K0 = K
            FREEOTFE_MEMCPY(
                            Kzero,
                            K,
                            B
                           );
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 2: n/a; skipped\n"));
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 3: n/a; skipped\n"));
            }
        else if (KLengthBytes > B)
            {
            // Step 2
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 1: n/a; skipped\n"));
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 2: Executing...\n"));
            // Hash K to get a hashlength string, then pad out to B bytes
            // with NULLs
            step2TmpBufSize = (HashDetails.Length / 8);   // Divide by 8 to get bytes from bits
            hashValidBits = HashDetails.Length;  // In *bits*
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("Allocating tmp buffer (%d bytes)...\n", step2TmpBufSize));
            step2TmpBuf = FREEOTFE_MEMALLOC(step2TmpBufSize);

            status = FnHash(
					        &HashGUID,
					        KLength,
					        K,
					        &hashValidBits,
					        step2TmpBuf
                        );
            if (!(NT_SUCCESS(status)))
                {
                DEBUGOUTMACDRV(DEBUGLEV_ERROR, ("HMAC Step 2: Call to hash driver failed\n"));
                status = STATUS_INTERNAL_ERROR;
                }
            else
                {
                hashUseLenBytes = min(B, (hashValidBits / 8));
                DEBUGOUTMACDRV(DEBUGLEV_INFO, ("Hash output: %d bits (%d bytes)...\n", hashValidBits, (hashValidBits / 8)));
                DEBUGOUTMACDRV(DEBUGLEV_INFO, ("Using first %d bytes...\n", hashUseLenBytes));
                // Copy the hash value to K0
                FREEOTFE_MEMCPY(
                    Kzero,
                    step2TmpBuf,
                    hashUseLenBytes
                    );
                DEBUGOUTMACDRV(DEBUGLEV_INFO, ("Step 2 Kzero: Padding end with NULLs (%d bytes of nulls)...\n", (B - hashUseLenBytes)));
                // Pad K0 with NULLs
                SecZeroMemory(&Kzero[hashUseLenBytes], (B - hashUseLenBytes));
                }            

            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("Zeroing and freeing hash buffer\n"));
            SecZeroMemory(step2TmpBuf, step2TmpBufSize);
            FREEOTFE_FREE(step2TmpBuf);
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 3: n/a; skipped\n"));
            }
        else
            {
            // Step 3
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 1: n/a; skipped\n"));
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 2: n/a; skipped\n"));
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 3: Executing...\n"));
            FREEOTFE_MEMCPY(
                Kzero,
                K,
                KLengthBytes
                );
            DEBUGOUTMACDRV(DEBUGLEV_INFO, ("Step 3 Kzero: Padding end with NULLs (%d bytes of nulls)...\n", (B - KLengthBytes)));
            // Pad K0 with NULLs
            SecZeroMemory(&Kzero[KLengthBytes], (B - KLengthBytes));
            }
        }

    // ---------
    // FIPS-198 Step 4
    // XOR K0 with ipad (0x36)
    if (NT_SUCCESS(status))
        {
        DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 4: Executing...\n"));
        step4OutBuf = FREEOTFE_MEMALLOC(B);
        for (i = 0; i < B; i++)
            {
            step4OutBuf[i] = Kzero[i] ^ 0x36;
            }
        }


    // ---------
    // FIPS-198 Step 5
    // Append input text onto the end of the output from step 4
    if (NT_SUCCESS(status))
        {
        DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 5: Executing...\n"));
        step5OutBufSize = (B + (textLength / 8));
        step5OutBuf = FREEOTFE_MEMALLOC(step5OutBufSize);
        FREEOTFE_MEMCPY(
            step5OutBuf,
            step4OutBuf,
            B
            );
        FREEOTFE_MEMCPY(
            &step5OutBuf[B],
            text,
            (textLength / 8)
            );
        }

    // ---------
    // FIPS-198 Step 6
    // Apply H to the output from step 5
    if (NT_SUCCESS(status))
        {
        DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 6: Executing...\n"));
        step6ValidBits = HashDetails.Length;
        step6OutBufSize = (step6ValidBits / 8);
        step6OutBuf = FREEOTFE_MEMALLOC(step6OutBufSize);
        status = FnHash(
					    &HashGUID,
					    (step5OutBufSize * 8),
					    step5OutBuf,
					    &step6ValidBits,
					    step6OutBuf
                    );
        }

    // ---------
    // FIPS-198 Step 7
    // XOR K0 with opad (0x5C)
    if (NT_SUCCESS(status))
        {
        DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 7: Executing...\n"));
        step7OutBuf = FREEOTFE_MEMALLOC(B);
        for (i = 0; i < B; i++)
            {
            step7OutBuf[i] = Kzero[i] ^ 0x5C;
            }
        }

    // ---------
    // FIPS-198 Step 8
    // Append the result from step 6 to step 7
    if (NT_SUCCESS(status))
        {
        DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 8: Executing...\n"));
        step8OutBufSize = (B + (step6ValidBits / 8));
        step8OutBuf = FREEOTFE_MEMALLOC(step8OutBufSize);
        FREEOTFE_MEMCPY(
            step8OutBuf,
            step7OutBuf,
            B
            );
        FREEOTFE_MEMCPY(
            &step8OutBuf[B],
            step6OutBuf,
            (step6ValidBits / 8)
            );
        }

    // ---------
    // FIPS-198 Step 9
    // Apply H to the output from step 8
    if (NT_SUCCESS(status))
        {
        DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 9: Executing...\n"));
        step9ValidBits = HashDetails.Length;
        step9OutBufSize = (step9ValidBits / 8);
        step9OutBuf = FREEOTFE_MEMALLOC(step9OutBufSize);
        status = FnHash(
					    &HashGUID,
					    (step8OutBufSize * 8),
					    step8OutBuf,
					    &step9ValidBits,
					    step9OutBuf
                    );
        }


    // ---------
    // FIPS-198 Step 10
    // Select the leftmost t bytes of output from step 9
    if (NT_SUCCESS(status))
        {
        DEBUGOUTMACDRV(DEBUGLEV_INFO, ("HMAC Step 10: Executing...\n"));
        // Truncate, if necessary, to tBits of data
        if (tBits >= 0)
            {
            outputBits = tBits;
            }
        else
            {
            outputBits = step9ValidBits;
            }

        // Sanity check on the buffer...
        if (*MACLength < outputBits)
            {
            DEBUGOUTMACDRV(DEBUGLEV_ERROR, ("Buffer too small; supplied %d bits, need %d bits\n", MACLength, outputBits));
            status = STATUS_BUFFER_TOO_SMALL;
            }
        else
            {
            // Copy the appropriate number of bits to the output buffer
            FREEOTFE_MEMCPY(
                MAC,
                step9OutBuf,
                (outputBits / 8)  // Convert to bytes
                );
            *MACLength = outputBits;
            }

        }


    // Cleanup...
    DEBUGOUTMACDRV(DEBUGLEV_INFO, ("Freeing off any used buffers...\n"));

    if (step9OutBuf != NULL)
        {
        SecZeroMemory(step9OutBuf, step9OutBufSize);
        FREEOTFE_FREE(step9OutBuf);
        }
    if (step8OutBuf != NULL)
        {
        SecZeroMemory(step8OutBuf, step8OutBufSize);
        FREEOTFE_FREE(step8OutBuf);
        }
    if (step7OutBuf != NULL)
        {
        SecZeroMemory(step7OutBuf, B);
        FREEOTFE_FREE(step7OutBuf);
        }
    if (step6OutBuf != NULL)
        {
        SecZeroMemory(step6OutBuf, step6OutBufSize);
        FREEOTFE_FREE(step6OutBuf);
        }
    if (step5OutBuf != NULL)
        {
        SecZeroMemory(step5OutBuf, step5OutBufSize);
        FREEOTFE_FREE(step5OutBuf);
        }
    if (step4OutBuf != NULL)
        {
        SecZeroMemory(step4OutBuf, B);
        FREEOTFE_FREE(step4OutBuf);
        }
    if (Kzero != NULL)
        {
        SecZeroMemory(Kzero, B);
        FREEOTFE_FREE(Kzero);
        }
    

    DEBUGOUTMACDRV(DEBUGLEV_EXIT, ("ImplMACHMAC\n"));

    return status;
}
// =========================================================================
// Generate a key based on the hash of the salted password
NTSTATUS
ImplKDFHashSaltedPassword(
    IN      PDataHashFn FnHash,
    IN      GUID HashGUID,
    IN      unsigned int Iterations,
    IN      unsigned int PasswordLength,  // In bits
    IN      unsigned char* Password,
    IN      unsigned int SaltLength,  // In bits
    IN      unsigned char* Salt,

    IN OUT  unsigned int* DerivedKeyLength,  // In bits
    OUT     unsigned char* DerivedKey
)
{
    NTSTATUS status;
    unsigned char* tmpBuffer;
    unsigned int tmpBufferSizeBytes;
    unsigned char* tmpIterateBuffer;
    unsigned int tmpIterateBufferSizeBytes;
    unsigned int i;

    DEBUGOUTKDFDRV(DEBUGLEV_ENTER, ("ImplKDFHashSaltedPassword\n"));


    tmpBufferSizeBytes = ((PasswordLength + SaltLength) / 8);
    tmpBuffer = FREEOTFE_MEMALLOC(tmpBufferSizeBytes);    

    // Add the salt onto the end of the password before hashing the combined
    // password/salt
    FREEOTFE_MEMCPY(
        tmpBuffer,
        Password,
        (PasswordLength / 8)
        );
    FREEOTFE_MEMCPY(
        &tmpBuffer[(PasswordLength / 8)],
        Salt,
        (SaltLength / 8)
        );

    status = FnHash(
					&HashGUID,
					(tmpBufferSizeBytes * 8),  // Get bits from bytes
					tmpBuffer,
					DerivedKeyLength,
					DerivedKey
                   );
    if (!(NT_SUCCESS(status)))
        {
        DEBUGOUTKDFDRV(DEBUGLEV_ERROR, ("First KDF call to hash driver failed\n"));
        }
    else
        {
        // Iterate, if needed...
        if (Iterations >= 2)
            {
            tmpIterateBufferSizeBytes = (*DerivedKeyLength / 8);
            tmpIterateBuffer = FREEOTFE_MEMALLOC(tmpIterateBufferSizeBytes);
            for (i = 2; i <= Iterations; i++)
                {
                // Copy to temp buffer...
                FREEOTFE_MEMCPY(
                    tmpIterateBuffer,
                    DerivedKey,
                    tmpIterateBufferSizeBytes
                    );

                // Only pass a buffer that we can copy back into
                // Note: This value should never be bigger than the original
                //       value passed in, otherwise the original hash would
                //       fail
                *DerivedKeyLength = (tmpIterateBufferSizeBytes * 8);

                // Hash temp buffer...
                status = FnHash(
					            &HashGUID,
					            (tmpIterateBufferSizeBytes * 8),  // Get bits from bytes
					            tmpIterateBuffer,
					            DerivedKeyLength,
					            DerivedKey
                            );

                if (!(NT_SUCCESS(status)))
                    {
                    DEBUGOUTKDFDRV(DEBUGLEV_ERROR, ("Subsequent KDF call to hash driver failed\n"));
                    break;
                    }

                }

            SecZeroMemory(tmpIterateBuffer, tmpIterateBufferSizeBytes);
            FREEOTFE_FREE(tmpIterateBuffer);
            }
        }



    // Note: No need to explicitly set *MACLength as FnHash does this for us

    SecZeroMemory(tmpBuffer, tmpBufferSizeBytes);
    FREEOTFE_FREE(tmpBuffer);


    DEBUGOUTKDFDRV(DEBUGLEV_EXIT, ("ImplKDFHashSaltedPassword\n"));

    return status;
}
// =========================================================================
// Encrypt/Decrypt function
// Note: PlaintextLength must be set to the size of the PlaintextData buffer on
//       entry; on exit, this will be set to the size of the buffer used.
NTSTATUS
ImpCypherCryptData(
    IN      GUID* CypherGUID,
    IN      LARGE_INTEGER SectorID,
    IN      int SectorSize, // In bytes
    IN      int KeyLength,  // In bits
    IN      FREEOTFEBYTE* Key,
    IN      char* KeyASCII,  // ASCII representation of "Key"
    IN      int IVLength,  // In bits
    IN      FREEOTFEBYTE* IV,
    IN      BOOLEAN encryptNotDecrypt,  // TRUE = encrypt; FALSE = decrypt
    IN      int InLength,  // In bytes
    IN      FREEOTFEBYTE* InData,
    OUT     FREEOTFEBYTE* OutData
)
{
    NTSTATUS status = STATUS_SUCCESS;
    // libtomcrypt can't handle NULL IVs in CBC mode - it ASSERTs that IV != NULL
    char ltcNullIV[FREEOTFE_MAX_CYPHER_BLOCKSIZE];
    int cipher;
    symmetric_CBC *cbc;
    symmetric_LRW *lrw;
    symmetric_xts *xts;
    int errnum;
    CYPHER_MODE mode;
    int keySizeUnderlying;
    LARGE_INTEGER blockID64;
    INTEGER_128 blockID128;

    DEBUGOUTCYPHERIMPL(DEBUGLEV_ENTER, (TEXT("ImpCypherDecryptData\n")));

    status = DetermineCypherDetails(
                                    CypherGUID,
                                    &keySizeUnderlying,
                                    &mode
                                   );

    // libtomcrypt can't handle NULL IVs in CBC mode - it ASSERTs that IV != NULL
    if ( (IVLength == 0) || (IV == NULL) )
        {
        FREEOTFE_MEMZERO(&ltcNullIV, sizeof(ltcNullIV));
        IV = (char*)&ltcNullIV;
        }

    // Sanity check on key supplied
    if NT_SUCCESS(status) 
        {
        switch (mode)
            {
            case CYPHER_MODE_CBC:
                {
                if (KeyLength != keySizeUnderlying)
                    {
                    status = STATUS_INVALID_PARAMETER;
                    }

                break;
                }

            case CYPHER_MODE_XTS:
                {
                if (KeyLength != (2 * keySizeUnderlying))
                    {
                    status = STATUS_INVALID_PARAMETER;
                    }

                break;
                }

            case CYPHER_MODE_LRW:
                {
                if (KeyLength != (keySizeUnderlying + (twofish_desc.block_length * 8)))
                    {
                    status = STATUS_INVALID_PARAMETER;
                    }

                break;
                }

            }
        }

    if NT_SUCCESS(status) 
        {
        status = InitLTCCypher(&cipher);
        }

    if NT_SUCCESS(status)
        {
        switch (mode)
            {
            case CYPHER_MODE_CBC:
                {
                cbc = FREEOTFE_MEMALLOC(sizeof(symmetric_CBC));    
                FREEOTFE_MEMZERO(cbc, sizeof(symmetric_CBC));

                // Start a CBC session
                if ((errnum = cbc_start(
                                        cipher, 
                                        IV, 
                                        Key, 
                                        (keySizeUnderlying/8), 
                                        0, 
                                        cbc
                                       )) != CRYPT_OK)
                    {
                    status = STATUS_UNSUCCESSFUL;
                    DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to start CBC session (errnum: %d)\n"), errnum));
                    }
                else
                    {
                    if (encryptNotDecrypt)
                        {
                        if ((errnum = cbc_encrypt(
                                                  InData, 
                                                  OutData, 
                                                  InLength, 
                                                  cbc
                                                 )) != CRYPT_OK)
                            {
                            DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to encrypt block (errnum: %d)\n"), errnum));
                            status = STATUS_UNSUCCESSFUL;
                            } 
                        }
                    else
                        {
                        if ((errnum = cbc_decrypt(
                                              InData, 
                                              OutData, 
                                              InLength, 
                                              cbc
                                             )) != CRYPT_OK)
                            {
                            DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to decrypt block (errnum: %d)\n"), errnum));
                            status = STATUS_UNSUCCESSFUL;
                            } 
                        }

                    cbc_done(cbc);
                    }

                SecZeroMemory(cbc, sizeof(symmetric_CBC));
                FREEOTFE_FREE(cbc);

                break;
                }

            case CYPHER_MODE_LRW:
                {
                lrw = FREEOTFE_MEMALLOC(sizeof(symmetric_LRW));    
                FREEOTFE_MEMZERO(lrw, sizeof(symmetric_LRW));

                // Generate index in correct format
                // LRW uses:
                //   *) The block index (i.e. the number of 128 bit blocks)
                //   *) The first block has block index 1 - not 0!
                //   *) Bigendian format
                // Note: LTC increments this itself as it processes each block
                SectorIDToBlockIdx_64Bit(SectorID, SectorSize, &blockID64);
                LARGE_INTEGER__To__INTEGER_128_BigEndian(
                                                    blockID64,
                                                    blockID128
                                                   );
                IV = blockID128;

                // Start a LRW session
                if ((errnum = lrw_start(
                                        cipher, 
                                        IV, 
                                        Key, 
                                        (keySizeUnderlying/8), 
                                        // 128 bits tweak key begins after the
                                        // cypher key
                                        (Key + (keySizeUnderlying/8)),
                                        0, 
                                        lrw
                                       )) != CRYPT_OK)
                    {
                    status = STATUS_UNSUCCESSFUL;
                    DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to start LRW session (errnum: %d)\n"), errnum));
                    }
                else
                    {
                    if (encryptNotDecrypt)
                        {
                        if ((errnum = lrw_encrypt(
                                                  InData, 
                                                  OutData, 
                                                  InLength, 
                                                  lrw
                                                 )) != CRYPT_OK)
                            {
                            DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to encrypt block (errnum: %d)\n"), errnum));
                            status = STATUS_UNSUCCESSFUL;
                            } 
                        }
                    else 
                        {
                            if ((errnum = lrw_decrypt(
                                                  InData, 
                                                  OutData, 
                                                  InLength, 
                                                  lrw
                                                 )) != CRYPT_OK)
                            {
                            DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to decrypt block (errnum: %d)\n"), errnum));
                            status = STATUS_UNSUCCESSFUL;
                            } 
                        }

                    lrw_done(lrw);
                    }

                SecZeroMemory(lrw, sizeof(symmetric_LRW));
                FREEOTFE_FREE(lrw);

                break;
                }

            case CYPHER_MODE_XTS:
                {
                xts = FREEOTFE_MEMALLOC(sizeof(symmetric_xts));    
                FREEOTFE_MEMZERO(xts, sizeof(symmetric_xts));

                // Generate index in correct format
                // XTS uses:
                //   *) The sector index (i.e. the number of N-bit sectors)
                //   *) The first sector is sector 0
                //   *) Littleendian format
                LARGE_INTEGER__To__INTEGER_128_LittleEndian(
                                                    SectorID,
                                                    blockID128
                                                   );

                // Start an XTS session
                if ((errnum = xts_start(
                                        cipher, 
                                        Key, 
                                        &(Key[keySizeUnderlying/8]),
                                        (keySizeUnderlying/8), 
                                        0, 
                                        xts
                                       )) != CRYPT_OK)
                    {
                    status = STATUS_UNSUCCESSFUL;
                    DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to start XTS session (errnum: %d)\n"), errnum));
                    }
                else
                    {
                    if (encryptNotDecrypt)
                        {
                        if ((errnum = xts_encrypt(
                                                  InData, 
                                                  InLength, 
                                                  OutData, 
                                                  blockID128,
                                                  xts
                                                 )) != CRYPT_OK)
                            {
                            DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to encrypt block (errnum: %d)\n"), errnum));
                            status = STATUS_UNSUCCESSFUL;
                            } 
                        }
                    else 
                        {
                        if ((errnum = xts_decrypt(
                                              InData, 
                                              InLength, 
                                              OutData, 
                                              blockID128,
                                              xts
                                             )) != CRYPT_OK)
                            {
                            DEBUGOUTCYPHERIMPL(DEBUGLEV_ERROR, (TEXT("Unable to decrypt block (errnum: %d)\n"), errnum));
                            status = STATUS_UNSUCCESSFUL;
                            } 
                        }

                    xts_done(xts);
                    }

                SecZeroMemory(xts, sizeof(symmetric_xts));
                FREEOTFE_FREE(xts);

                break;
                }

            }

        }

    DEBUGOUTCYPHERIMPL(DEBUGLEV_EXIT, (TEXT("ImpCypherDecryptData\n")));

    return status;
}
// =========================================================================
// IOCTL to hash data
NTSTATUS
IOCTL_FreeOTFEHashIOCTL_Hash(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
)
{
    NTSTATUS status;
    PIO_STACK_LOCATION irpSp;
    PDIOC_HASH_DATA_IN DIOCBufferIn;
    PDIOC_HASH_DATA_OUT DIOCBufferOut;
    FREEOTFEBYTE* tmpOutput;
    unsigned int tmpLengthBits;  // In *bits*
    int userBufferSizeBytes;  // In *bytes*

    DEBUGOUTHASHDRV(DEBUGLEV_ENTER, ("IOCTL_FreeOTFEHashIOCTL_Hash\n"));

    irpSp = IoGetCurrentIrpStackLocation(Irp);

    DIOCBufferIn = (PDIOC_HASH_DATA_IN)Irp->AssociatedIrp.SystemBuffer;
    DIOCBufferOut = (PDIOC_HASH_DATA_OUT)Irp->AssociatedIrp.SystemBuffer;

    // Check size of INPUT buffer
    // Minimum check...
    // (Done first in order to ensure that we can later access "length" parts
    // for actual check)
    if (irpSp->Parameters.DeviceIoControl.InputBufferLength <
            sizeof(*DIOCBufferIn)-sizeof(DIOCBufferIn->Data))            
        {
        DEBUGOUTHASHDRV(DEBUGLEV_ERROR, ("inBuffer size wrong size (expect min: %d; got: %d)\n",
            sizeof(*DIOCBufferIn)-sizeof(DIOCBufferIn->Data),
            irpSp->Parameters.DeviceIoControl.InputBufferLength
            ));
        status = STATUS_INVALID_BUFFER_SIZE;
        return status;            
        }
    // Actual check...
    if (irpSp->Parameters.DeviceIoControl.InputBufferLength <
            sizeof(*DIOCBufferIn)+(DIOCBufferIn->DataLength/8)-sizeof(DIOCBufferIn->Data))            
        {
        DEBUGOUTHASHDRV(DEBUGLEV_ERROR, ("inBuffer size wrong size (expect actual: %d; got: %d)\n",
            sizeof(*DIOCBufferIn)+(DIOCBufferIn->DataLength/8)-sizeof(DIOCBufferIn->Data),
            irpSp->Parameters.DeviceIoControl.InputBufferLength
            ));
        status = STATUS_INVALID_BUFFER_SIZE;
        return status;            
        }


    // Check size of OUTPUT buffer
    // Minimum check...
    // (Done first in order to ensure that we can later access "length" parts
    // for actual check)
    if (irpSp->Parameters.DeviceIoControl.OutputBufferLength <
            sizeof(*DIOCBufferOut)-sizeof(DIOCBufferOut->Hash))
        {
        DEBUGOUTHASHDRV(DEBUGLEV_ERROR, ("outBuffer size wrong size (expect min: %d; got: %d)\n",
            sizeof(*DIOCBufferOut)-sizeof(DIOCBufferOut->Hash),
            irpSp->Parameters.DeviceIoControl.OutputBufferLength
            ));
        status = STATUS_INVALID_BUFFER_SIZE;
        return status;            
        }

        

    // Request valid so far, process...    


    // We have to allocate a buffer to hold the output, before it can be copied
    // out to the output buffer; otherwise, the input buffer gets garbled as
    // data is written to the output buffer

    // Match the user's buffer; if it's too small, the operation will kick
    // it out
    // This calculates the size of the variable part of the struct
    // Yes, this should be correct: The total size of the user's output buffer,
    // less the size of the struct - but then plus the size of
    // the variable parts reserved within the buffer
    userBufferSizeBytes = (irpSp->Parameters.DeviceIoControl.OutputBufferLength) - 
                          sizeof(*DIOCBufferOut) +
                          sizeof(DIOCBufferOut->Hash);
    tmpOutput = FREEOTFE_MEMALLOC(userBufferSizeBytes); 

    // The size of the buffer in bits; the algorithm will read this value, then
    // overwrite it with the actual size of the output (in bits)
    tmpLengthBits = (userBufferSizeBytes * 8);

    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Input buffer length is: %d bits\n", DIOCBufferIn->DataLength));
    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Allocated output buffer for: %d bits\n", tmpLengthBits));
    
    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("About to process...\n"));
    status = ImpHashHashData(
                             &DIOCBufferIn->HashGUID,
                             DIOCBufferIn->DataLength,  // In bits
                             (FREEOTFEBYTE*)&DIOCBufferIn->Data,
                             &tmpLengthBits,  // In bits
                             (FREEOTFEBYTE*)tmpOutput
                            );

    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Done with status: %d (want status: %d)\n", status, STATUS_SUCCESS));
    if (NT_SUCCESS(status)) 
        {
        DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("tmp buffer was: %d bits, output was %d bits\n", (userBufferSizeBytes * 8), tmpLengthBits));

        // Actual check...

        // Ensure output DIOC butter is large enough to store the output
        // Note that we can't carry out this check until we've created the
        // output value, in case the algorithm used produces variable length
        // output
        if (irpSp->Parameters.DeviceIoControl.OutputBufferLength <
                sizeof(*DIOCBufferOut)+(tmpLengthBits/8)-sizeof(DIOCBufferOut->Hash))
            {
            DEBUGOUTHASHDRV(DEBUGLEV_ERROR, ("outBuffer size wrong size (expect actual: %d; got: %d)\n",
                sizeof(*DIOCBufferOut)+(tmpLengthBits/8)-sizeof(DIOCBufferOut->Hash),
                irpSp->Parameters.DeviceIoControl.OutputBufferLength
                ));
            status = STATUS_INVALID_BUFFER_SIZE;
            }
        else
            {
            RtlCopyMemory(&DIOCBufferOut->Hash, tmpOutput, (tmpLengthBits / 8));
            DIOCBufferOut->HashLength = tmpLengthBits;
            Irp->IoStatus.Information = sizeof(*DIOCBufferOut)+(tmpLengthBits/8)-sizeof(DIOCBufferOut->Hash); 
            }

        }
    else
        {
        DEBUGOUTHASHDRV(DEBUGLEV_ERROR, ("FAILED"));
        }

    SecZeroMemory(tmpOutput, userBufferSizeBytes);
    FREEOTFE_FREE(tmpOutput);


    DEBUGOUTHASHDRV(DEBUGLEV_EXIT, ("IOCTL_FreeOTFEHashIOCTL_Hash\n"));

    return status;
}
// =========================================================================
// This routine is the driver's entry point, called by the I/O system
// to load the driver.  The driver's entry points are initialized.
// In DBG mode, this routine also examines the registry for special
// debug parameters.
NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
{
    NTSTATUS status = STATUS_SUCCESS;
    UNICODE_STRING devDirName;
    OBJECT_ATTRIBUTES dirObjAttribs;
    PDEVICE_OBJECT mainDevObj;
    PDEVICE_EXTENSION mainDevExt;
    
    // The following debug handling was ripped from the MS DDK "floppy.c"
    // example
#if DBG
    // We use this to query into the registry as to whether we
    // should break at driver entry.
    RTL_QUERY_REGISTRY_TABLE paramTable[3];
    ULONG default_ShouldBreak = 0;           // Default to 0; no break
//    ULONG default_DebugLevel  = 0xFFFFFFFF;  // Default to all on
    ULONG default_DebugLevel  = 0x0000001F;  // All except verbose debug
    ULONG debugLevel = 0;
    ULONG shouldBreak = 0;
    PWCHAR path;
    ULONG pathLength;
    
    // Since the registry path parameter is a "counted" UNICODE string, it
    // might not be zero terminated.  For a very short time allocate memory
    // to hold the registry path zero terminated so that we can use it to
    // delve into the registry.
    //
    // NOTE NOTE!!!! This is not an architected way of breaking into
    // a driver.  It happens to work for this driver because the original
    // DDK example code for DriverEntry happened to be written in this manner.
    pathLength = RegistryPath->Length + sizeof(WCHAR);

    if (path = FREEOTFE_MEMALLOC(pathLength)) {

        SecZeroMemory(&paramTable[0], sizeof(paramTable));
        SecZeroMemory(path, pathLength);
        RtlMoveMemory( path, RegistryPath->Buffer, RegistryPath->Length );

        paramTable[0].Flags         = RTL_QUERY_REGISTRY_DIRECT;
        paramTable[0].Name          = L"BreakOnEntry";
        paramTable[0].EntryContext  = &shouldBreak;
        paramTable[0].DefaultType   = REG_DWORD;
        paramTable[0].DefaultData   = &default_ShouldBreak;
        paramTable[0].DefaultLength = sizeof(ULONG);

        paramTable[1].Flags         = RTL_QUERY_REGISTRY_DIRECT;
        paramTable[1].Name          = L"DebugLevel";
        paramTable[1].EntryContext  = &debugLevel;
        paramTable[1].DefaultType   = REG_DWORD;
        paramTable[1].DefaultData   = &default_DebugLevel;
        paramTable[1].DefaultLength = sizeof(ULONG);

        if (!(NT_SUCCESS(RtlQueryRegistryValues(
                            RTL_REGISTRY_ABSOLUTE | RTL_REGISTRY_OPTIONAL,
                            path,
                            &paramTable[0],
                            NULL,
                            NULL)))) {

            shouldBreak = default_ShouldBreak;
            debugLevel = default_DebugLevel;
        }

        FREEOTFE_FREE(path);
    }

    FreeOTFEDebugLevel = debugLevel;
    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Debug level: %d\n", FreeOTFEDebugLevel));
    
    if (shouldBreak == 1)
        {
        DbgBreakPoint();
        }

#endif

    DEBUGOUTHASHDRV(DEBUGLEV_ENTER, ("DriverEntry\n"));

    // Create main device dir
    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Creating dir object (main)...\n"));
    status = CreateDeviceDir(
                DEVICE_FREEOTFE_ROOT,
                &DirFreeOTFERoot
               );
    if (!(NT_SUCCESS(status)))
        {
        DEBUGOUTHASHDRV(DEBUGLEV_ERROR, ("Unable to CreateDeviceDir (main)\n"));
        return status;
        }

    // Create hash device dir
    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Creating dir object (hash)...\n"));
    status = CreateDeviceDir(
                DEVICE_HASH_DIR_NAME,
                &DirFreeOTFEHash
               );
    if (!(NT_SUCCESS(status)))
        {
        DEBUGOUTHASHDRV(DEBUGLEV_ERROR, ("Unable to CreateDeviceDir (hash)\n"));
        ZwClose(DirFreeOTFERoot);
        return status;
        }

    // Create main device
    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Creating main device...\n"));
    status = CreateDevice(DriverObject, &mainDevObj);
    if (!(NT_SUCCESS(status)))
        {
        DEBUGOUTHASHDRV(DEBUGLEV_ERROR, ("Call to CreateDevice FAILED.\n"));
        return status;
        }
    
    // Store the device dir handle for closure on unload
    mainDevExt = (PDEVICE_EXTENSION)mainDevObj->DeviceExtension;
    

    // Initialize the driver object with this driver's entry points.
    DriverObject->MajorFunction[IRP_MJ_CREATE] = 
                                            FreeOTFE_MF_DispatchCreate;
                                            
    DriverObject->MajorFunction[IRP_MJ_CLOSE] =
                                            FreeOTFE_MF_DispatchClose;
                                            
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
                                            FreeOTFE_MF_DispatchDeviceControl;
                                            

    DriverObject->DriverUnload = DriverUnload;


    DEBUGOUTHASHDRV(DEBUGLEV_EXIT, ("DriverEntry\n"));    

    return status;
}
// =========================================================================
// Destroy the specified device object, regardless of it's state
// Note: This can be called to destroy *either* the main device, *or* a
//       disk device
// Returns: The next device object
PDEVICE_OBJECT
DestroyDevice(
    IN PDEVICE_OBJECT devObj    
)
{
    NTSTATUS status;
    PDEVICE_EXTENSION devExt;
    PDEVICE_OBJECT nextDevObj;

    DEBUGOUTHASHDRV(DEBUGLEV_ENTER, ("DestroyDevice\n"));
        
    nextDevObj = devObj->NextDevice;
    
    devExt = (PDEVICE_EXTENSION)devObj->DeviceExtension;    

    // Signal the device's thread to terminate, and wait until it does.
    devExt->TerminateThread = TRUE;
    CancelAllQueuedIRPs(devObj);
    if (devExt->ThreadObject != NULL) 
        {    
        // Make sure the thread wakes up, so it can see that it should terminate
        KeReleaseSemaphore(&devExt->IRPQueueSemaphore,
                            0,  // No priority boost
                            1,  // Increment semaphore by 1
                            TRUE );// WaitForXxx after this call

        // Wait for the thread to terminate
        KeWaitForSingleObject(devExt->ThreadObject,
                            Executive,
                            KernelMode,
                            FALSE,
                            NULL );

        DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Wait completed; thread terminated.\n"));
        
        // Release the thread object ref
        DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Dereferencing ThreadObject...\n"));
        ObDereferenceObject(devExt->ThreadObject);
        devExt->ThreadObject = NULL;
        }
    

    // Tear down any symbolic device name link
    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Tearing down main device specific...\n"));
    if (devExt->zzSymbolicLinkName.Buffer != NULL)
        {
        DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Tearing down symbolic device link...\n"));
        DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("About to delete symlink: %ls\n", devExt->zzSymbolicLinkName.Buffer));
        status = IoDeleteSymbolicLink(&devExt->zzSymbolicLinkName);
        if (!NT_SUCCESS(status))
            {
            DEBUGOUTHASHDRV(DEBUGLEV_ERROR, ("Status NOT OK\n"));
            }
        
        DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Freeing off symlink unicode buffer...\n")); 
        SecZeroMemory(
                        devExt->zzSymbolicLinkName.Buffer,
                        devExt->zzSymbolicLinkName.MaximumLength
                        );
        FREEOTFE_FREE(devExt->zzSymbolicLinkName.Buffer);
        devExt->zzSymbolicLinkName.Buffer = NULL;
        DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("OK.\n"));   
        }
        
    
    // Tear down any device name
    if (devExt->zzDeviceName.Buffer != NULL)
        {
        DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Freeing off devname unicode buffer...\n"));
        SecZeroMemory(
                      devExt->zzDeviceName.Buffer,
                      devExt->zzDeviceName.MaximumLength
                     );
        FREEOTFE_FREE(devExt->zzDeviceName.Buffer);
        devExt->zzDeviceName.Buffer = NULL;
        }
    

    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("OK.\n"));   
    
    // Cleardown details of hashes supported
    ImpHashDriverExtDetailsCleardown(&(devExt->DriverInfo));

    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("OK.\n"));   

    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Overwriting device extension memory...\n"));   
    SecZeroMemory(devExt, sizeof(DEVICE_EXTENSION));
    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("OK.\n"));   

    // Get rid of the device object...
    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("Deleting device...\n"));   
    IoDeleteDevice(devObj);
    DEBUGOUTHASHDRV(DEBUGLEV_INFO, ("OK.\n"));   
    

    DEBUGOUTHASHDRV(DEBUGLEV_EXIT, ("DestroyDevice\n"));
    
    return nextDevObj;
}