/*++

Copyright (c) 1994-1998  Microsoft Corporation

Module Name:

    sesskey.c

Abstract:

    Contains common client/server code that generate session key.

Author:

    Madan Appiah (madana)  24-Jan-1998

Environment:

    User Mode - Win32

Revision History:

--*/

#include <seccom.h>

VOID
Salt8ByteKey(
    LPBYTE pbKey,
    DWORD dwSaltBytes
    )
/*++

Routine Description:

    This macro function salts the first 1 or 3 bytes of the 8 bytes key to a
    known value in order to make it a 40-bit key.

Arguments:

    pbKey - pointer to a 8 bytes key buffer.
    dwSaltBytes - this value should be either 1 or 3

Return Value:

    None.

--*/
{
    ASSERT( (dwSaltBytes == 1) || (dwSaltBytes == 3) );

    if( dwSaltBytes == 1 ) {

        //
        // for 56-bit encryption, salt first byte only.
        //

        *pbKey++ = 0xD1 ;
    }
    else if (dwSaltBytes == 3) {

        //
        // for 40-bit encryption, salt first 3 bytes.
        //

        *pbKey++ = 0xD1 ;
        *pbKey++ = 0x26 ;
        *pbKey  = 0x9E ;
    }

    return;
}

VOID
FinalHash(
    LPRANDOM_KEYS_PAIR pKeyPair,
    LPBYTE pbKey
    )
/*++

Routine Description:

    This macro function hashes the final key with the client and server randoms.

Arguments:

    pKeyPair - pointer a random key pair structure.

    pbKey - pointer to a key buffer, the final key is returned back in the same
        buffer.

Return Value:

    None.

--*/
{
    MD5_CTX Md5Hash;

    //
    // final_key = MD5(key + clientRandom + serverRandom)
    //

    MD5Init  (&Md5Hash);

    MD5Update(&Md5Hash, pbKey, MAX_SESSION_KEY_SIZE);
    MD5Update(&Md5Hash, pKeyPair->clientRandom, RANDOM_KEY_LENGTH);
    MD5Update(&Md5Hash, pKeyPair->serverRandom, RANDOM_KEY_LENGTH);
    MD5Final (&Md5Hash);

    //
    // copy the final key back to the input buffer.
    //

    ASSERT( MD5DIGESTLEN >= MAX_SESSION_KEY_SIZE );
    memcpy(pbKey, Md5Hash.digest, MAX_SESSION_KEY_SIZE);

    return;
}

VOID
MakeMasterKey(
    LPRANDOM_KEYS_PAIR pKeyPair,
    LPSTR FAR *ppszSalts,
    LPBYTE pbPreMaster,
    LPBYTE pbMaster
    )
/*++

Routine Description:

    This macro function makes a master secret using a pre-master secret.

Arguments:

    pKeyPair - pointer a key pair structure.

    ppszSalts - pointer to a salt key strings array.

    pbPreMaster - pointer to a pre-master secret key buffer.

    pbMaster - pointer to a master secret key buffer.

Return Value:

    None.

--*/
{
    DWORD i;

    MD5_CTX Md5Hash;
    A_SHA_CTX ShaHash;
    BYTE bShaHashValue[A_SHA_DIGEST_LEN];

    //
    //initialize all buffers with zero
    //

    memset( pbMaster, 0, PRE_MASTER_SECRET_LEN);
    memset( bShaHashValue, 0, A_SHA_DIGEST_LEN);

    for ( i = 0 ; i < 3 ; i++) {

        //
        // SHA(ppszSalts[i] + pre-master + clientRandom +  serverRandom)
        //

        A_SHAInit(&ShaHash);
        A_SHAUpdate(&ShaHash, ppszSalts[i], strlen(ppszSalts[i]));
        A_SHAUpdate(&ShaHash, pbPreMaster, PRE_MASTER_SECRET_LEN );
        A_SHAUpdate(
            &ShaHash,
            pKeyPair->clientRandom,
            sizeof(pKeyPair->clientRandom) );
        A_SHAUpdate(
            &ShaHash,
            pKeyPair->serverRandom,
            sizeof(pKeyPair->serverRandom) );
        A_SHAFinal(&ShaHash, bShaHashValue);

        //
        // MD5(pre_master + SHA-hash)
        //

        MD5Init(&Md5Hash);
        MD5Update(&Md5Hash, pbPreMaster, PRE_MASTER_SECRET_LEN );
        MD5Update(&Md5Hash, bShaHashValue, A_SHA_DIGEST_LEN);
        MD5Final(&Md5Hash);

        //
        // copy part of the master secret.
        //

        memcpy(
            pbMaster + (i * MD5DIGESTLEN),
            Md5Hash.digest,
            MD5DIGESTLEN);
    }

    return;
}

VOID
MakePreMasterSecret(
    LPRANDOM_KEYS_PAIR pKeyPair,
    LPBYTE pbPreMasterSecret
    )
/*++

Routine Description:

    This function makes a pre-master secret for the initial session key.

Arguments:

    pKeyPair - pointer a key pair structure.

    pbPreMasterSecret - pointer to a pre-master secret key buffer, it is
        PRE_MASTER_SECRET_LEN bytes long.

Return Value:

    None.

--*/
{
    //
    // copy PRE_MASTER_SECRET_LEN/2 bytes from clientRandom first.
    //

    memcpy(
        pbPreMasterSecret,
        pKeyPair->clientRandom,
        PRE_MASTER_SECRET_LEN/2 );

    //
    // copy PRE_MASTER_SECRET_LEN/2 bytes from serverRandom next.
    //

    memcpy(
        pbPreMasterSecret + PRE_MASTER_SECRET_LEN/2,
        pKeyPair->serverRandom,
        PRE_MASTER_SECRET_LEN/2 );

    return;
}

VOID
GenerateMasterSecret(
    LPRANDOM_KEYS_PAIR pKeyPair,
    LPBYTE pbPreMasterSecret
    )
/*++

Routine Description:

    This function creates a master secret key using the pre-master key and
    random key pair.

Arguments:

    pKeyPair - pointer a key pair structure.

    pbPreMasterSecret - pointer to a pre-master secret key buffer, it is
        PRE_MASTER_SECRET_LEN bytes long.


Return Value:

    None.

--*/
{
    BYTE abMasterSecret[PRE_MASTER_SECRET_LEN];
    LPSTR apszSalts[3] = { "A","BB","CCC" } ;

    ASSERT( PRE_MASTER_SECRET_LEN == 3 * MD5DIGESTLEN );

    //
    // make master secret.
    //

    MakeMasterKey(
        pKeyPair,
        (LPSTR FAR *)apszSalts,
        pbPreMasterSecret,
        (LPBYTE)abMasterSecret );

    //
    // copy master secret in the return buffer.
    //

    memcpy( pbPreMasterSecret, abMasterSecret, PRE_MASTER_SECRET_LEN);

    return;
}

VOID
UpdateKey(
    LPBYTE pbStartKey,
    LPBYTE pbCurrentKey,
    DWORD dwKeyLength
    )
/*++

Routine Description:

    This function updates a key.

Arguments:

    pbStartKey - pointer to the start session key buffer.

    pbCurrentKey - pointer to the current session key buffer, new session key is
        copied to this buffer on return.

    dwKeyLength - length of the key.

Return Value:

    None.

--*/
{
    A_SHA_CTX       SHAHash;
    MD5_CTX         MD5Hash;
    BYTE            abSHADigest[A_SHA_DIGEST_LEN];

    //
    // make a SHA(pbStartKey + g_abPad1 + pbCurrentKey) hash.
    //

    A_SHAInit(&SHAHash);
    A_SHAUpdate(&SHAHash, pbStartKey, dwKeyLength);
    A_SHAUpdate(&SHAHash, (unsigned char *)g_abPad1, 40);
    A_SHAUpdate(&SHAHash, pbCurrentKey, dwKeyLength);
    A_SHAFinal(&SHAHash, abSHADigest);

    //
    // make a MD5(pbStartKey + g_abPad2 + SHAHash) hash.
    //

    MD5Init(&MD5Hash);
    MD5Update(&MD5Hash, pbStartKey, dwKeyLength);
    MD5Update(&MD5Hash, g_abPad2, 48);
    MD5Update(&MD5Hash, abSHADigest, A_SHA_DIGEST_LEN);
    MD5Final(&MD5Hash);

    ASSERT( dwKeyLength <= MD5DIGESTLEN );
    memcpy(pbCurrentKey, MD5Hash.digest, (UINT)dwKeyLength);

    return;
}

BOOL
MakeSessionKeys(
    LPRANDOM_KEYS_PAIR pKeyPair,
    LPBYTE pbEncryptKey,
    struct RC4_KEYSTRUCT FAR *prc4EncryptKey,
    LPBYTE pbDecryptKey,
    struct RC4_KEYSTRUCT FAR *prc4DecryptKey,
    LPBYTE pbMACSaltKey,
    DWORD dwKeyStrength,
    LPDWORD pdwKeyLength,
    DWORD dwEncryptionLevel
    )
/*++

Routine Description:

    Make the server session key using the client and server random keys.

    Assume : the encrypt and decrypt buffer presented are
        atleast MAX_SESSION_KEY_SIZE (16) bytes long.

Arguments:

    pKeyPair - pointer a key pair structure.

    pbEncryptKey - pointer to a buffer where the encryption key is stored.

    prc4EncryptKey - pointer to a RC4 encrypt key structure.

    pbDecryptKey - pointer to a buffer where the decryption key is stored.

    prc4DecryptKey - pointer to a RC4 decrypt key structure.

    pbMACSaltKey - pointer to a buffer where the message authentication key is
        stored.

    dwKeyStrength - specify key strength to use.

    pdwKeyLength - pointer to a location where the length of the above
        encryption/decryption key is returned.

    dwEncryptionLevel - encryption level, used to select the encryption
        algorithm.

Return Value:

    TRUE - if successfully created the session key.

    FALSE - otherwise.

--*/
{
    BYTE abPreMasterSecret[PRE_MASTER_SECRET_LEN];
    BYTE abMasterSessionKey[PRE_MASTER_SECRET_LEN];
    LPSTR apszSalts[3] = { "X","YY","ZZZ" } ;
    DWORD dwSaltLen;

    //
    // make a pre-master secret.
    //

    MakePreMasterSecret( pKeyPair, (LPBYTE)abPreMasterSecret );

    //
    // generate master secret.
    //

    GenerateMasterSecret( pKeyPair, (LPBYTE)abPreMasterSecret );

    //
    // make a master session key for all three session keys (encrypt, decrypt
    // and MACSalt).
    //

    MakeMasterKey(
        pKeyPair,
        (LPSTR FAR *)apszSalts,
        (LPBYTE)abPreMasterSecret,
        (LPBYTE)abMasterSessionKey );

    ASSERT( PRE_MASTER_SECRET_LEN == 3 * MAX_SESSION_KEY_SIZE );

    //
    // copy first part of the master key as MAC salt key.
    //

    memcpy(
        pbMACSaltKey,
        (LPBYTE)abMasterSessionKey,
        MAX_SESSION_KEY_SIZE );

    //
    // copy second part of the master key as encrypt key and final hash it.
    //

    memcpy(
        pbEncryptKey,
        (LPBYTE)abMasterSessionKey + MAX_SESSION_KEY_SIZE,
        MAX_SESSION_KEY_SIZE );

    FinalHash( pKeyPair, pbEncryptKey );

    //
    // copy second part of the master key as decrypt key and final hash it.
    //

    memcpy(
        pbDecryptKey,
        (LPBYTE)abMasterSessionKey + MAX_SESSION_KEY_SIZE * 2,
        MAX_SESSION_KEY_SIZE );

    FinalHash( pKeyPair, pbDecryptKey );


    //
    // finally select the key length.
    //

    ASSERT( MAX_SESSION_KEY_SIZE == 16 );

    dwSaltLen = 0;
    switch ( dwKeyStrength ) {

        case SM_40BIT_ENCRYPTION_FLAG:
            *pdwKeyLength = MAX_SESSION_KEY_SIZE/2;
            dwSaltLen = 3;
            break;

        case SM_56BIT_ENCRYPTION_FLAG:
            *pdwKeyLength = MAX_SESSION_KEY_SIZE/2;
            dwSaltLen = 1;
            break;

        case SM_128BIT_ENCRYPTION_FLAG:
            ASSERT( g_128bitEncryptionEnabled );
            *pdwKeyLength = MAX_SESSION_KEY_SIZE;
            break;

        default:

            //
            // we shouldn't reach here.
            //

            ASSERT( FALSE );
            *pdwKeyLength = MAX_SESSION_KEY_SIZE/2;
            dwSaltLen = 1;
            break;
    }

    if( dwSaltLen ) {

        Salt8ByteKey( pbMACSaltKey, dwSaltLen );
        Salt8ByteKey( pbEncryptKey, dwSaltLen );
        Salt8ByteKey( pbDecryptKey, dwSaltLen );
    }

    //
    // finally make rc4 keys.
    //
    // use microsoft version of rc4 algorithm (super fast!) for level 1 and
    // level 2 encryption, for level 3 use RSA rc4 algorithm.
    //

    if( dwEncryptionLevel <= 2 ) {
        msrc4_key( prc4EncryptKey, (UINT)*pdwKeyLength, pbEncryptKey );
        msrc4_key( prc4DecryptKey, (UINT)*pdwKeyLength, pbDecryptKey );
    }
    else {
        rc4_key( prc4EncryptKey, (UINT)*pdwKeyLength, pbEncryptKey );
        rc4_key( prc4DecryptKey, (UINT)*pdwKeyLength, pbDecryptKey );
    }

    return( TRUE );
}

BOOL
UpdateSessionKey(
    LPBYTE pbStartKey,
    LPBYTE pbCurrentKey,
    DWORD dwKeyStrength,
    DWORD dwKeyLength,
    struct RC4_KEYSTRUCT FAR *prc4Key,
    DWORD dwEncryptionLevel
    )
/*++

Routine Description:

    Update the session key using the current and start session keys.

Arguments:

    pbStartKey - pointer to the start session key buffer.

    pbCurrentKey - pointer to the current session key buffer, new session key is
        copied to this buffer on return.

    dwKeyStrength - specify key strength to use.

    dwKeyLength - length of the key.

    prc4Key - pointer to a RC4 key structure.

    dwEncryptionLevel - encryption level, used to select the encryption
        algorithm.

Return Value:

    TRUE - if the successfully update the key.

    FALSE - otherwise.

--*/
{
    DWORD dwSaltLen;

    //
    // update current key first.
    //

    UpdateKey( pbStartKey, pbCurrentKey, dwKeyLength );

    //
    // use microsoft version of rc4 algorithm (super fast!) for level 1 and
    // level 2 encryption, for level 3 use RSA rc4 algorithm.
    //

    if( dwEncryptionLevel <= 2 ) {

        //
        // re-initialized RC4 table.
        //

        msrc4_key( prc4Key, (UINT)dwKeyLength, pbCurrentKey );

        //
        // scramble the current key.
        //

        msrc4( prc4Key, (UINT)dwKeyLength, pbCurrentKey );
    }
    else {

        //
        // re-initialized RC4 table.
        //

        rc4_key( prc4Key, (UINT)dwKeyLength, pbCurrentKey );

        //
        // scramble the current key.
        //

        rc4( prc4Key, (UINT)dwKeyLength, pbCurrentKey );
    }

    //
    // salt the key appropriately.
    //

    dwSaltLen = 0;
    switch ( dwKeyStrength ) {

        case SM_40BIT_ENCRYPTION_FLAG:
            ASSERT( dwKeyLength = MAX_SESSION_KEY_SIZE/2 );
            dwSaltLen = 3;
            break;

        case SM_56BIT_ENCRYPTION_FLAG:
            ASSERT( dwKeyLength = MAX_SESSION_KEY_SIZE/2 );
            dwSaltLen = 1;
            break;

        case SM_128BIT_ENCRYPTION_FLAG:
            ASSERT( g_128bitEncryptionEnabled );
            ASSERT( dwKeyLength = MAX_SESSION_KEY_SIZE );
            break;

        default:

            //
            // we shouldn't reach here.
            //

            ASSERT( FALSE );
            ASSERT( dwKeyLength = MAX_SESSION_KEY_SIZE/2 );
            dwSaltLen = 1;
            break;
    }

    if( dwSaltLen ) {
        Salt8ByteKey( pbCurrentKey, dwSaltLen );
    }

    //
    // re-initialized RC4 table again.
    //

    if( dwEncryptionLevel <= 2 ) {

        msrc4_key( prc4Key, (UINT)dwKeyLength, pbCurrentKey );
    }
    else {

        rc4_key( prc4Key, (UINT)dwKeyLength, pbCurrentKey );
    }

    return( TRUE );
}