KMSAgentLoadBalancer.cpp revision 4f14b0f29aa144cc03efdde5508ae126ae197acf
/*
* 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;
}
}
}