#include <Weave/Core/WeaveCore.h>
#include "WeaveTAKE.h"
#include "WeaveSecurity.h"
#include <Weave/Support/crypto/WeaveCrypto.h>
#include <Weave/Support/crypto/HashAlgos.h>
#include <Weave/Support/crypto/EllipticCurve.h>
#include <Weave/Support/CodeUtils.h>
#include <Weave/Support/ErrorStr.h>
#include <Weave/Support/ASN1.h>
#include <Weave/Support/crypto/WeaveCrypto.h>
#include <Weave/Support/crypto/AESBlockCipher.h>

namespace nl {
namespace Weave {
namespace Profiles {
namespace Security {
namespace TAKE {

using namespace nl::Weave::Encoding;
using namespace nl::Weave::Crypto;
using namespace nl::Weave::ASN1;

void WeaveTAKEEngine::Init()
{
    State = kState_Reset;
    KeyState = kEncryptionKeyState_Uninitialized;
    SessionKeyId = WeaveKeyId::kNone;
}

void WeaveTAKEEngine::Shutdown()
{
    ClearSecretData(IdentificationKey, sizeof(IdentificationKey));
    ClearSecretData(AuthenticationKey, sizeof(AuthenticationKey));
    ClearSecretData(ECDHPrivateKeyBuffer, sizeof(ECDHPrivateKeyBuffer));
    State = kState_Reset;
    KeyState = kEncryptionKeyState_Uninitialized;
    SessionKeyId = WeaveKeyId::kNone;
}

static WEAVE_ERROR PackControlHeader(uint8_t numOptionalConfigurations, bool encryptAuthPhase, bool encryptCommPhase, bool timeLimitedIK, bool hasChallengerId, uint8_t& controlHeader)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;

    VerifyOrExit(numOptionalConfigurations < kMaxOptionalConfigurations, err = WEAVE_ERROR_INVALID_ARGUMENT);
    controlHeader = (numOptionalConfigurations << kControlHeader_NumOptionalConfigurationShift) & kControlHeader_NumOptionalConfigurationMask;

    if (encryptAuthPhase)
        controlHeader |= kControlHeader_EncryptAuthenticationPhaseFlag;
    if (encryptCommPhase)
        controlHeader |= kControlHeader_EncryptCommunicationsPhaseFlag;
    if (timeLimitedIK)
        controlHeader |= kControlHeader_TimeLimitFlag;
    if (hasChallengerId)
        controlHeader |= kControlHeader_HasChallengerIdFlag;

exit:
    return err;
}

uint8_t WeaveTAKEEngine::GetNumOptionalConfigurations() const
{
    return static_cast<uint8_t>((ControlHeader & kControlHeader_NumOptionalConfigurationMask) >> kControlHeader_NumOptionalConfigurationShift);
}

bool WeaveTAKEEngine::IsEncryptAuthPhase() const
{
    return (ControlHeader & kControlHeader_EncryptAuthenticationPhaseFlag) != 0;
}

bool WeaveTAKEEngine::IsEncryptCommPhase() const
{
    return (ControlHeader & kControlHeader_EncryptCommunicationsPhaseFlag) != 0;
}

bool WeaveTAKEEngine::IsTimeLimitedIK() const
{
    return (ControlHeader & kControlHeader_TimeLimitFlag) != 0;
}

bool WeaveTAKEEngine::HasSentChallengerId() const
{
    return (ControlHeader & kControlHeader_HasChallengerIdFlag) != 0;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateIdentifyTokenMessage(uint16_t sessionKeyId, uint8_t takeConfig, bool encryptAuthPhase, bool encryptCommPhase, bool timeLimitedIK,
                                                              bool sendChallengerId, uint8_t encryptionType, uint64_t localNodeId, PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t* p = msgBuf->Start();
    uint16_t msgLen;

    VerifyOrExit(State == kState_Reset || State == kState_InitiatorReconfigureProcessed, err = WEAVE_ERROR_INCORRECT_STATE);

    PackControlHeader(0, encryptAuthPhase, encryptCommPhase, timeLimitedIK, sendChallengerId, ControlHeader);

    if (sendChallengerId)
    {
        ChallengerIdLen = kMaxChallengerIdSize;
        err = ChallengerAuthDelegate->GetChallengerID(ChallengerId, ChallengerIdLen);
        SuccessOrExit(err);

        VerifyOrExit(ChallengerIdLen <= kMaxChallengerIdSize, err = WEAVE_ERROR_INVALID_ARGUMENT);
    }
    else
    {
        uint8_t* ChallengerIdPtr = ChallengerId;
        LittleEndian::Write64(ChallengerIdPtr, localNodeId);
        ChallengerIdLen = 8;
    }

    msgLen = kIdentifyTokenMsgMinSize + (sendChallengerId ? 1 + ChallengerIdLen : 0) + GetNumOptionalConfigurations() + (UseSessionKey() ? 2 : 0);
    VerifyOrExit(msgBuf->AvailableDataLength() >= msgLen, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    err = Platform::Security::GetSecureRandomData(ChallengerNonce, kNonceSize);
    SuccessOrExit(err);

    *p++ = ControlHeader;

    if (sendChallengerId)
    {
        *p++ = ChallengerIdLen;
    }

    EncryptionType = encryptionType;
    *p++ = EncryptionType;

    VerifyOrExit(takeConfig == kTAKEConfig_Config1, err = WEAVE_ERROR_UNSUPPORTED_TAKE_CONFIGURATION);
    ProtocolConfig = takeConfig;
    *p++ = ProtocolConfig;

    if (UseSessionKey())
    {
        SessionKeyId = sessionKeyId;
        LittleEndian::Write16(p, SessionKeyId);
    }

    if (sendChallengerId)
    {
        WriteArray(ChallengerId, p, ChallengerIdLen);
    }

    WriteArray(ChallengerNonce, p, kNonceSize);

    msgBuf->SetDataLength(msgLen);
    State = kState_InitiatorIdentifyTokenGenerated;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessIdentifyTokenMessage(uint64_t peerNodeId, const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    const uint8_t* p = msgBuf->Start();

    VerifyOrExit(msgLen >= kIdentifyTokenMsgMinSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_Reset, err = WEAVE_ERROR_INCORRECT_STATE);

    ControlHeader = *p++;

    if (HasSentChallengerId())
    {
        ChallengerIdLen = *p++;
        VerifyOrExit(ChallengerIdLen <= kMaxChallengerIdSize, err = WEAVE_ERROR_INVALID_ARGUMENT);
    }
    else
    {
        uint8_t* ChallengerIdPtr = ChallengerId;
        LittleEndian::Write64(ChallengerIdPtr, peerNodeId);
        ChallengerIdLen = 8;
    }

    VerifyOrExit(msgLen == (kIdentifyTokenMsgMinSize + (HasSentChallengerId() ? 1 + ChallengerIdLen : 0) + GetNumOptionalConfigurations() + (UseSessionKey() ? 2 : 0)), err = WEAVE_ERROR_MESSAGE_INCOMPLETE);

    EncryptionType = *p++;
    VerifyOrExit(EncryptionType == kWeaveEncryptionType_AES128CTRSHA1, err = WEAVE_ERROR_UNSUPPORTED_ENCRYPTION_TYPE);

    ProtocolConfig = *p++;

    ReadArray(OptionalConfigurations, p, GetNumOptionalConfigurations() * sizeof(uint8_t));

    ChosenConfiguration = ProtocolConfig;
    if (ChosenConfiguration != kTAKEConfig_Config1)
    {
        for (int i = 0; i < GetNumOptionalConfigurations(); i++)
        {
            if (OptionalConfigurations[i] == kTAKEConfig_Config1)
            {
                ChosenConfiguration = OptionalConfigurations[i];
                break;
            }
        }
    }

    VerifyOrExit(ChosenConfiguration == kTAKEConfig_Config1, err = WEAVE_ERROR_TAKE_RECONFIGURE_REQUIRED);

    if (UseSessionKey())
    {
        SessionKeyId = LittleEndian::Read16(p);
    }

    if (HasSentChallengerId())
    {
        ReadArray(ChallengerId, p, ChallengerIdLen);
    }

    ReadArray(ChallengerNonce, p, kNonceSize);

    State = kState_ResponderIdentifyTokenProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateIdentifyTokenResponseMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    Platform::Security::SHA1 sha1;
    uint8_t* p = msgBuf->Start();
    uint8_t identificationRootKey[kIdentificationRootKeySize];
    HKDFSHA1 hkdf;
    uint8_t keySaltLen = ChallengerIdLen + sizeof(uint32_t);
    uint8_t keySalt[kMaxIdentifyTokenResponseKeySaltSize];

    VerifyOrExit(State == kState_ResponderIdentifyTokenProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= kIdentifyTokenResponseMsgSize, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    err = Platform::Security::GetSecureRandomData(TokenNonce, kNonceSize);
    SuccessOrExit(err);

    err = TokenAuthDelegate->GetIdentificationRootKey(identificationRootKey);
    SuccessOrExit(err);

    *p++ = ChosenConfiguration;

    WriteArray(TokenNonce, p, kNonceSize);

    {
        uint8_t* keySaltPtr = keySalt;
        WriteArray(ChallengerId, keySaltPtr, ChallengerIdLen);
        if (!IsTimeLimitedIK())
        {
            WriteArray(kSaltTimeUnlimitedIdentificationKey, keySaltPtr, sizeof(kSaltTimeUnlimitedIdentificationKey));
        }
        else
        {
            uint32_t time;
            err = TokenAuthDelegate->GetTAKETime(time);
            SuccessOrExit(err);

            LittleEndian::Write32(keySaltPtr, time);
        }

        hkdf.BeginExtractKey(keySalt, keySaltLen);

        hkdf.AddKeyMaterial(identificationRootKey, kIdentificationRootKeySize);

        err = hkdf.FinishExtractKey();
        SuccessOrExit(err);

        err = hkdf.ExpandKey(NULL, 0, kIdentificationKeySize, IdentificationKey);
        SuccessOrExit(err);
    }

    GenerateHMACSignature(IdentificationKey, p);

    msgBuf->SetDataLength(kIdentifyTokenResponseMsgSize);

    if (UseSessionKey())
    {
        err = GenerateProtocolEncryptionKey();
        SuccessOrExit(err);
    }

    State = kState_ResponderIdentifyTokenResponseGenerated;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessIdentifyTokenResponseMessage(const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    const uint8_t* p = msgBuf->Start();
    bool isAuthorisedIK = false;
    int i, end;
    uint8_t takeConfig;
    uint16_t authKeyLen = kAuthenticationKeySize;
    uint16_t encAuthBlobLen = kTokenEncryptedStateSize;

    VerifyOrExit(msgLen == kIdentifyTokenResponseMsgSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_InitiatorIdentifyTokenGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    ChosenConfiguration = *p++;

    ReadArray(TokenNonce, p, kNonceSize);

    if (ChosenConfiguration != ProtocolConfig)
    {
        bool isProposedConfiguration = false;
        for (i = 0, end = GetNumOptionalConfigurations(); i < end; i++)
        {
            if (ChosenConfiguration == OptionalConfigurations[i])
            {
                isProposedConfiguration = true;
                break;
            }
        }
        VerifyOrExit(isProposedConfiguration, err = WEAVE_ERROR_UNSUPPORTED_TAKE_CONFIGURATION);
    }

    uint8_t hmacBuffer[kConfig1_HMACSignatureSize];

    ChallengerAuthDelegate->RewindIdentificationKeyIterator();

    while (1)
    {
        uint16_t identificationKeyLen = kIdentificationKeySize; 

        err = ChallengerAuthDelegate->GetNextIdentificationKey(TokenId, IdentificationKey, identificationKeyLen);
        SuccessOrExit(err);

        if (TokenId == kNodeIdNotSpecified)
        {
            VerifyOrExit(identificationKeyLen == kIdentificationKeySize, err = WEAVE_ERROR_INVALID_ARGUMENT);
            break;
        }
        GenerateHMACSignature(IdentificationKey, hmacBuffer);

        if (ConstantTimeCompare(hmacBuffer, p, kConfig1_HMACSignatureSize))
        {
            isAuthorisedIK = true;
            break;
        }
    }

    VerifyOrExit(isAuthorisedIK, err = WEAVE_ERROR_TAKE_TOKEN_IDENTIFICATION_FAILED);

    if (UseSessionKey())
    {
        err = GenerateProtocolEncryptionKey();
        SuccessOrExit(err);
    }

    err = ChallengerAuthDelegate->GetTokenAuthData(TokenId, takeConfig, AuthenticationKey, authKeyLen, EncryptedAuthenticationKey, encAuthBlobLen);
    SuccessOrExit(err);

    VerifyOrExit(authKeyLen == kAuthenticationKeySize, err = WEAVE_ERROR_INVALID_ARGUMENT);
    VerifyOrExit(encAuthBlobLen == kTokenEncryptedStateSize, err = WEAVE_ERROR_INVALID_ARGUMENT);

    if (takeConfig == ChosenConfiguration)
    {
        err = WEAVE_ERROR_TAKE_REAUTH_POSSIBLE;
    }

    State = kState_InitiatorIdentifyTokenResponseProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateTokenReconfigureMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t* p = msgBuf->Start();

    VerifyOrExit(State == kState_Reset, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= kTokenRecongfigureMsgSize, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    *p++ = kTAKEConfig_Config1;

    msgBuf->SetDataLength(kTokenRecongfigureMsgSize);

    State = kState_ResponderDone;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessTokenReconfigureMessage(uint8_t& config, const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    const uint8_t* p = msgBuf->Start();

    VerifyOrExit(msgLen == kTokenRecongfigureMsgSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_InitiatorIdentifyTokenGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    config = *p++;

    VerifyOrExit(config == kTAKEConfig_Config1, err = WEAVE_ERROR_UNSUPPORTED_TAKE_CONFIGURATION);

    State = kState_InitiatorReconfigureProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateAuthenticateTokenMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t* hmacSignature = msgBuf->Start();
    uint8_t* challengerECDHPublicKey = hmacSignature + kConfig1_HMACSignatureSize;
    uint16_t msgLen = kConfig1_HMACSignatureSize + GetECPointLen();

    VerifyOrExit(State == kState_InitiatorIdentifyTokenResponseProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= msgLen, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    EncodedECPrivateKey privKey;
    EncodedECPublicKey pubKey;
    pubKey.ECPoint = ECDHPublicKeyBuffer;
    pubKey.ECPointLen = sizeof(ECDHPublicKeyBuffer);
    privKey.PrivKey = ECDHPrivateKeyBuffer;
    privKey.PrivKeyLen = sizeof(ECDHPrivateKeyBuffer);
    err = GenerateECDHKey(GetCurveOID(), pubKey, privKey);
    SuccessOrExit(err);

    ECDHPrivateKeyLength = privKey.PrivKeyLen;

    memcpy(challengerECDHPublicKey, ECDHPublicKeyBuffer, GetECPointLen());

    GenerateHMACSignature(IdentificationKey, hmacSignature, ECDHPublicKeyBuffer, GetECPointLen());

    msgBuf->SetDataLength(msgLen);

    State = kState_InitiatorAuthenticateTokenGenerated;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessAuthenticateTokenMessage(const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    uint8_t* p = msgBuf->Start();
    bool res;
    uint8_t* challengerECDHPublicKey = p + kConfig1_HMACSignatureSize;

    VerifyOrExit(msgLen >= kAuthenticateTokenMsgMinSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_ResponderIdentifyTokenResponseGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    uint8_t hmacBuffer[kConfig1_HMACSignatureSize];

    GenerateHMACSignature(IdentificationKey, hmacBuffer, challengerECDHPublicKey, GetECPointLen());

    res = ConstantTimeCompare(hmacBuffer, p, kConfig1_HMACSignatureSize);
    VerifyOrExit(res == true, err = WEAVE_ERROR_INVALID_TAKE_PARAMETER);

    memcpy(ECDHPublicKeyBuffer, challengerECDHPublicKey, GetECPointLen());

    State = kState_ResponderAuthenticateTokenProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateAuthenticateTokenResponseMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    Platform::Security::AES256BlockCipherEnc aes256BlockCipherEnc;
    uint8_t *encryptedState = msgBuf->Start();
    uint8_t *tokenECDHPublicKey =  encryptedState + kTokenEncryptedStateSize;
    uint8_t *ecdsaSignature = tokenECDHPublicKey + GetECPointLen();
    EncodedECPrivateKey tokenPrivKey;
    union
    {
        uint8_t tokenMasterKey[kTokenMasterKeySize];
        uint8_t tokenPrivateKeyBuffer[kMaxTokenPrivateKeySize];
    };
    tokenPrivKey.PrivKey = tokenPrivateKeyBuffer;
    tokenPrivKey.PrivKeyLen = kMaxTokenPrivateKeySize;
    OID tokenPrivKeyOID;
    uint16_t curveSize;
    uint16_t msgLen = kTokenEncryptedStateSize + kMaxECDSASignatureSize + GetECPointLen();

    VerifyOrExit(State == kState_ResponderAuthenticateTokenProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= msgLen, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    EncodedECPrivateKey privKey;
    EncodedECPublicKey pubKey;
    pubKey.ECPoint = tokenECDHPublicKey;
    pubKey.ECPointLen = GetECPointLen();
    privKey.PrivKey = ECDHPrivateKeyBuffer;
    privKey.PrivKeyLen = sizeof(ECDHPrivateKeyBuffer);
    err = GenerateECDHKey(GetCurveOID(), pubKey, privKey);
    SuccessOrExit(err);

    err = GenerateAuthenticationKey(ChallengerId, ECDHPrivateKeyBuffer, ECDHPublicKeyBuffer, privKey.PrivKeyLen);
    SuccessOrExit(err);

    err = TokenAuthDelegate->GetTokenMasterKey(tokenMasterKey);
    SuccessOrExit(err);

    aes256BlockCipherEnc.SetKey(tokenMasterKey);
    aes256BlockCipherEnc.EncryptBlock(AuthenticationKey, encryptedState);

    err = TokenAuthDelegate->GetTokenPrivateKey(tokenPrivKeyOID, tokenPrivKey);
    SuccessOrExit(err);

    curveSize = GetCurveSize(tokenPrivKeyOID);
    VerifyOrExit(curveSize != 0, err = WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);

    err = GenerateSignatureForAuthenticateTokenResponse(ecdsaSignature, ECDHPublicKeyBuffer, tokenECDHPublicKey, tokenPrivKey, encryptedState, tokenPrivKeyOID);
    SuccessOrExit(err);

    msgBuf->SetDataLength(kTokenEncryptedStateSize + 2 * curveSize + GetECPointLen());

    State = kState_ResponderAuthenticateTokenResponseGenerated;

exit:
    ClearSecretData(tokenMasterKey, sizeof(tokenMasterKey));
    ClearSecretData(tokenPrivateKeyBuffer, sizeof(tokenPrivateKeyBuffer));
    ClearSecretData(ECDHPrivateKeyBuffer, sizeof(ECDHPrivateKeyBuffer));
    aes256BlockCipherEnc.Reset();

    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessAuthenticateTokenResponseMessage(const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    uint8_t *encryptedState = msgBuf->Start();
    uint8_t *tokenECDHPublicKey =  encryptedState + kTokenEncryptedStateSize;
    uint8_t *ecdsaSignature = tokenECDHPublicKey + GetECPointLen();
    EncodedECPublicKey encodedPubKey;
    uint8_t TPub[GetECPointLen()];
    OID tokenPubKeyOID;
    uint16_t curveSize;

    VerifyOrExit(State == kState_InitiatorAuthenticateTokenGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    encodedPubKey.ECPoint = TPub;
    encodedPubKey.ECPointLen = GetECPointLen();

    err = ChallengerAuthDelegate->GetTokenPublicKey(TokenId, tokenPubKeyOID, encodedPubKey);
    SuccessOrExit(err);

    curveSize = GetCurveSize(tokenPubKeyOID);
    VerifyOrExit(curveSize != 0, err = WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);

    VerifyOrExit(msgLen == kAuthenticateTokenResponseMsgMinSize + 2 * curveSize + GetECPointLen(), err = WEAVE_ERROR_MESSAGE_INCOMPLETE);

    err = GenerateAuthenticationKey(ChallengerId, ECDHPrivateKeyBuffer, tokenECDHPublicKey, ECDHPrivateKeyLength);
    SuccessOrExit(err);

    err = ChallengerAuthDelegate->StoreTokenAuthData(TokenId, ChosenConfiguration, AuthenticationKey, kAuthenticationKeySize, encryptedState, kTokenEncryptedStateSize);
    SuccessOrExit(err);

    err = VerifySignatureForAuthenticateTokenResponse(ecdsaSignature, ECDHPublicKeyBuffer, tokenECDHPublicKey, encryptedState, tokenPubKeyOID, encodedPubKey);
    SuccessOrExit(err);

    State = kState_InitiatorAuthenticateTokenResponseProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateReAuthenticateTokenMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t* tokenEncryptedState = msgBuf->Start();
    uint8_t* hmacSignature = tokenEncryptedState + kTokenEncryptedStateSize;

    VerifyOrExit(State == kState_InitiatorIdentifyTokenResponseProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= kReAuthenticateTokenMsgSize, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    memcpy(tokenEncryptedState, EncryptedAuthenticationKey, kTokenEncryptedStateSize);

    GenerateHMACSignature(IdentificationKey, hmacSignature, tokenEncryptedState, kTokenEncryptedStateSize);

    msgBuf->SetDataLength(kReAuthenticateTokenMsgSize);

    State = kState_InitiatorReAuthenticateTokenGenerated;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateProtocolEncryptionKey()
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t sessionKey[WeaveEncryptionKey_AES128CTRSHA1::KeySize];
    HKDFSHA1 hkdf;

    uint8_t keySaltLen = sizeof(ControlHeader) + sizeof(EncryptionType) + sizeof(ProtocolConfig) +
        sizeof(uint8_t) * GetNumOptionalConfigurations() + sizeof(SessionKeyId) + sizeof(ChosenConfiguration) + kNonceSize + kNonceSize +
        sizeof(kSaltProtocolEncryption);
    uint8_t keySalt[kMaxProtocolEncryptionKeySaltSize];
    uint8_t* p = keySalt;

    WriteArray(&ControlHeader, p, sizeof(ControlHeader));
    WriteArray(&EncryptionType, p, sizeof(EncryptionType));
    WriteArray(&ProtocolConfig, p, sizeof(ProtocolConfig));
    WriteArray(OptionalConfigurations, p, sizeof(uint8_t) * GetNumOptionalConfigurations());
    LittleEndian::Write16(p, SessionKeyId);
    WriteArray(&ChosenConfiguration, p, sizeof(ChosenConfiguration));
    WriteArray(ChallengerNonce, p, kNonceSize);
    WriteArray(TokenNonce, p, kNonceSize);
    WriteArray(kSaltProtocolEncryption, p, sizeof(kSaltProtocolEncryption));

    hkdf.BeginExtractKey(keySalt, keySaltLen);

    hkdf.AddKeyMaterial(IdentificationKey, kIdentificationKeySize);

    err = hkdf.FinishExtractKey();
    SuccessOrExit(err);

    err = hkdf.ExpandKey(NULL, 0, WeaveEncryptionKey_AES128CTRSHA1::KeySize, sessionKey);
    SuccessOrExit(err);

    memcpy(EncryptionKey.AES128CTRSHA1.DataKey,
        sessionKey,
        WeaveEncryptionKey_AES128CTRSHA1::DataKeySize);
    memcpy(EncryptionKey.AES128CTRSHA1.IntegrityKey,
        sessionKey + WeaveEncryptionKey_AES128CTRSHA1::DataKeySize,
        WeaveEncryptionKey_AES128CTRSHA1::IntegrityKeySize);

    KeyState = kEncryptionKeyState_Initialized;

exit:
    ClearSecretData(sessionKey, sizeof(sessionKey));

    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessReAuthenticateTokenMessage(const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    uint8_t* tokenEncryptedState = msgBuf->Start();
    uint8_t* hmacSignature = tokenEncryptedState + kTokenEncryptedStateSize;
    bool res;
    uint8_t tokenMasterKey[kTokenMasterKeySize];
    Platform::Security::AES256BlockCipherDec aes256BlockCipherDec;

    VerifyOrExit(msgLen == kReAuthenticateTokenMsgSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_ResponderIdentifyTokenResponseGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    uint8_t hmacBuffer[kConfig1_HMACSignatureSize];
    GenerateHMACSignature(IdentificationKey, hmacBuffer, tokenEncryptedState, kTokenEncryptedStateSize);

    res = ConstantTimeCompare(hmacBuffer, hmacSignature, kConfig1_HMACSignatureSize);
    VerifyOrExit(res == true, err = WEAVE_ERROR_INVALID_SIGNATURE);

    err = TokenAuthDelegate->GetTokenMasterKey(tokenMasterKey);
    SuccessOrExit(err);

    aes256BlockCipherDec.SetKey(tokenMasterKey);
    aes256BlockCipherDec.DecryptBlock(tokenEncryptedState, AuthenticationKey);

    State = kState_ResponderReAuthenticateTokenProcessed;

exit:
    ClearSecretData(tokenMasterKey, sizeof(tokenMasterKey));
    aes256BlockCipherDec.Reset();

    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateReAuthenticateTokenResponseMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    Crypto::HMACSHA1 hmac;
    uint8_t* p = msgBuf->Start();

    VerifyOrExit(State == kState_ResponderReAuthenticateTokenProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= kReAuthenticateTokenResponseMsgSize, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    GenerateHMACSignature(AuthenticationKey, p, NULL, 0, kAuthenticationKeySize);

    msgBuf->SetDataLength(kReAuthenticateTokenResponseMsgSize);

    State = kState_ResponderReAuthenticateTokenResponseGenerated;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessReAuthenticateTokenResponseMessage(const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t* hmacSignature = msgBuf->Start();
    uint16_t msgLen = msgBuf->DataLength();
    bool res;

    VerifyOrExit(msgLen == kReAuthenticateTokenResponseMsgSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_InitiatorReAuthenticateTokenGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    uint8_t hmacBuffer[kConfig1_HMACSignatureSize];
    GenerateHMACSignature(AuthenticationKey, hmacBuffer, NULL, 0, kAuthenticationKeySize);

    res = ConstantTimeCompare(hmacBuffer, hmacSignature, kConfig1_HMACSignatureSize);
    VerifyOrExit(res == true, err = WEAVE_ERROR_INVALID_SIGNATURE);

    State = kState_InitiatorReAuthenticateTokenResponseProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GetSessionKey(const WeaveEncryptionKey *& encKey) const
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;

    VerifyOrExit(KeyState == kEncryptionKeyState_Initialized, err = WEAVE_ERROR_INCORRECT_STATE);

    encKey = &EncryptionKey;

exit:
    return err;
}

uint8_t WeaveTAKEEngine::GetEncryptionType()
{
    return EncryptionType;
}

void WeaveTAKEEngine::GenerateHMACSignature(const uint8_t* key, uint8_t* dest, const uint8_t* additionalField, uint8_t additionalFieldLength, uint16_t keyLength)
{
    Crypto::HMACSHA1 hmac;

    uint8_t SessionKeyIdArray[sizeof(SessionKeyId)];
    LittleEndian::Put16(SessionKeyIdArray, SessionKeyId);

    hmac.Begin(key, keyLength);
    hmac.AddData(&ControlHeader, sizeof(ControlHeader));
    hmac.AddData(&EncryptionType, sizeof(EncryptionType));
    hmac.AddData(&ProtocolConfig, sizeof(ProtocolConfig));
    hmac.AddData(OptionalConfigurations, sizeof(uint8_t) * GetNumOptionalConfigurations());
    hmac.AddData(SessionKeyIdArray, sizeof(SessionKeyId));
    hmac.AddData(&ChosenConfiguration, sizeof(ChosenConfiguration));
    hmac.AddData(ChallengerNonce, kNonceSize);
    hmac.AddData(TokenNonce, kNonceSize);
    if (additionalFieldLength != 0)
    {
        hmac.AddData(additionalField, additionalFieldLength);
    }
    hmac.Finish(dest);
}

WEAVE_ERROR WeaveTAKEEngine::GenerateAuthenticationKey(const uint8_t* challengerId, uint8_t* privateKey, uint8_t* publicKey, uint16_t privateKeyLen)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    HKDFSHA1 hkdf;
    uint8_t sharedSecret[kMaxCurveSize];
    uint16_t sharedSecretLen;
    uint8_t keySaltLen = ChallengerIdLen + sizeof(ControlHeader) + sizeof(EncryptionType) + sizeof(ProtocolConfig) +
        sizeof(uint8_t) * GetNumOptionalConfigurations() + sizeof(SessionKeyId) + sizeof(ChosenConfiguration) + kNonceSize + kNonceSize;
    uint8_t keySalt[kMaxAuthenticationKeySaltSize];
    uint8_t* p = keySalt;

    EncodedECPrivateKey encodedPrivKey;
    EncodedECPublicKey encodedPubKey;

    encodedPubKey.ECPoint = publicKey;
    encodedPubKey.ECPointLen = GetECPointLen();
    encodedPrivKey.PrivKey = privateKey;
    encodedPrivKey.PrivKeyLen = privateKeyLen;

    err = ECDHComputeSharedSecret(GetCurveOID(), encodedPubKey, encodedPrivKey, sharedSecret, sizeof(sharedSecret), sharedSecretLen);
    SuccessOrExit(err);

    WriteArray(challengerId, p, ChallengerIdLen);
    WriteArray(&ControlHeader, p, sizeof(ControlHeader));
    WriteArray(&EncryptionType, p, sizeof(EncryptionType));
    WriteArray(&ProtocolConfig, p, sizeof(ProtocolConfig));
    WriteArray(OptionalConfigurations, p, sizeof(uint8_t) * GetNumOptionalConfigurations());
    LittleEndian::Write16(p, SessionKeyId);
    WriteArray(&ChosenConfiguration, p, sizeof(ChosenConfiguration));
    WriteArray(ChallengerNonce, p, kNonceSize);
    WriteArray(TokenNonce, p, kNonceSize);

    hkdf.BeginExtractKey(keySalt, keySaltLen);
    hkdf.AddKeyMaterial(sharedSecret, sharedSecretLen);

    err = hkdf.FinishExtractKey();
    SuccessOrExit(err);

    err = hkdf.ExpandKey(NULL, 0, kAuthenticationKeySize, AuthenticationKey);
    SuccessOrExit(err);

exit:
    ClearSecretData(sharedSecret, sizeof(sharedSecret));

    return err;
}

void WeaveTAKEEngine::GenerateHashForAuthenticateTokenResponse(uint8_t* dest, const uint8_t* challengerECDHPublicKey, const uint8_t* tokenECDHPublicKey, const uint8_t* encryptedState)
{
    Platform::Security::SHA1 sha1;

    uint8_t SessionKeyIdArray[sizeof(SessionKeyId)];
    LittleEndian::Put16(SessionKeyIdArray, SessionKeyId);

    sha1.Begin();
    sha1.AddData(&ControlHeader, sizeof(ControlHeader));
    sha1.AddData(&EncryptionType, sizeof(EncryptionType));
    sha1.AddData(&ProtocolConfig, sizeof(ProtocolConfig));
    sha1.AddData(OptionalConfigurations, sizeof(uint8_t) * GetNumOptionalConfigurations());
    sha1.AddData(SessionKeyIdArray, sizeof(SessionKeyId));
    sha1.AddData(&ChosenConfiguration, sizeof(ChosenConfiguration));
    sha1.AddData(ChallengerNonce, kNonceSize);
    sha1.AddData(TokenNonce, kNonceSize);
    sha1.AddData(challengerECDHPublicKey, GetECPointLen());
    sha1.AddData(tokenECDHPublicKey, GetECPointLen());
    sha1.AddData(encryptedState, kTokenEncryptedStateSize);
    sha1.Finish(dest);
}

WEAVE_ERROR WeaveTAKEEngine::GenerateSignatureForAuthenticateTokenResponse(uint8_t* dest, const uint8_t* challengerECDHPublicKey, const uint8_t* tokenECDHPublicKey,
                                                                           EncodedECPrivateKey TPriv, const uint8_t* encryptedState, OID& curveOID)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t messageHash[kConfig1_HMACSignatureSize];

    GenerateHashForAuthenticateTokenResponse(messageHash, challengerECDHPublicKey, tokenECDHPublicKey, encryptedState);

    err = GenerateECDSASignature(curveOID, messageHash, kConfig1_HMACSignatureSize, TPriv, dest);
    SuccessOrExit(err);

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::VerifySignatureForAuthenticateTokenResponse(const uint8_t* signature, const uint8_t* challengerECDHPublicKey, const uint8_t* tokenECDHPublicKey,
                                                                         const uint8_t* encryptedState, OID& curveOID, EncodedECPublicKey& encodedPubKey)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t messageHash[kConfig1_HMACSignatureSize];

    GenerateHashForAuthenticateTokenResponse(messageHash, challengerECDHPublicKey, tokenECDHPublicKey, encryptedState);

    err = VerifyECDSASignature(curveOID, messageHash, kConfig1_HMACSignatureSize, signature, encodedPubKey);
    SuccessOrExit(err);

exit:
    return err;
}

void WeaveTAKEEngine::ReadArray(uint8_t* dest, const uint8_t*& src, uint8_t length)
{
    memcpy(dest, src, length);
    src += length;
}

void WeaveTAKEEngine::WriteArray(const uint8_t* src, uint8_t*& dest, uint8_t length)
{
    memcpy(dest, src, length);
    dest += length;
}

bool WeaveTAKEEngine::UseSessionKey() const
{
    return IsEncryptAuthPhase() || IsEncryptCommPhase();
}

uint16_t WeaveTAKEEngine::GetCurveLen() const
{

    return kConfig1_CurveSize;
}

uint16_t WeaveTAKEEngine::GetPrivKeyLen() const
{

    return kConfig1_PrivKeySize;
}

uint16_t WeaveTAKEEngine::GetECPointLen() const
{

    return kConfig1_ECPointX962FormatSize;
}

OID WeaveTAKEEngine::GetCurveOID() const
{

    return kOID_EllipticCurve_secp224r1;
}

} 
} 
} 
} 
}
/*
 *
 *    Copyright (c) 2015-2017 Nest Labs, Inc.
 *    All rights reserved.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

/**
 *    @file
 *      This file implements objects for initiators and responders for
 *      the Weave Token Authenticated Key Exchange (TAKE) protocol.
 *
 */

#include <Weave/Core/WeaveCore.h>
#include "WeaveTAKE.h"
#include "WeaveSecurity.h"
#include <Weave/Support/crypto/WeaveCrypto.h>
#include <Weave/Support/crypto/HashAlgos.h>
#include <Weave/Support/crypto/EllipticCurve.h>
#include <Weave/Support/CodeUtils.h>
#include <Weave/Support/ErrorStr.h>
#include <Weave/Support/ASN1.h>
#include <Weave/Support/crypto/WeaveCrypto.h>
#include <Weave/Support/crypto/AESBlockCipher.h>

namespace nl {
namespace Weave {
namespace Profiles {
namespace Security {
namespace TAKE {

using namespace nl::Weave::Encoding;
using namespace nl::Weave::Crypto;
using namespace nl::Weave::ASN1;

void WeaveTAKEEngine::Init()
{
    State = kState_Reset;
    KeyState = kEncryptionKeyState_Uninitialized;
    SessionKeyId = WeaveKeyId::kNone;
}

void WeaveTAKEEngine::Shutdown()
{
    ClearSecretData(IdentificationKey, sizeof(IdentificationKey));
    ClearSecretData(AuthenticationKey, sizeof(AuthenticationKey));
    ClearSecretData(ECDHPrivateKeyBuffer, sizeof(ECDHPrivateKeyBuffer));
    State = kState_Reset;
    KeyState = kEncryptionKeyState_Uninitialized;
    SessionKeyId = WeaveKeyId::kNone;
}

static WEAVE_ERROR PackControlHeader(uint8_t numOptionalConfigurations, bool encryptAuthPhase, bool encryptCommPhase, bool timeLimitedIK, bool hasChallengerId, uint8_t& controlHeader)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;

    VerifyOrExit(numOptionalConfigurations < kMaxOptionalConfigurations, err = WEAVE_ERROR_INVALID_ARGUMENT);
    controlHeader = (numOptionalConfigurations << kControlHeader_NumOptionalConfigurationShift) & kControlHeader_NumOptionalConfigurationMask;

    if (encryptAuthPhase)
        controlHeader |= kControlHeader_EncryptAuthenticationPhaseFlag;
    if (encryptCommPhase)
        controlHeader |= kControlHeader_EncryptCommunicationsPhaseFlag;
    if (timeLimitedIK)
        controlHeader |= kControlHeader_TimeLimitFlag;
    if (hasChallengerId)
        controlHeader |= kControlHeader_HasChallengerIdFlag;

exit:
    return err;
}

uint8_t WeaveTAKEEngine::GetNumOptionalConfigurations() const
{
    return static_cast<uint8_t>((ControlHeader & kControlHeader_NumOptionalConfigurationMask) >> kControlHeader_NumOptionalConfigurationShift);
}

bool WeaveTAKEEngine::IsEncryptAuthPhase() const
{
    return (ControlHeader & kControlHeader_EncryptAuthenticationPhaseFlag) != 0;
}

bool WeaveTAKEEngine::IsEncryptCommPhase() const
{
    return (ControlHeader & kControlHeader_EncryptCommunicationsPhaseFlag) != 0;
}

bool WeaveTAKEEngine::IsTimeLimitedIK() const
{
    return (ControlHeader & kControlHeader_TimeLimitFlag) != 0;
}

bool WeaveTAKEEngine::HasSentChallengerId() const
{
    return (ControlHeader & kControlHeader_HasChallengerIdFlag) != 0;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateIdentifyTokenMessage(uint16_t sessionKeyId, uint8_t takeConfig, bool encryptAuthPhase, bool encryptCommPhase, bool timeLimitedIK,
                                                              bool sendChallengerId, uint8_t encryptionType, uint64_t localNodeId, PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t* p = msgBuf->Start();
    uint16_t msgLen;

    VerifyOrExit(State == kState_Reset || State == kState_InitiatorReconfigureProcessed, err = WEAVE_ERROR_INCORRECT_STATE);

    PackControlHeader(0, encryptAuthPhase, encryptCommPhase, timeLimitedIK, sendChallengerId, ControlHeader);

    if (sendChallengerId)
    {
        ChallengerIdLen = kMaxChallengerIdSize;
        err = ChallengerAuthDelegate->GetChallengerID(ChallengerId, ChallengerIdLen);
        SuccessOrExit(err);

        VerifyOrExit(ChallengerIdLen <= kMaxChallengerIdSize, err = WEAVE_ERROR_INVALID_ARGUMENT);
    }
    else
    {
        uint8_t* ChallengerIdPtr = ChallengerId;
        LittleEndian::Write64(ChallengerIdPtr, localNodeId);
        ChallengerIdLen = 8;
    }

    msgLen = kIdentifyTokenMsgMinSize + (sendChallengerId ? 1 + ChallengerIdLen : 0) + GetNumOptionalConfigurations() + (UseSessionKey() ? 2 : 0);
    VerifyOrExit(msgBuf->AvailableDataLength() >= msgLen, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    err = Platform::Security::GetSecureRandomData(ChallengerNonce, kNonceSize);
    SuccessOrExit(err);

    *p++ = ControlHeader;

    if (sendChallengerId)
    {
        *p++ = ChallengerIdLen;
    }

    EncryptionType = encryptionType;
    *p++ = EncryptionType;

    VerifyOrExit(takeConfig == kTAKEConfig_Config1, err = WEAVE_ERROR_UNSUPPORTED_TAKE_CONFIGURATION);
    ProtocolConfig = takeConfig;
    *p++ = ProtocolConfig;

    // No optional configuration for now

    if (UseSessionKey())
    {
        SessionKeyId = sessionKeyId;
        LittleEndian::Write16(p, SessionKeyId);
    }

    if (sendChallengerId)
    {
        WriteArray(ChallengerId, p, ChallengerIdLen);
    }

    WriteArray(ChallengerNonce, p, kNonceSize);

    msgBuf->SetDataLength(msgLen);
    State = kState_InitiatorIdentifyTokenGenerated;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessIdentifyTokenMessage(uint64_t peerNodeId, const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    const uint8_t* p = msgBuf->Start();

    VerifyOrExit(msgLen >= kIdentifyTokenMsgMinSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_Reset, err = WEAVE_ERROR_INCORRECT_STATE);

    ControlHeader = *p++;

    if (HasSentChallengerId())
    {
        ChallengerIdLen = *p++;
        VerifyOrExit(ChallengerIdLen <= kMaxChallengerIdSize, err = WEAVE_ERROR_INVALID_ARGUMENT);
    }
    else
    {
        uint8_t* ChallengerIdPtr = ChallengerId;
        LittleEndian::Write64(ChallengerIdPtr, peerNodeId);
        ChallengerIdLen = 8;
    }

    VerifyOrExit(msgLen == (kIdentifyTokenMsgMinSize + (HasSentChallengerId() ? 1 + ChallengerIdLen : 0) + GetNumOptionalConfigurations() + (UseSessionKey() ? 2 : 0)), err = WEAVE_ERROR_MESSAGE_INCOMPLETE);

    EncryptionType = *p++;
    VerifyOrExit(EncryptionType == kWeaveEncryptionType_AES128CTRSHA1, err = WEAVE_ERROR_UNSUPPORTED_ENCRYPTION_TYPE);

    ProtocolConfig = *p++;

    ReadArray(OptionalConfigurations, p, GetNumOptionalConfigurations() * sizeof(uint8_t));

    // Choosing the first valid configuration, for now choosing the first one which is Config1
    ChosenConfiguration = ProtocolConfig;
    if (ChosenConfiguration != kTAKEConfig_Config1)
    {
        for (int i = 0; i < GetNumOptionalConfigurations(); i++)
        {
            if (OptionalConfigurations[i] == kTAKEConfig_Config1)
            {
                ChosenConfiguration = OptionalConfigurations[i];
                break;
            }
        }
    }

    VerifyOrExit(ChosenConfiguration == kTAKEConfig_Config1, err = WEAVE_ERROR_TAKE_RECONFIGURE_REQUIRED);

    if (UseSessionKey())
    {
        SessionKeyId = LittleEndian::Read16(p);
    }

    if (HasSentChallengerId())
    {
        ReadArray(ChallengerId, p, ChallengerIdLen);
    }

    ReadArray(ChallengerNonce, p, kNonceSize);

    State = kState_ResponderIdentifyTokenProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateIdentifyTokenResponseMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    Platform::Security::SHA1 sha1;
    uint8_t* p = msgBuf->Start();
    uint8_t identificationRootKey[kIdentificationRootKeySize];
    HKDFSHA1 hkdf;
    uint8_t keySaltLen = ChallengerIdLen + sizeof(uint32_t);
    uint8_t keySalt[kMaxIdentifyTokenResponseKeySaltSize];

    VerifyOrExit(State == kState_ResponderIdentifyTokenProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= kIdentifyTokenResponseMsgSize, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    err = Platform::Security::GetSecureRandomData(TokenNonce, kNonceSize);
    SuccessOrExit(err);

    err = TokenAuthDelegate->GetIdentificationRootKey(identificationRootKey);
    SuccessOrExit(err);

    *p++ = ChosenConfiguration;

    WriteArray(TokenNonce, p, kNonceSize);

    {
        uint8_t* keySaltPtr = keySalt;
        WriteArray(ChallengerId, keySaltPtr, ChallengerIdLen);
        if (!IsTimeLimitedIK())
        {
            WriteArray(kSaltTimeUnlimitedIdentificationKey, keySaltPtr, sizeof(kSaltTimeUnlimitedIdentificationKey));
        }
        else
        {
            uint32_t time;
            err = TokenAuthDelegate->GetTAKETime(time);
            SuccessOrExit(err);

            LittleEndian::Write32(keySaltPtr, time);
        }

        hkdf.BeginExtractKey(keySalt, keySaltLen);

        hkdf.AddKeyMaterial(identificationRootKey, kIdentificationRootKeySize);

        err = hkdf.FinishExtractKey();
        SuccessOrExit(err);

        err = hkdf.ExpandKey(NULL, 0, kIdentificationKeySize, IdentificationKey);
        SuccessOrExit(err);
    }

    GenerateHMACSignature(IdentificationKey, p);

    msgBuf->SetDataLength(kIdentifyTokenResponseMsgSize);

    if (UseSessionKey())
    {
        err = GenerateProtocolEncryptionKey();
        SuccessOrExit(err);
    }

    State = kState_ResponderIdentifyTokenResponseGenerated;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessIdentifyTokenResponseMessage(const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    const uint8_t* p = msgBuf->Start();
    bool isAuthorisedIK = false;
    int i, end;
    uint8_t takeConfig;
    uint16_t authKeyLen = kAuthenticationKeySize;
    uint16_t encAuthBlobLen = kTokenEncryptedStateSize;

    VerifyOrExit(msgLen == kIdentifyTokenResponseMsgSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_InitiatorIdentifyTokenGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    ChosenConfiguration = *p++;

    ReadArray(TokenNonce, p, kNonceSize);

    if (ChosenConfiguration != ProtocolConfig)
    {
        bool isProposedConfiguration = false;
        for (i = 0, end = GetNumOptionalConfigurations(); i < end; i++)
        {
            if (ChosenConfiguration == OptionalConfigurations[i])
            {
                isProposedConfiguration = true;
                break;
            }
        }
        VerifyOrExit(isProposedConfiguration, err = WEAVE_ERROR_UNSUPPORTED_TAKE_CONFIGURATION);
    }

    uint8_t hmacBuffer[kConfig1_HMACSignatureSize];

    ChallengerAuthDelegate->RewindIdentificationKeyIterator();

    while (1)
    {
        uint16_t identificationKeyLen = kIdentificationKeySize; // TODO unused

        err = ChallengerAuthDelegate->GetNextIdentificationKey(TokenId, IdentificationKey, identificationKeyLen);
        SuccessOrExit(err);

        if (TokenId == kNodeIdNotSpecified)
        {
            VerifyOrExit(identificationKeyLen == kIdentificationKeySize, err = WEAVE_ERROR_INVALID_ARGUMENT);
            break;
        }
        GenerateHMACSignature(IdentificationKey, hmacBuffer);

        if (ConstantTimeCompare(hmacBuffer, p, kConfig1_HMACSignatureSize))
        {
            isAuthorisedIK = true;
            break;
        }
    }

    VerifyOrExit(isAuthorisedIK, err = WEAVE_ERROR_TAKE_TOKEN_IDENTIFICATION_FAILED);

    if (UseSessionKey())
    {
        err = GenerateProtocolEncryptionKey();
        SuccessOrExit(err);
    }

    err = ChallengerAuthDelegate->GetTokenAuthData(TokenId, takeConfig, AuthenticationKey, authKeyLen, EncryptedAuthenticationKey, encAuthBlobLen);
    SuccessOrExit(err);

    VerifyOrExit(authKeyLen == kAuthenticationKeySize, err = WEAVE_ERROR_INVALID_ARGUMENT);
    VerifyOrExit(encAuthBlobLen == kTokenEncryptedStateSize, err = WEAVE_ERROR_INVALID_ARGUMENT);

    if (takeConfig == ChosenConfiguration)
    {
        err = WEAVE_ERROR_TAKE_REAUTH_POSSIBLE;
    }

    State = kState_InitiatorIdentifyTokenResponseProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateTokenReconfigureMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t* p = msgBuf->Start();

    VerifyOrExit(State == kState_Reset, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= kTokenRecongfigureMsgSize, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    *p++ = kTAKEConfig_Config1;

    msgBuf->SetDataLength(kTokenRecongfigureMsgSize);

    State = kState_ResponderDone;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessTokenReconfigureMessage(uint8_t& config, const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    const uint8_t* p = msgBuf->Start();

    VerifyOrExit(msgLen == kTokenRecongfigureMsgSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_InitiatorIdentifyTokenGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    config = *p++;

    VerifyOrExit(config == kTAKEConfig_Config1, err = WEAVE_ERROR_UNSUPPORTED_TAKE_CONFIGURATION);

    State = kState_InitiatorReconfigureProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateAuthenticateTokenMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t* hmacSignature = msgBuf->Start();
    uint8_t* challengerECDHPublicKey = hmacSignature + kConfig1_HMACSignatureSize;
    uint16_t msgLen = kConfig1_HMACSignatureSize + GetECPointLen();

    VerifyOrExit(State == kState_InitiatorIdentifyTokenResponseProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= msgLen, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    EncodedECPrivateKey privKey;
    EncodedECPublicKey pubKey;
    pubKey.ECPoint = ECDHPublicKeyBuffer;
    pubKey.ECPointLen = sizeof(ECDHPublicKeyBuffer);
    privKey.PrivKey = ECDHPrivateKeyBuffer;
    privKey.PrivKeyLen = sizeof(ECDHPrivateKeyBuffer);
    err = GenerateECDHKey(GetCurveOID(), pubKey, privKey);
    SuccessOrExit(err);

    ECDHPrivateKeyLength = privKey.PrivKeyLen;

    memcpy(challengerECDHPublicKey, ECDHPublicKeyBuffer, GetECPointLen());

    GenerateHMACSignature(IdentificationKey, hmacSignature, ECDHPublicKeyBuffer, GetECPointLen());

    msgBuf->SetDataLength(msgLen);

    State = kState_InitiatorAuthenticateTokenGenerated;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessAuthenticateTokenMessage(const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    uint8_t* p = msgBuf->Start();
    bool res;
    uint8_t* challengerECDHPublicKey = p + kConfig1_HMACSignatureSize;

    VerifyOrExit(msgLen >= kAuthenticateTokenMsgMinSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_ResponderIdentifyTokenResponseGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    uint8_t hmacBuffer[kConfig1_HMACSignatureSize];

    GenerateHMACSignature(IdentificationKey, hmacBuffer, challengerECDHPublicKey, GetECPointLen());

    res = ConstantTimeCompare(hmacBuffer, p, kConfig1_HMACSignatureSize);
    VerifyOrExit(res == true, err = WEAVE_ERROR_INVALID_TAKE_PARAMETER);

    memcpy(ECDHPublicKeyBuffer, challengerECDHPublicKey, GetECPointLen());

    State = kState_ResponderAuthenticateTokenProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateAuthenticateTokenResponseMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    Platform::Security::AES256BlockCipherEnc aes256BlockCipherEnc;
    uint8_t *encryptedState = msgBuf->Start();
    uint8_t *tokenECDHPublicKey =  encryptedState + kTokenEncryptedStateSize;
    uint8_t *ecdsaSignature = tokenECDHPublicKey + GetECPointLen();
    EncodedECPrivateKey tokenPrivKey;
    union
    {
        uint8_t tokenMasterKey[kTokenMasterKeySize];
        uint8_t tokenPrivateKeyBuffer[kMaxTokenPrivateKeySize];
    };
    tokenPrivKey.PrivKey = tokenPrivateKeyBuffer;
    tokenPrivKey.PrivKeyLen = kMaxTokenPrivateKeySize;
    OID tokenPrivKeyOID;
    uint16_t curveSize;
    uint16_t msgLen = kTokenEncryptedStateSize + kMaxECDSASignatureSize + GetECPointLen();

    VerifyOrExit(State == kState_ResponderAuthenticateTokenProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= msgLen, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    EncodedECPrivateKey privKey;
    EncodedECPublicKey pubKey;
    pubKey.ECPoint = tokenECDHPublicKey;
    pubKey.ECPointLen = GetECPointLen();
    privKey.PrivKey = ECDHPrivateKeyBuffer;
    privKey.PrivKeyLen = sizeof(ECDHPrivateKeyBuffer);
    err = GenerateECDHKey(GetCurveOID(), pubKey, privKey);
    SuccessOrExit(err);

    err = GenerateAuthenticationKey(ChallengerId, ECDHPrivateKeyBuffer, ECDHPublicKeyBuffer, privKey.PrivKeyLen);
    SuccessOrExit(err);

    err = TokenAuthDelegate->GetTokenMasterKey(tokenMasterKey);
    SuccessOrExit(err);

    aes256BlockCipherEnc.SetKey(tokenMasterKey);
    aes256BlockCipherEnc.EncryptBlock(AuthenticationKey, encryptedState);

    err = TokenAuthDelegate->GetTokenPrivateKey(tokenPrivKeyOID, tokenPrivKey);
    SuccessOrExit(err);

    curveSize = GetCurveSize(tokenPrivKeyOID);
    VerifyOrExit(curveSize != 0, err = WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);

    err = GenerateSignatureForAuthenticateTokenResponse(ecdsaSignature, ECDHPublicKeyBuffer, tokenECDHPublicKey, tokenPrivKey, encryptedState, tokenPrivKeyOID);
    SuccessOrExit(err);

    msgBuf->SetDataLength(kTokenEncryptedStateSize + 2 * curveSize + GetECPointLen());

    State = kState_ResponderAuthenticateTokenResponseGenerated;

exit:
    ClearSecretData(tokenMasterKey, sizeof(tokenMasterKey));
    ClearSecretData(tokenPrivateKeyBuffer, sizeof(tokenPrivateKeyBuffer));
    ClearSecretData(ECDHPrivateKeyBuffer, sizeof(ECDHPrivateKeyBuffer));
    aes256BlockCipherEnc.Reset();

    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessAuthenticateTokenResponseMessage(const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    uint8_t *encryptedState = msgBuf->Start();
    uint8_t *tokenECDHPublicKey =  encryptedState + kTokenEncryptedStateSize;
    uint8_t *ecdsaSignature = tokenECDHPublicKey + GetECPointLen();
    EncodedECPublicKey encodedPubKey;
    uint8_t TPub[GetECPointLen()];
    OID tokenPubKeyOID;
    uint16_t curveSize;

    VerifyOrExit(State == kState_InitiatorAuthenticateTokenGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    encodedPubKey.ECPoint = TPub;
    encodedPubKey.ECPointLen = GetECPointLen();

    err = ChallengerAuthDelegate->GetTokenPublicKey(TokenId, tokenPubKeyOID, encodedPubKey);
    SuccessOrExit(err);

    curveSize = GetCurveSize(tokenPubKeyOID);
    VerifyOrExit(curveSize != 0, err = WEAVE_ERROR_UNSUPPORTED_ELLIPTIC_CURVE);

    VerifyOrExit(msgLen == kAuthenticateTokenResponseMsgMinSize + 2 * curveSize + GetECPointLen(), err = WEAVE_ERROR_MESSAGE_INCOMPLETE);

    err = GenerateAuthenticationKey(ChallengerId, ECDHPrivateKeyBuffer, tokenECDHPublicKey, ECDHPrivateKeyLength);
    SuccessOrExit(err);

    err = ChallengerAuthDelegate->StoreTokenAuthData(TokenId, ChosenConfiguration, AuthenticationKey, kAuthenticationKeySize, encryptedState, kTokenEncryptedStateSize);
    SuccessOrExit(err);

    err = VerifySignatureForAuthenticateTokenResponse(ecdsaSignature, ECDHPublicKeyBuffer, tokenECDHPublicKey, encryptedState, tokenPubKeyOID, encodedPubKey);
    SuccessOrExit(err);

    State = kState_InitiatorAuthenticateTokenResponseProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateReAuthenticateTokenMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t* tokenEncryptedState = msgBuf->Start();
    uint8_t* hmacSignature = tokenEncryptedState + kTokenEncryptedStateSize;

    VerifyOrExit(State == kState_InitiatorIdentifyTokenResponseProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= kReAuthenticateTokenMsgSize, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    memcpy(tokenEncryptedState, EncryptedAuthenticationKey, kTokenEncryptedStateSize);

    GenerateHMACSignature(IdentificationKey, hmacSignature, tokenEncryptedState, kTokenEncryptedStateSize);

    msgBuf->SetDataLength(kReAuthenticateTokenMsgSize);

    State = kState_InitiatorReAuthenticateTokenGenerated;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateProtocolEncryptionKey()
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t sessionKey[WeaveEncryptionKey_AES128CTRSHA1::KeySize];
    HKDFSHA1 hkdf;

    uint8_t keySaltLen = sizeof(ControlHeader) + sizeof(EncryptionType) + sizeof(ProtocolConfig) +
        sizeof(uint8_t) * GetNumOptionalConfigurations() + sizeof(SessionKeyId) + sizeof(ChosenConfiguration) + kNonceSize + kNonceSize +
        sizeof(kSaltProtocolEncryption);
    uint8_t keySalt[kMaxProtocolEncryptionKeySaltSize];
    uint8_t* p = keySalt;

    WriteArray(&ControlHeader, p, sizeof(ControlHeader));
    WriteArray(&EncryptionType, p, sizeof(EncryptionType));
    WriteArray(&ProtocolConfig, p, sizeof(ProtocolConfig));
    WriteArray(OptionalConfigurations, p, sizeof(uint8_t) * GetNumOptionalConfigurations());
    LittleEndian::Write16(p, SessionKeyId);
    WriteArray(&ChosenConfiguration, p, sizeof(ChosenConfiguration));
    WriteArray(ChallengerNonce, p, kNonceSize);
    WriteArray(TokenNonce, p, kNonceSize);
    WriteArray(kSaltProtocolEncryption, p, sizeof(kSaltProtocolEncryption));

    hkdf.BeginExtractKey(keySalt, keySaltLen);

    hkdf.AddKeyMaterial(IdentificationKey, kIdentificationKeySize);

    err = hkdf.FinishExtractKey();
    SuccessOrExit(err);

    err = hkdf.ExpandKey(NULL, 0, WeaveEncryptionKey_AES128CTRSHA1::KeySize, sessionKey);
    SuccessOrExit(err);

    memcpy(EncryptionKey.AES128CTRSHA1.DataKey,
        sessionKey,
        WeaveEncryptionKey_AES128CTRSHA1::DataKeySize);
    memcpy(EncryptionKey.AES128CTRSHA1.IntegrityKey,
        sessionKey + WeaveEncryptionKey_AES128CTRSHA1::DataKeySize,
        WeaveEncryptionKey_AES128CTRSHA1::IntegrityKeySize);

    KeyState = kEncryptionKeyState_Initialized;

exit:
    ClearSecretData(sessionKey, sizeof(sessionKey));

    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessReAuthenticateTokenMessage(const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint16_t msgLen = msgBuf->DataLength();
    uint8_t* tokenEncryptedState = msgBuf->Start();
    uint8_t* hmacSignature = tokenEncryptedState + kTokenEncryptedStateSize;
    bool res;
    uint8_t tokenMasterKey[kTokenMasterKeySize];
    Platform::Security::AES256BlockCipherDec aes256BlockCipherDec;

    VerifyOrExit(msgLen == kReAuthenticateTokenMsgSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_ResponderIdentifyTokenResponseGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    uint8_t hmacBuffer[kConfig1_HMACSignatureSize];
    GenerateHMACSignature(IdentificationKey, hmacBuffer, tokenEncryptedState, kTokenEncryptedStateSize);

    res = ConstantTimeCompare(hmacBuffer, hmacSignature, kConfig1_HMACSignatureSize);
    VerifyOrExit(res == true, err = WEAVE_ERROR_INVALID_SIGNATURE);

    err = TokenAuthDelegate->GetTokenMasterKey(tokenMasterKey);
    SuccessOrExit(err);

    aes256BlockCipherDec.SetKey(tokenMasterKey);
    aes256BlockCipherDec.DecryptBlock(tokenEncryptedState, AuthenticationKey);

    State = kState_ResponderReAuthenticateTokenProcessed;

exit:
    ClearSecretData(tokenMasterKey, sizeof(tokenMasterKey));
    aes256BlockCipherDec.Reset();

    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GenerateReAuthenticateTokenResponseMessage(PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    Crypto::HMACSHA1 hmac;
    uint8_t* p = msgBuf->Start();

    VerifyOrExit(State == kState_ResponderReAuthenticateTokenProcessed, err = WEAVE_ERROR_INCORRECT_STATE);
    VerifyOrExit(msgBuf->AvailableDataLength() >= kReAuthenticateTokenResponseMsgSize, err = WEAVE_ERROR_BUFFER_TOO_SMALL);

    GenerateHMACSignature(AuthenticationKey, p, NULL, 0, kAuthenticationKeySize);

    msgBuf->SetDataLength(kReAuthenticateTokenResponseMsgSize);

    State = kState_ResponderReAuthenticateTokenResponseGenerated;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::ProcessReAuthenticateTokenResponseMessage(const PacketBuffer *msgBuf)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t* hmacSignature = msgBuf->Start();
    uint16_t msgLen = msgBuf->DataLength();
    bool res;

    VerifyOrExit(msgLen == kReAuthenticateTokenResponseMsgSize, err = WEAVE_ERROR_MESSAGE_INCOMPLETE);
    VerifyOrExit(State == kState_InitiatorReAuthenticateTokenGenerated, err = WEAVE_ERROR_INCORRECT_STATE);

    uint8_t hmacBuffer[kConfig1_HMACSignatureSize];
    GenerateHMACSignature(AuthenticationKey, hmacBuffer, NULL, 0, kAuthenticationKeySize);

    res = ConstantTimeCompare(hmacBuffer, hmacSignature, kConfig1_HMACSignatureSize);
    VerifyOrExit(res == true, err = WEAVE_ERROR_INVALID_SIGNATURE);

    State = kState_InitiatorReAuthenticateTokenResponseProcessed;

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::GetSessionKey(const WeaveEncryptionKey *& encKey) const
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;

    VerifyOrExit(KeyState == kEncryptionKeyState_Initialized, err = WEAVE_ERROR_INCORRECT_STATE);

    encKey = &EncryptionKey;

exit:
    return err;
}

uint8_t WeaveTAKEEngine::GetEncryptionType()
{
    return EncryptionType;
}

void WeaveTAKEEngine::GenerateHMACSignature(const uint8_t* key, uint8_t* dest, const uint8_t* additionalField, uint8_t additionalFieldLength, uint16_t keyLength)
{
    Crypto::HMACSHA1 hmac;

    uint8_t SessionKeyIdArray[sizeof(SessionKeyId)];
    LittleEndian::Put16(SessionKeyIdArray, SessionKeyId);

    hmac.Begin(key, keyLength);
    hmac.AddData(&ControlHeader, sizeof(ControlHeader));
    hmac.AddData(&EncryptionType, sizeof(EncryptionType));
    hmac.AddData(&ProtocolConfig, sizeof(ProtocolConfig));
    hmac.AddData(OptionalConfigurations, sizeof(uint8_t) * GetNumOptionalConfigurations());
    hmac.AddData(SessionKeyIdArray, sizeof(SessionKeyId));
    hmac.AddData(&ChosenConfiguration, sizeof(ChosenConfiguration));
    hmac.AddData(ChallengerNonce, kNonceSize);
    hmac.AddData(TokenNonce, kNonceSize);
    if (additionalFieldLength != 0)
    {
        hmac.AddData(additionalField, additionalFieldLength);
    }
    hmac.Finish(dest);
}

WEAVE_ERROR WeaveTAKEEngine::GenerateAuthenticationKey(const uint8_t* challengerId, uint8_t* privateKey, uint8_t* publicKey, uint16_t privateKeyLen)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    HKDFSHA1 hkdf;
    uint8_t sharedSecret[kMaxCurveSize];
    uint16_t sharedSecretLen;
    uint8_t keySaltLen = ChallengerIdLen + sizeof(ControlHeader) + sizeof(EncryptionType) + sizeof(ProtocolConfig) +
        sizeof(uint8_t) * GetNumOptionalConfigurations() + sizeof(SessionKeyId) + sizeof(ChosenConfiguration) + kNonceSize + kNonceSize;
    uint8_t keySalt[kMaxAuthenticationKeySaltSize];
    uint8_t* p = keySalt;

    EncodedECPrivateKey encodedPrivKey;
    EncodedECPublicKey encodedPubKey;

    encodedPubKey.ECPoint = publicKey;
    encodedPubKey.ECPointLen = GetECPointLen();
    encodedPrivKey.PrivKey = privateKey;
    encodedPrivKey.PrivKeyLen = privateKeyLen;

    err = ECDHComputeSharedSecret(GetCurveOID(), encodedPubKey, encodedPrivKey, sharedSecret, sizeof(sharedSecret), sharedSecretLen);
    SuccessOrExit(err);

    WriteArray(challengerId, p, ChallengerIdLen);
    WriteArray(&ControlHeader, p, sizeof(ControlHeader));
    WriteArray(&EncryptionType, p, sizeof(EncryptionType));
    WriteArray(&ProtocolConfig, p, sizeof(ProtocolConfig));
    WriteArray(OptionalConfigurations, p, sizeof(uint8_t) * GetNumOptionalConfigurations());
    LittleEndian::Write16(p, SessionKeyId);
    WriteArray(&ChosenConfiguration, p, sizeof(ChosenConfiguration));
    WriteArray(ChallengerNonce, p, kNonceSize);
    WriteArray(TokenNonce, p, kNonceSize);

    hkdf.BeginExtractKey(keySalt, keySaltLen);
    hkdf.AddKeyMaterial(sharedSecret, sharedSecretLen);

    err = hkdf.FinishExtractKey();
    SuccessOrExit(err);

    err = hkdf.ExpandKey(NULL, 0, kAuthenticationKeySize, AuthenticationKey);
    SuccessOrExit(err);

exit:
    ClearSecretData(sharedSecret, sizeof(sharedSecret));

    return err;
}

void WeaveTAKEEngine::GenerateHashForAuthenticateTokenResponse(uint8_t* dest, const uint8_t* challengerECDHPublicKey, const uint8_t* tokenECDHPublicKey, const uint8_t* encryptedState)
{
    Platform::Security::SHA1 sha1;

    uint8_t SessionKeyIdArray[sizeof(SessionKeyId)];
    LittleEndian::Put16(SessionKeyIdArray, SessionKeyId);

    sha1.Begin();
    sha1.AddData(&ControlHeader, sizeof(ControlHeader));
    sha1.AddData(&EncryptionType, sizeof(EncryptionType));
    sha1.AddData(&ProtocolConfig, sizeof(ProtocolConfig));
    sha1.AddData(OptionalConfigurations, sizeof(uint8_t) * GetNumOptionalConfigurations());
    sha1.AddData(SessionKeyIdArray, sizeof(SessionKeyId));
    sha1.AddData(&ChosenConfiguration, sizeof(ChosenConfiguration));
    sha1.AddData(ChallengerNonce, kNonceSize);
    sha1.AddData(TokenNonce, kNonceSize);
    sha1.AddData(challengerECDHPublicKey, GetECPointLen());
    sha1.AddData(tokenECDHPublicKey, GetECPointLen());
    sha1.AddData(encryptedState, kTokenEncryptedStateSize);
    sha1.Finish(dest);
}

WEAVE_ERROR WeaveTAKEEngine::GenerateSignatureForAuthenticateTokenResponse(uint8_t* dest, const uint8_t* challengerECDHPublicKey, const uint8_t* tokenECDHPublicKey,
                                                                           EncodedECPrivateKey TPriv, const uint8_t* encryptedState, OID& curveOID)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t messageHash[kConfig1_HMACSignatureSize];

    GenerateHashForAuthenticateTokenResponse(messageHash, challengerECDHPublicKey, tokenECDHPublicKey, encryptedState);

    err = GenerateECDSASignature(curveOID, messageHash, kConfig1_HMACSignatureSize, TPriv, dest);
    SuccessOrExit(err);

exit:
    return err;
}

WEAVE_ERROR WeaveTAKEEngine::VerifySignatureForAuthenticateTokenResponse(const uint8_t* signature, const uint8_t* challengerECDHPublicKey, const uint8_t* tokenECDHPublicKey,
                                                                         const uint8_t* encryptedState, OID& curveOID, EncodedECPublicKey& encodedPubKey)
{
    WEAVE_ERROR err = WEAVE_NO_ERROR;
    uint8_t messageHash[kConfig1_HMACSignatureSize];

    GenerateHashForAuthenticateTokenResponse(messageHash, challengerECDHPublicKey, tokenECDHPublicKey, encryptedState);

    err = VerifyECDSASignature(curveOID, messageHash, kConfig1_HMACSignatureSize, signature, encodedPubKey);
    SuccessOrExit(err);

exit:
    return err;
}

void WeaveTAKEEngine::ReadArray(uint8_t* dest, const uint8_t*& src, uint8_t length)
{
    memcpy(dest, src, length);
    src += length;
}

void WeaveTAKEEngine::WriteArray(const uint8_t* src, uint8_t*& dest, uint8_t length)
{
    memcpy(dest, src, length);
    dest += length;
}

bool WeaveTAKEEngine::UseSessionKey() const
{
    return IsEncryptAuthPhase() || IsEncryptCommPhase();
}

uint16_t WeaveTAKEEngine::GetCurveLen() const
{
    // NOTE: Should be reviewed/updated when new TAKE Configs are introduced
    return kConfig1_CurveSize;
}

uint16_t WeaveTAKEEngine::GetPrivKeyLen() const
{
    // NOTE: Should be reviewed/updated when new TAKE Configs are introduced
    return kConfig1_PrivKeySize;
}

uint16_t WeaveTAKEEngine::GetECPointLen() const
{
    // NOTE: Should be reviewed/updated when new TAKE Configs are introduced
    return kConfig1_ECPointX962FormatSize;
}

OID WeaveTAKEEngine::GetCurveOID() const
{
    // NOTE: Should be reviewed/updated when new TAKE Configs are introduced
    return kOID_EllipticCurve_secp224r1;
}

} // namespace TAKE
} // namespace Security
} // namespace Profiles
} // namespace Weave
} // namespace nl
