diff 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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/libkmsagent/common/KMSAgentLoadBalancer.cpp	Mon Jun 28 16:04:11 2010 -0700
@@ -0,0 +1,1169 @@
+/*
+ * 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;
+        }
+    }
+}