view usr/src/lib/libkmsagent/common/KMSAgentLoadBalancer.cpp @ 12720:3db6e0082404

PSARC 2010/195 PKCS11 KMS Provider 6944296 Solaris needs a PKCS#11 provider to allow access to KMS keystore functionality
author Wyllys Ingersoll <Wyllys.Ingersoll@Sun.COM>
date Mon, 28 Jun 2010 16:04:11 -0700
parents
children
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 */

/**
 * \file KMSAgentLoadBalancer.cpp
 */

#ifdef WIN32
#define _WIN32_WINNT 0x0400
#include <windows.h>
#include <process.h>
#endif

#include <stdlib.h>

#include "KMS_AgentH.h"
#include "KMSClientProfile.h"
#include "KMSAgentSoapUtilities.h"
#include "KMSAgentStringUtilities.h"
#include "KMSClientProfileImpl.h"
#include "KMSAgent.h"
#include "KMSAuditLogger.h"
#include "ApplianceParameters.h"
#include "KMSAgentCryptoUtilities.h"

#ifdef METAWARE
#include "debug.h"
#include "sizet.h"
typedef unsigned char		uint8_t;
typedef unsigned short		uint16_t;
typedef unsigned int		uint32_t;
typedef unsigned long long	uint64_t;
#endif
#include "KMSAgentAESKeyWrap.h"

#ifdef METAWARE
#include "stdsoap2.h" /* makes fewer platform assumptions 
                          than the standard stdsoap2.h */

int time (char *);
#include "literals.h"
#else
#include "stdsoap2.h"
#endif

#include "AutoMutex.h"

// real declaration of soap *
#include "KMSAgentDataUnitCache.h"

#include "ClientSoapFaultCodes.h"
#include "KMSAgentPKICommon.h"
#include "KMSAgentLoadBalancer.h" // needs to be after stdsoap2.h to use the

CAgentLoadBalancer::CAgentLoadBalancer (KMSClientProfile * const i_pProfile)
: m_pProfile (i_pProfile),
m_iTransactionStartTimeInMilliseconds (0),
m_bFIPS (false),
m_iKWKEntryNum (0),
m_iLastAttemptedWhenNoneResponding (0)
{
    CAutoMutex oAutoMutex((K_MUTEX_HANDLE) m_pProfile->m_pLock);

    // initialize the aCluster, let it contain the default appliance
    m_iClusterNum = 1;
    memset(&(m_aCluster[0]), 0, sizeof (KMSClusterEntry));
    strncpy(m_aCluster[0].m_wsApplianceNetworkAddress,
            i_pProfile->m_wsApplianceAddress,
            sizeof(m_aCluster[0].m_wsApplianceNetworkAddress));
    m_aCluster[0].m_wsApplianceNetworkAddress[sizeof(m_aCluster[0].m_wsApplianceNetworkAddress)-1] = '\0';

    // This may not be known because the initial 
    // appliance's Alias is not yet entered.
    strcpy(m_aCluster[0].m_wsApplianceAlias, "");
    strcpy(m_sURL, "");
    memset(m_aKWKEntries, 0, KMS_MAX_CLUSTER_NUM * sizeof(struct KWKEntry *));
}

CAgentLoadBalancer::~CAgentLoadBalancer ()
{
    // free up KWK entries
    for( int i=0; i < m_iKWKEntryNum && i < KMS_MAX_CLUSTER_NUM; i++)
    {
        if (m_aKWKEntries[i] != NULL)
        {
            delete m_aKWKEntries[i];
        }
    }
    return;
}

char *CAgentLoadBalancer::GetHTTPSURL (int i_iIndex, int i_iPort)
{
    if (i_iIndex < 0 || i_iIndex >= m_iClusterNum)
    {
        strcpy(m_sURL, "");
    }
    else
    {
        K_snprintf(m_sURL, KMS_MAX_URL, "https://%s:%d",
                m_aCluster[i_iIndex].m_wsApplianceNetworkAddress,
                i_iPort);
    }

    return m_sURL;
}

char *CAgentLoadBalancer::GetHTTPURL (int i_iIndex, int i_iPort)
{
    if (i_iIndex < 0 || i_iIndex >= m_iClusterNum)
    {
        strcpy(m_sURL, "");
    }
    else
    {
        K_snprintf(m_sURL, KMS_MAX_URL, "http://%s:%d",
                m_aCluster[i_iIndex].m_wsApplianceNetworkAddress,
                i_iPort);
    }

    return m_sURL;
}

int CAgentLoadBalancer::Balance ()
{
    CAutoMutex oAutoMutex((K_MUTEX_HANDLE) m_pProfile->m_pLock);

    int i;
    unsigned int iSelected = 0;
    unsigned int iSelected2 = 0;

    // clear the failover attempts
    m_pProfile->m_iFailoverAttempts = 0;

    // This assumes Balance()/BalanceBy...() are called at the top of
    // each Agent Library transaction
    // m_iTransactionStartTimeInMilliseconds is used to determine if
    // enough time remains
    // (vs. KMSClientProfile::m_iTransactionTimeout) to retry a
    // request if there was a Server Busy error.

    m_iTransactionStartTimeInMilliseconds = K_GetTickCount();

    // if not enabling load balancing, return the default appliance & if
    // its FIPS compatible when running in FIPS_MODE

    if (m_pProfile->m_iClusterDiscoveryFrequency == 0)
    {
        if (m_bFIPS && !FIPScompatibleKMA(m_aCluster[0].m_sKMAVersion))
        {
            return NO_FIPS_KMA_AVAILABLE;
        }
        return 0;
    }

    int iCurrentTime = K_GetTickCount() / 1000;

    // if it is the first time or time to get cluster information
    if ((!m_pProfile->m_bIsClusterDiscoveryCalled) ||
        ((iCurrentTime - m_pProfile->m_iLastClusterDiscoveryTime) >
        m_pProfile->m_iClusterDiscoveryFrequency))
    {
        if (!KMSClient_GetClusterInformation(m_pProfile,
            m_pProfile->m_wsEntitySiteID,
            sizeof (m_pProfile->m_wsEntitySiteID),
            &(m_pProfile->m_iClusterNum),
            m_pProfile->m_aCluster,
            KMS_MAX_CLUSTER_NUM))
        {
            // if failed due to some error, return default one
            // KMSClient_GetClusterInformation logs

            return 0;
        }

        m_pProfile->m_bIsClusterDiscoveryCalled = true;

        // Reset the transaction start time to not include the time spent
        // calling KMSClient_GetClusterInformation.

        m_iTransactionStartTimeInMilliseconds = K_GetTickCount();

        // reset this index since cluster size may have changed
        m_iLastAttemptedWhenNoneResponding = 0;

        // TODO: Adjust timeouts to guarentee a response to the Agent
        // Library called in m_iTransactionTimeout seconds?  This means
        // not adjusting m_iTransactionStartTimeInMilliseconds, but also
        // reducing socket timeouts for subsequent calls.
    }

    // sort the cluster array by Load

    KMSClient_SortClusterArray(m_pProfile);

    // copy all Appliances to this object

    for (i = 0; i < m_pProfile->m_iClusterNum; i++)
    {
        m_aCluster[i] = m_pProfile->m_aCluster[i];
    }

    m_iClusterNum = m_pProfile->m_iClusterNum;

    int iCandidateAppliances = 0;

    // the initial set of candidates for load balancing are all enabled,
    // responding and unlocked KMAs (assumes they are at the top of the sort
    // order) & FIPS compatible if we're in that mode

    for (i = 0; i < m_iClusterNum; i++)
    {
        if ((m_aCluster[i].m_iResponding == TRUE) &&
            (m_aCluster[i].m_iEnabled == TRUE ) &&
			(m_aCluster[i].m_iKMALocked == FALSE))
        {
            iCandidateAppliances++;
        }
    }

    // check if there are any enabled and responding Appliances in the
    // same site as this Agent, and if so make those the candidates
    // (assumes they are at the top of the sort order)

    int iCandidateAppliancesInSameSite = 0;

    if (strlen(m_pProfile->m_wsEntitySiteID) > 0)
    {
        for (i = 0; i < iCandidateAppliances; i++)
        {
            if (strncmp(m_aCluster[i].m_wsApplianceSiteID,
                m_pProfile->m_wsEntitySiteID,
                sizeof(m_aCluster[i].m_wsApplianceSiteID)) == 0)
            {
                iCandidateAppliancesInSameSite++;
            }
        }
    }

    // reduce the candidate set to just KMAs within the site
    if (iCandidateAppliancesInSameSite > 0)
    {
        iCandidateAppliances = iCandidateAppliancesInSameSite;
    }

    // constrain the candidate set to just FIPS compatible KMAs
    if (m_bFIPS)
    {
        int iCandidateFIPSKMAs = 0;
        
        for (i = 0; i < iCandidateAppliances; i++)
        {
            if ( FIPScompatibleKMA(m_aCluster[i].m_sKMAVersion ))
            {
                iCandidateFIPSKMAs++;
            }
        }
        
        // select only from FIPS capable KMAs
        iCandidateAppliances = iCandidateFIPSKMAs;
    }
    
    // if there are no candidate Appliances, use the default Appliance unless
    // we're in FIPS mode

    if (!m_bFIPS && iCandidateAppliances <= 1)
    {
        return 0;
    }
    
    // FIPS mode
    else if (iCandidateAppliances <= 0)
    {
        return NO_FIPS_KMA_AVAILABLE;
    }
    else if (iCandidateAppliances == 1)
    {
        return 0;
    }

    // randomly select two candidate Appliances and select the one
    // with the smaller load

    // choose one random number between 0 -- iCandidateAppliances - 1
    iSelected = rand() % iCandidateAppliances;
    iSelected2 = (iSelected + 1) % iCandidateAppliances;

    // select the one with the smaller load

    if (m_aCluster[iSelected2].m_lLoad < m_aCluster[iSelected].m_lLoad)
    {
        iSelected = iSelected2;
    }

    return iSelected;
}

int CAgentLoadBalancer::BalanceByDataUnitID (
                                             const unsigned char * const i_pDataUnitID,
                                             int i_iDataUnitIDMaxLen)
{
    FATAL_ASSERT(i_pDataUnitID);

    CAutoMutex oAutoMutex((K_MUTEX_HANDLE) m_pProfile->m_pLock);

    // clear the failover attempts
    m_pProfile->m_iFailoverAttempts = 0;

    // This assumes Balance(), or BalanceBy...(),
    // is called at the top of each Agent Library transaction 
    // m_iTransactionStartTimeInMilliseconds is used to determine if enough time remains
    // (vs. KMSClientProfile::m_iTransactionTimeout) to retry a request if there was
    // a Server Busy error.

    m_iTransactionStartTimeInMilliseconds = K_GetTickCount();

    // look in cache

    CDataUnitCache *pDataUnitCache = (CDataUnitCache *) m_pProfile->m_pDataUnitCache;

    // if not enabling load balancing, return the default appliance & if
    // its FIPS compatible when running in FIPS_MODE

    if (m_pProfile->m_iClusterDiscoveryFrequency == 0)
    {
        if (m_bFIPS && !FIPScompatibleKMA(m_aCluster[0].m_sKMAVersion))
        {
            return NO_FIPS_KMA_AVAILABLE;
        }
        return 0;
    }

    // if the Data Unit ID is in the server affinity cache, use that Appliance

    utf8char wsApplianceNetworkAddress[KMS_MAX_NETWORK_ADDRESS];
    int iIndex = CLIENT_SIDE_ERROR;

    if (pDataUnitCache->GetApplianceByDataUnitID(
        i_pDataUnitID,
        i_iDataUnitIDMaxLen,
        wsApplianceNetworkAddress,
        sizeof(wsApplianceNetworkAddress)))
    {
        iIndex = FindIndexByNetworkAddress(wsApplianceNetworkAddress);
    }

    if (iIndex != CLIENT_SIDE_ERROR)
    {
        if (m_bFIPS && !FIPScompatibleKMA(m_aCluster[iIndex].m_sKMAVersion))
        {
            // in spite of caching we need to attempt an alternate KMA due
            // to the FIPS mode setting
            return Balance();
        }
        return iIndex;
    }

    // normal balancing
    return Balance();
}

int CAgentLoadBalancer::BalanceByDataUnitKeyID (
                                                const unsigned char * const i_pDataUnitKeyID,
                                                int i_iDataUnitKeyIDMaxLen)
{
    FATAL_ASSERT(i_pDataUnitKeyID);

    CAutoMutex oAutoMutex((K_MUTEX_HANDLE) m_pProfile->m_pLock);

    // clear the failover attempts
    m_pProfile->m_iFailoverAttempts = 0;

    // This assumes Balance()/BalanceBy...()
    // are called at the top of each Agent Library transaction 
    // m_iTransactionStartTimeInMilliseconds is used to determine if enough time remains
    // (vs. KMSClientProfile::m_iTransactionTimeout) to retry a request if there was
    // a Server Busy error.

    m_iTransactionStartTimeInMilliseconds = K_GetTickCount();

    // look in cache

    CDataUnitCache *pDataUnitCache = (CDataUnitCache *) m_pProfile->m_pDataUnitCache;

    // if not enabling load balancing, return the default appliance & if
    // its FIPS compatible when running in FIPS_MODE

    if (m_pProfile->m_iClusterDiscoveryFrequency == 0)
    {
        if (m_bFIPS && !FIPScompatibleKMA(m_aCluster[0].m_sKMAVersion))
        {
            return NO_FIPS_KMA_AVAILABLE;
        }
        return 0;
    }

    // if the Data Unit Key ID is in the server affinity cache, use that Appliance

    utf8char sApplianceNetworkAddress[KMS_MAX_NETWORK_ADDRESS];
    int iIndex = CLIENT_SIDE_ERROR;

    if (pDataUnitCache->GetApplianceByDataUnitKeyID(
        i_pDataUnitKeyID,
        i_iDataUnitKeyIDMaxLen,
        sApplianceNetworkAddress,
        sizeof(sApplianceNetworkAddress)))
    {
        iIndex = FindIndexByNetworkAddress(sApplianceNetworkAddress);
    }

    if (iIndex != CLIENT_SIDE_ERROR)
    {
        if (m_bFIPS && !FIPScompatibleKMA(m_aCluster[iIndex].m_sKMAVersion))
        {
            // in spite of caching we need to attempt an alternate KMA due
            // to the FIPS mode setting
            return Balance();
        }
        return iIndex;
    }

    // normal balancing
    return Balance();
}

int CAgentLoadBalancer::FindIndexByNetworkAddress
(char * i_wsApplianceNetworkAddress)
{
    FATAL_ASSERT(i_wsApplianceNetworkAddress);

    for (int i = 0; i < m_iClusterNum; i++)
    {

        if ((strncmp(m_aCluster[i].m_wsApplianceNetworkAddress,
            i_wsApplianceNetworkAddress,
            sizeof(m_aCluster[i].m_wsApplianceNetworkAddress)) == 0) &&
            m_aCluster[i].m_iEnabled == TRUE &&
            m_aCluster[i].m_iResponding == TRUE)
        {
            return i;
        }

    }

    return CLIENT_SIDE_ERROR;
}

char* CAgentLoadBalancer::GetApplianceNetworkAddress (int i_iIndex)
{
    if (i_iIndex < 0 || i_iIndex >= m_iClusterNum)
    {
        return (char *)"";
    }

    return m_aCluster[i_iIndex].m_wsApplianceNetworkAddress;
}

bool CAgentLoadBalancer::FailOverLimit (void)
{
    if (m_pProfile->m_iFailoverLimit >= 0 &&
        m_pProfile->m_iFailoverAttempts > m_pProfile->m_iFailoverLimit)
        return true;
    else
        return false;
}

int CAgentLoadBalancer::FailOver (int i_iFailedApplianceIndex,
                                  struct soap *i_pstSoap)
{
    FATAL_ASSERT(i_pstSoap);

    CAutoMutex oAutoMutex((K_MUTEX_HANDLE) m_pProfile->m_pLock);

    const char *strError = GET_SOAP_FAULTSTRING(i_pstSoap);
    int iSoapErrno = i_pstSoap->errnum;
    int iErrorCode = GET_FAULT_CODE(strError);
    int i;

    if ( m_bFIPS &&
        KMSClient_NoFIPSCompatibleKMAs(m_pProfile))
    {
        return NO_FIPS_KMA_AVAILABLE;
    }

    m_pProfile->m_iFailoverAttempts++;

    /*
     *  if KWK is not registered, or mismatched, most likely KMA lost its key due to a service
     *  restart.  Call RegisterKWK to re-register the KWK.
     *  If RegisterKWK  fails proceed from here with new failover info
     */
    if ( iErrorCode == CLIENT_ERROR_AGENT_KWK_NOT_REGISTERED ||
         iErrorCode == CLIENT_ERROR_AGENT_KWK_ID_MISMATCH )
    {
        LogError(m_pProfile,
                AGENT_LOADBALANCER_FAILOVER,
                NULL,
                m_aCluster[i_iFailedApplianceIndex].m_wsApplianceNetworkAddress,
                "KWK not registered or ID mismatch - registering");
        // delete the KWK entry since the KMA no longer has it
        DeleteKWKEntry( GetKMAID(i_iFailedApplianceIndex));
        
        return i_iFailedApplianceIndex;
    }

    bool bServerError = false;

    // if the request failed due to a Server Busy error, and if
    //  - transaction timeout has not been exceeded OR
    //  - failover attempts remain
    // then failover

    if (iErrorCode == CLIENT_ERROR_SERVER_BUSY &&
        (K_GetTickCount() < m_iTransactionStartTimeInMilliseconds + (m_pProfile->m_iTransactionTimeout * 1000) ||
        !CAgentLoadBalancer::FailOverLimit()))
    {
        LogError(m_pProfile,
                AGENT_LOADBALANCER_FAILOVER,
                NULL,
                m_aCluster[i_iFailedApplianceIndex].m_wsApplianceNetworkAddress,
                "Server Busy - failing over");
        bServerError = true;
    }
    else if (ServerError(strError,iSoapErrno))
    {
        bServerError = true;
    }
    else
    {
        if (i_iFailedApplianceIndex == AES_KEY_WRAP_SETUP_ERROR)
        {
            return AES_KEY_WRAP_SETUP_ERROR;
        }
        else
        {
            return CLIENT_SIDE_ERROR; // it is a client side problem, don't fail over
        }
    }

    // disable the failed Appliance in the profile, and
    // re-sort the cluster array, so transactions in other threads
    // will not send requests to the same failed Appliance
#if defined(METAWARE)
    log_cond_printf(ECPT_LOG_AGENT, "CAgentLoadBalancer::Failover(): FailoverAttempts=%d\n",
            m_pProfile->m_iFailoverAttempts);
#endif
    for (i = 0; i < m_pProfile->m_iClusterNum; i++)
    {
        if (m_pProfile->m_aCluster[i].m_lApplianceID ==
            m_aCluster[i_iFailedApplianceIndex].m_lApplianceID)
        {
            m_pProfile->m_aCluster[i].m_iResponding = FALSE;
            break;
        }
    }

    KMSClient_SortClusterArray(m_pProfile);

    // mark the failed Appliance as not responding (unlike the case
    // above which is conditional on bServerError, this marking is
    // only local to this transaction; it must be done to ensure that
    // this transaction does not cycle in its fail-over loop.)

    m_aCluster[i_iFailedApplianceIndex].m_iResponding = FALSE;

    if (!CAgentLoadBalancer::FailOverLimit())
    {
        // now try to fail over to all other Appliances that are
        // apparently enabled and responding 

        for (i = 0; i < m_iClusterNum; i++)
        {
            if (m_aCluster[i].m_iEnabled == TRUE &&
                m_aCluster[i].m_iResponding == TRUE &&
				m_aCluster[i].m_iKMALocked == FALSE)
            {
                Log(AGENT_LOADBALANCER_FAILOVER,
                        NULL,
                        m_aCluster[i].m_wsApplianceNetworkAddress,
                        "Failing over to this addr");

                return i;
            }
        }

        // now retry KMAs previously reported as not responding

        m_iLastAttemptedWhenNoneResponding++;

        if (m_iLastAttemptedWhenNoneResponding >= m_iClusterNum)
        {
            m_iLastAttemptedWhenNoneResponding = m_iLastAttemptedWhenNoneResponding % m_iClusterNum;
        }

        Log(AGENT_LOADBALANCER_FAILOVER,
                NULL,
                m_aCluster[m_iLastAttemptedWhenNoneResponding].m_wsApplianceNetworkAddress,
                "Failing over to retry this addr");

        return m_iLastAttemptedWhenNoneResponding;
    }
    else
    {
         Log(AGENT_LOADBALANCER_FAILOVER,
                NULL,
                NULL,
                "Failover limit reached");       
    }

    return m_bFIPS ? NO_FIPS_KMA_AVAILABLE : NO_KMA_AVAILABLE;
}

void CAgentLoadBalancer::UpdateResponseStatus(int i_iIndex)
{
    bool bStatusChanged = false;
    
    CAutoMutex oAutoMutex((K_MUTEX_HANDLE) m_pProfile->m_pLock);

    // enable the responding Appliance in the profile, and
    // re-sort the cluster array, so transactions in other threads
    // will not send requests to the same failed Appliance

    for (int i = 0; i < m_pProfile->m_iClusterNum; i++)
    {
        if (m_pProfile->m_aCluster[i].m_lApplianceID ==
            m_aCluster[i_iIndex].m_lApplianceID)
        {
            if (m_pProfile->m_aCluster[i].m_iResponding == FALSE)
            {
                bStatusChanged = true;
            }
            m_pProfile->m_aCluster[i].m_iResponding = TRUE;
            break;
        }
    }

    // only resort if the responding status actually changed
    if (bStatusChanged)
    {
        KMSClient_SortClusterArray(m_pProfile);
    }

    // mark the Appliance as now responding
    m_aCluster[i_iIndex].m_iResponding = TRUE;

    return;
}

Long64 CAgentLoadBalancer::GetKMAID (
                                     int i_iIndex)
{
    if (i_iIndex < 0 || i_iIndex >= m_iClusterNum)
    {
        return -1;
    }

    return m_aCluster[i_iIndex].m_lApplianceID;
}

CAgentLoadBalancer::KWKEntry *CAgentLoadBalancer::GetKWK (
                                                          Long64 i_lKMAID)
{
    if (i_lKMAID == -1)
    {
        return NULL;
    }
    
    for (int i = 0; i < m_iKWKEntryNum && i < KMS_MAX_CLUSTER_NUM; i++)
    {
        if (m_aKWKEntries[i] != NULL &&
            m_aKWKEntries[i]->m_lKMAID == i_lKMAID )
        {
            return m_aKWKEntries[i];
        }
    }

    return NULL;
}

CAgentLoadBalancer::KWKEntry *CAgentLoadBalancer::CreateKWK (
                                         Long64 i_lKMAID,
                                         struct soap * const i_pstSoap,
                                         const char * const i_sURL,
                                         bool * const o_pbClientAESKeyWrapSetupError)
{
    FATAL_ASSERT(i_pstSoap);
    FATAL_ASSERT(i_sURL);

    int bSuccess = FALSE;
    KWKEntry *oKWKEntry = new KWKEntry;

    oKWKEntry->m_lKMAID = i_lKMAID;
    *o_pbClientAESKeyWrapSetupError = false;
    
    bSuccess = GetPseudorandomBytes(sizeof (oKWKEntry->m_acKWK),
            oKWKEntry->m_acKWK);
    if (!bSuccess)
    {
        Log(AUDIT_CLIENT_AGENT_CREATE_KWK_RNG_ERROR,
                NULL,
                NULL,
                "Error from RNG");
        *o_pbClientAESKeyWrapSetupError = true;
        delete(oKWKEntry);
        return NULL;
    }

#if defined(DEBUG)
    char sHexKWK[2*KMS_MAX_KEY_SIZE+1];
    ConvertBinaryToUTF8HexString( sHexKWK, oKWKEntry->m_acKWK, sizeof (oKWKEntry->m_acKWK));
#if defined(METAWARE)
    log_printf("CAgentLoadBalancer::CreateKWK(): KWK hex=%s\n",
            sHexKWK);
#else
//    printf("CAgentLoadBalancer::CreateKWK(): KWK hex=%s\n",
//            sHexKWK);
#endif    
#endif
    
    CPublicKey oPublicKEK;

    bSuccess = GetKWKWrappingKey(i_pstSoap, i_sURL, &oPublicKEK);

    if (!bSuccess)
    {
        // GetKWKWrappingKey logs errors   
        
        if (!ServerError(GET_SOAP_FAULTSTRING(i_pstSoap),i_pstSoap->errnum))
        {
            *o_pbClientAESKeyWrapSetupError = true;
        }
        delete(oKWKEntry);
        return NULL;
    }

    unsigned char acWrappedKWK[MAX_RSA_PUB_KEY_LENGTH];
    int iWrappedKWKLength;
    bSuccess = oPublicKEK.Encrypt(sizeof (oKWKEntry->m_acKWK),
            oKWKEntry->m_acKWK, (unsigned char *) acWrappedKWK, &iWrappedKWKLength);

    if (!bSuccess)
    {
        Log(AUDIT_CLIENT_AGENT_CREATE_KWK_PUBLIC_ENCRYPT_ERROR,
                NULL,
                NULL,
                "Error encrypting KWK with KMA public key");
        *o_pbClientAESKeyWrapSetupError = true;
        delete(oKWKEntry);
        return NULL;
    }
//#if defined(DEBUG) && !defined(METAWARE)
//    char sHexWrappedKWK[2*MAX_RSA_PUB_KEY_LENGTH+1];
//    ConvertBinaryToUTF8HexString( sHexWrappedKWK, acWrappedKWK, iWrappedKWKLength);
//    printf("CAgentLoadBalancer::CreateKWK(): wrapped KWK hex=%s\n",
//            sHexWrappedKWK);
//#endif

    // register the new KWK
    bSuccess = RegisterKWK(iWrappedKWKLength, acWrappedKWK, i_pstSoap,
            i_sURL, oKWKEntry->m_acKWKID);

    if (!bSuccess)
    {
        // RegisterKWK logs errors       
        if (!ServerError(GET_SOAP_FAULTSTRING(i_pstSoap), i_pstSoap->error))
        {
            *o_pbClientAESKeyWrapSetupError = true;
        }
        delete(oKWKEntry);
        return NULL;
    }

    // save the new KWK entry in an empty slot in the array
    for (int i=0; i < m_iKWKEntryNum && i < KMS_MAX_CLUSTER_NUM; i++)
    {
        if (m_aKWKEntries[i] == NULL)
        {
            m_aKWKEntries[i] = oKWKEntry; 
            return oKWKEntry;
        }
    }
    
    // no empty slots so add it to the end
    m_aKWKEntries[m_iKWKEntryNum++] = oKWKEntry;

    return oKWKEntry;
}

void CAgentLoadBalancer::DeleteKWKEntry(Long64 i_lKMAID)
{
    for (int i=0; i < m_iKWKEntryNum && i < KMS_MAX_CLUSTER_NUM; i++)
    {
        if (m_aKWKEntries[i] && m_aKWKEntries[i]->m_lKMAID == i_lKMAID)
        {
            delete(m_aKWKEntries[i]);
            m_aKWKEntries[i] = NULL;
            return;
        }
    }
    // should not occur
    FATAL_ASSERT(0);
    return;
}

bool CAgentLoadBalancer::AESKeyWrapSupported (int i_iIndex)
{
    if (i_iIndex < 0 || i_iIndex >= m_iClusterNum)
    {
        return false;
    }
    return (strcmp(m_aCluster[i_iIndex].m_sKMAVersion,
                    FIPS_COMPATIBLE_KMA_VERSION) >= 0);
}

int CAgentLoadBalancer::GetKWKID (
                                  int    i_Index,
                                  Long64 i_lKMAID,
                                  struct soap * const i_pstSoap,
                                  UTF8_KEYID o_pKWKID,
                                  bool * const o_pbClientAESKeyWrapSetupError)
{
    FATAL_ASSERT(i_Index >= 0 && i_Index <= m_iClusterNum);
    FATAL_ASSERT(i_lKMAID != 0);
    FATAL_ASSERT(i_pstSoap);
    FATAL_ASSERT(o_pKWKID);
    FATAL_ASSERT(o_pbClientAESKeyWrapSetupError);

    *o_pbClientAESKeyWrapSetupError = false;
    
    // check if the KMA for this cluster index is at a version supporting
    // AES key wrap
    if (!AESKeyWrapSupported(i_Index))
    {
        strcpy(o_pKWKID, "");
        return TRUE;
    }

    // AES Key Wrap Mode

    struct KWKEntry* pKWKentry = GetKWK(i_lKMAID);

    if (pKWKentry == NULL)
    {
        const char* sURL = GetHTTPSURL(
                i_Index,
                m_pProfile->m_iPortForAgentService);

        pKWKentry = CreateKWK(i_lKMAID, i_pstSoap, sURL, o_pbClientAESKeyWrapSetupError);

        if (pKWKentry == NULL)
        {
            return FALSE;
        }
    }

#if defined(DEBUG) && defined(METAWARE)
    log_printf("CAgentLoadBalancer::GetKWKID(): KWK IDhex=%s\n",
            pKWKentry->m_acKWKID,
            sizeof (UTF8_KEYID));
#endif
    
    strncpy(o_pKWKID, pKWKentry->m_acKWKID, sizeof(UTF8_KEYID));
    o_pKWKID[sizeof(UTF8_KEYID)-1] = '\0';

    return TRUE;
};

int CAgentLoadBalancer::GetKWKWrappingKey (
                                           struct soap * const i_pstSoap,
                                           const char * const i_sURL,
                                           CPublicKey * const  o_opPublicKEK)
{
    FATAL_ASSERT(i_pstSoap);
    FATAL_ASSERT(i_sURL);
    FATAL_ASSERT(o_opPublicKEK);

    int bSuccess = TRUE;
    struct KMS_Agent::KMS_Agent__GetAgentKWKPublicKeyResponse oResponse;
    char sSoapFaultMsg[g_iMAX_SOAP_FAULT_MESSAGE_LENGTH];
    char sKmaAddress[g_iMAX_PEER_NETWORK_ADDRESS_LENGTH];

    bSuccess = KMS_Agent::soap_call_KMS_Agent__GetAgentKWKPublicKey(
            const_cast<struct soap *> (i_pstSoap),
            i_sURL,
            NULL,
            oResponse) == SOAP_OK;

    if (!bSuccess)
    {
        GetSoapFault(sSoapFaultMsg, const_cast<struct soap *> (i_pstSoap));
        GetPeerNetworkAddress(sKmaAddress, const_cast<struct soap *> (i_pstSoap));

        LogError(m_pProfile,
                AUDIT_CLIENT_GET_KWK_WRAPPING_KEY_SOAP_ERROR,
                NULL,
                sKmaAddress,
                sSoapFaultMsg);

        return FALSE;
    }

    // Validate the response structure
    if (bSuccess)
    {
        if (oResponse.KWKPublicKey.__ptr == NULL
            || oResponse.KWKPublicKey.__size < 1)
        {
            bSuccess = FALSE;

            GetPeerNetworkAddress(sKmaAddress, const_cast<struct soap *> (i_pstSoap));

            LogError(m_pProfile,
                    AUDIT_CLIENT_GET_KWK_WRAPPING_KEY_INVALID_KEY_RESPONSE,
                    NULL,
                    sKmaAddress,
                    NULL);
        }
        else
        {
            bSuccess = o_opPublicKEK->Load(oResponse.KWKPublicKey.__ptr,
                    oResponse.KWKPublicKey.__size, PKI_FORMAT);
            if (!bSuccess)
            {
                GetPeerNetworkAddress(sKmaAddress, const_cast<struct soap *> (i_pstSoap));

                LogError(m_pProfile,
                        AUDIT_CLIENT_GET_KWK_WRAPPING_KEY_INVALID_RSA_PUB_KEY,
                        NULL,
                        sKmaAddress,
                        NULL);
            }
        }
    }

    // Note: no SOAP cleanup as caller's environment will get destroyed

    return bSuccess;
};

int CAgentLoadBalancer::RegisterKWK (
                                     int i_iWrappedKWKSize,
                                     const unsigned char * const i_acWrappedKWK,
                                     struct soap * const i_pstSoap,
                                     const char * const i_sURL,
                                     UTF8_KEYID o_acUTF8KeyID)
{
    FATAL_ASSERT(i_iWrappedKWKSize > 0);
    FATAL_ASSERT(i_acWrappedKWK);
    FATAL_ASSERT(i_pstSoap);
    FATAL_ASSERT(i_sURL);
    FATAL_ASSERT(o_acUTF8KeyID);

    int bSuccess;

    struct KMS_Agent::xsd__hexBinary oKWK;

#if defined(DEBUG) && defined(METAWARE)
    char sHexWrappedKWK[512];
    ConvertBinaryToUTF8HexString( sHexWrappedKWK, i_acWrappedKWK, i_iWrappedKWKSize);
    log_printf("CAgentLoadBalancer::RegisterKWK(): Wrapped KWK hex=%s, len=%d\n",
            sHexWrappedKWK, i_iWrappedKWKSize);
#endif
    
    if (!PutBinaryIntoSoapBinary(i_pstSoap,
        i_acWrappedKWK,
        i_iWrappedKWKSize,
        oKWK.__ptr,
        oKWK.__size))
    {
        return FALSE;
    }

    char sSoapFaultMsg[g_iMAX_SOAP_FAULT_MESSAGE_LENGTH];
    char sKmaAddress[g_iMAX_PEER_NETWORK_ADDRESS_LENGTH];
    struct KMS_Agent::KMS_Agent__RegisterAgentKWKResponse oResponse;

    bSuccess = KMS_Agent::soap_call_KMS_Agent__RegisterAgentKWK(
            const_cast<struct soap *> (i_pstSoap),
            i_sURL, NULL, oKWK, oResponse) == SOAP_OK;

    if (bSuccess)
    {
        // verify response
        if (oResponse.AgentKWKID &&
            strlen(oResponse.AgentKWKID) == 2 * KMS_KWK_KEY_ID_SIZE)
        {
#if defined(DEBUG) && defined(METAWARE)
            log_printf("CAgentLoadBalancer::RegisterKWK(): KWK ID hex=%s\n",
                    oResponse.AgentKWKID,
                    sizeof (UTF8_KEYID));
#endif
            strncpy(o_acUTF8KeyID, oResponse.AgentKWKID, sizeof(UTF8_KEYID));
            o_acUTF8KeyID[sizeof(UTF8_KEYID)-1] = '\0';
        }
        else
        {
            GetPeerNetworkAddress(sKmaAddress, const_cast<struct soap *> (i_pstSoap));
            GetSoapFault(sSoapFaultMsg, const_cast<struct soap *> (i_pstSoap));

            Log(AUDIT_CLIENT_AGENT_REGISTER_KWK_INVALID_KEYID_RESPONSE,
                    NULL,
                    sKmaAddress,
                    sSoapFaultMsg);
            bSuccess = FALSE;
        }
    }
    else
    {
        GetPeerNetworkAddress(sKmaAddress, const_cast<struct soap *> (i_pstSoap));
        GetSoapFault(sSoapFaultMsg, const_cast<struct soap *> (i_pstSoap));

        Log(AUDIT_CLIENT_AGENT_REGISTER_KWK_ERROR,
                NULL,
                sKmaAddress,
                sSoapFaultMsg);
        bSuccess = FALSE;
    }

    // Note: Clean up SOAP must happen in caller, not here

    return bSuccess;

};

bool CAgentLoadBalancer::AESKeyUnwrap (
                                       int * const io_pIndex,
                                       const WRAPPED_KEY i_pAESWrappedKey,
                                       KEY o_pPlainTextKey)
{
    FATAL_ASSERT(io_pIndex);
    FATAL_ASSERT(*io_pIndex >= 0);
    FATAL_ASSERT(o_pPlainTextKey);
    FATAL_ASSERT(i_pAESWrappedKey);

    struct KWKEntry * pKWKEntry = GetKWK(GetKMAID(*io_pIndex));

    if (pKWKEntry == NULL)
    {
        Log(AGENT_LOADBALANCER_AESKEYUNWRAP_GETKWK_RETURNED_NULL,
                NULL,
                m_aCluster[*io_pIndex].m_wsApplianceNetworkAddress,
                NULL);
        *io_pIndex = CAgentLoadBalancer::AES_KEY_UNWRAP_ERROR;
        
        return false;
    }

#if defined(DEBUG) && defined(METAWARE)
    char sHexKWK[2*KMS_MAX_KEY_SIZE+1];
    ConvertBinaryToUTF8HexString( sHexKWK, pKWKEntry->m_acKWK, sizeof (pKWKEntry->m_acKWK));
    log_printf("CAgentLoadBalancer::AESKeyUnwrap(): KWK hex=%s\n",
            sHexKWK);
#endif
    
    if (aes_key_unwrap(pKWKEntry->m_acKWK,
        sizeof (pKWKEntry->m_acKWK),
        i_pAESWrappedKey,
        o_pPlainTextKey, 4) != 0)
    {
        Log(AGENT_LOADBALANCER_AESKEYUNWRAP_KEY_UNWRAP_FAILED,
                NULL,
                m_aCluster[*io_pIndex].m_wsApplianceNetworkAddress,
                NULL);
        *io_pIndex = CAgentLoadBalancer::AES_KEY_UNWRAP_ERROR;
        return false;
    }

    return true;
}

/*---------------------------------------------------------------------------
 * Function: KMSClient_SortClusterArray
 *
 *--------------------------------------------------------------------------*/
void CAgentLoadBalancer::KMSClient_SortClusterArray (KMSClientProfile * const i_pProfile)
{
    FATAL_ASSERT(i_pProfile);

    CAutoMutex oAutoMutex((K_MUTEX_HANDLE) i_pProfile->m_pLock);

    int i;


    // adjust loads according to availability, site and FIPS compatibility
    for (i = 0; i < i_pProfile->m_iClusterNum; i++)
    {
        if ((i_pProfile->m_aCluster[i].m_iEnabled == FALSE
            || i_pProfile->m_aCluster[i].m_iResponding == FALSE
			|| i_pProfile->m_aCluster[i].m_iKMALocked))
        {
            ((unsigned char*) &(i_pProfile->m_aCluster[i].m_lLoad))[sizeof (int)+1] = 1;
        }
        else
        {
            ((unsigned char*) &(i_pProfile->m_aCluster[i].m_lLoad))[sizeof (int)+1] = 0;
        }

        if (strcmp(i_pProfile->m_aCluster[i].m_wsApplianceSiteID,
            i_pProfile->m_wsEntitySiteID) != 0)
        {
            ((unsigned char*) &(i_pProfile->m_aCluster[i].m_lLoad))[sizeof (int)] = 1;
        }
        else
        {
            ((unsigned char*) &(i_pProfile->m_aCluster[i].m_lLoad))[sizeof (int)] = 0;
        }
        
        if ( m_bFIPS && 
                !FIPScompatibleKMA(i_pProfile->m_aCluster[i].m_sKMAVersion))
        {
            ((unsigned char*) &(i_pProfile->m_aCluster[i].m_lLoad))[sizeof (int)+2] = 1;
        }
        else
        {
            ((unsigned char*) &(i_pProfile->m_aCluster[i].m_lLoad))[sizeof (int)+2] = 0;
        }
    }

    // sort ascending by load

    // gnome sort: the simplest sort algoritm
    // http://www.cs.vu.nl/~dick/gnomesort.html

    //void gnomesort(int n, int ar[]) {
    //    int i = 0;
    //
    //    while (i < n) {
    //        if (i == 0 || ar[i-1] <= ar[i]) i++;
    //        else {int tmp = ar[i]; ar[i] = ar[i-1]; ar[--i] = tmp;}
    //    }
    //}    

    i = 0;
    while (i < i_pProfile->m_iClusterNum)
    {
        if (i == 0 || i_pProfile->m_aCluster[i - 1].m_lLoad <= i_pProfile->m_aCluster[i].m_lLoad)
        {
            i++;
        }
        else
        {
            KMSClusterEntry tmp = i_pProfile->m_aCluster[i];
            i_pProfile->m_aCluster[i] = i_pProfile->m_aCluster[i - 1];
            i_pProfile->m_aCluster[--i] = tmp;
        }
    }
}