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
* 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
*/
/*
*/
/**
* \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
: m_pProfile (i_pProfile),
m_bFIPS (false),
m_iKWKEntryNum (0),
{
// initialize the aCluster, let it contain the default appliance
m_iClusterNum = 1;
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.
}
{
// 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;
}
{
{
}
else
{
i_iPort);
}
return m_sURL;
}
{
{
}
else
{
i_iPort);
}
return m_sURL;
}
int CAgentLoadBalancer::Balance ()
{
int i;
unsigned int iSelected = 0;
unsigned int iSelected2 = 0;
// clear the failover attempts
// 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.
// if not enabling load balancing, return the default appliance & if
// its FIPS compatible when running in FIPS_MODE
if (m_pProfile->m_iClusterDiscoveryFrequency == 0)
{
{
return NO_FIPS_KMA_AVAILABLE;
}
return 0;
}
// if it is the first time or time to get cluster information
if ((!m_pProfile->m_bIsClusterDiscoveryCalled) ||
{
sizeof (m_pProfile->m_wsEntitySiteID),
&(m_pProfile->m_iClusterNum),
{
// 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.
// reset this index since cluster size may have changed
// 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
// copy all Appliances to this object
for (i = 0; i < m_pProfile->m_iClusterNum; i++)
{
}
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++)
{
{
}
}
// 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;
{
for (i = 0; i < iCandidateAppliances; i++)
{
sizeof(m_aCluster[i].m_wsApplianceSiteID)) == 0)
{
}
}
}
// reduce the candidate set to just KMAs within the site
if (iCandidateAppliancesInSameSite > 0)
{
}
// constrain the candidate set to just FIPS compatible KMAs
if (m_bFIPS)
{
int iCandidateFIPSKMAs = 0;
for (i = 0; i < iCandidateAppliances; i++)
{
{
}
}
// select only from FIPS capable KMAs
}
// if there are no candidate Appliances, use the default Appliance unless
// we're in FIPS mode
{
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
// select the one with the smaller load
{
}
return iSelected;
}
const unsigned char * const i_pDataUnitID,
int i_iDataUnitIDMaxLen)
{
// clear the failover attempts
// 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.
// look in cache
// if not enabling load balancing, return the default appliance & if
// its FIPS compatible when running in FIPS_MODE
if (m_pProfile->m_iClusterDiscoveryFrequency == 0)
{
{
return NO_FIPS_KMA_AVAILABLE;
}
return 0;
}
// if the Data Unit ID is in the server affinity cache, use that Appliance
int iIndex = CLIENT_SIDE_ERROR;
sizeof(wsApplianceNetworkAddress)))
{
}
if (iIndex != CLIENT_SIDE_ERROR)
{
{
// 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();
}
const unsigned char * const i_pDataUnitKeyID,
{
// clear the failover attempts
// 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.
// look in cache
// if not enabling load balancing, return the default appliance & if
// its FIPS compatible when running in FIPS_MODE
if (m_pProfile->m_iClusterDiscoveryFrequency == 0)
{
{
return NO_FIPS_KMA_AVAILABLE;
}
return 0;
}
// if the Data Unit Key ID is in the server affinity cache, use that Appliance
int iIndex = CLIENT_SIDE_ERROR;
sizeof(sApplianceNetworkAddress)))
{
}
if (iIndex != CLIENT_SIDE_ERROR)
{
{
// 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();
}
(char * i_wsApplianceNetworkAddress)
{
for (int i = 0; i < m_iClusterNum; i++)
{
sizeof(m_aCluster[i].m_wsApplianceNetworkAddress)) == 0) &&
{
return i;
}
}
return CLIENT_SIDE_ERROR;
}
{
{
return (char *)"";
}
}
bool CAgentLoadBalancer::FailOverLimit (void)
{
if (m_pProfile->m_iFailoverLimit >= 0 &&
return true;
else
return false;
}
{
int i;
if ( m_bFIPS &&
{
return NO_FIPS_KMA_AVAILABLE;
}
/*
* 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 ||
{
NULL,
"KWK not registered or ID mismatch - registering");
// delete the KWK entry since the KMA no longer has it
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) ||
{
NULL,
"Server Busy - failing over");
bServerError = true;
}
{
bServerError = true;
}
else
{
{
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)
#endif
for (i = 0; i < m_pProfile->m_iClusterNum; i++)
{
{
break;
}
}
// 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.)
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++)
{
{
NULL,
"Failing over to this addr");
return i;
}
}
// now retry KMAs previously reported as not responding
{
}
NULL,
"Failing over to retry this addr");
}
else
{
NULL,
NULL,
"Failover limit reached");
}
}
{
bool bStatusChanged = false;
// 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++)
{
{
{
bStatusChanged = true;
}
break;
}
}
// only resort if the responding status actually changed
if (bStatusChanged)
{
}
// mark the Appliance as now responding
return;
}
int i_iIndex)
{
{
return -1;
}
}
{
if (i_lKMAID == -1)
{
return NULL;
}
for (int i = 0; i < m_iKWKEntryNum && i < KMS_MAX_CLUSTER_NUM; i++)
{
if (m_aKWKEntries[i] != NULL &&
{
return m_aKWKEntries[i];
}
}
return NULL;
}
const char * const i_sURL,
bool * const o_pbClientAESKeyWrapSetupError)
{
*o_pbClientAESKeyWrapSetupError = false;
if (!bSuccess)
{
NULL,
NULL,
"Error from RNG");
*o_pbClientAESKeyWrapSetupError = true;
delete(oKWKEntry);
return NULL;
}
#if defined(DEBUG)
#if defined(METAWARE)
log_printf("CAgentLoadBalancer::CreateKWK(): KWK hex=%s\n",
sHexKWK);
#else
// printf("CAgentLoadBalancer::CreateKWK(): KWK hex=%s\n",
// sHexKWK);
#endif
#endif
if (!bSuccess)
{
// GetKWKWrappingKey logs errors
{
*o_pbClientAESKeyWrapSetupError = true;
}
delete(oKWKEntry);
return NULL;
}
unsigned char acWrappedKWK[MAX_RSA_PUB_KEY_LENGTH];
int iWrappedKWKLength;
if (!bSuccess)
{
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
if (!bSuccess)
{
// RegisterKWK logs errors
{
*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
return oKWKEntry;
}
{
for (int i=0; i < m_iKWKEntryNum && i < KMS_MAX_CLUSTER_NUM; i++)
{
{
delete(m_aKWKEntries[i]);
m_aKWKEntries[i] = NULL;
return;
}
}
// should not occur
FATAL_ASSERT(0);
return;
}
{
{
return false;
}
FIPS_COMPATIBLE_KMA_VERSION) >= 0);
}
int CAgentLoadBalancer::GetKWKID (
int i_Index,
bool * const o_pbClientAESKeyWrapSetupError)
{
FATAL_ASSERT(i_lKMAID != 0);
*o_pbClientAESKeyWrapSetupError = false;
// check if the KMA for this cluster index is at a version supporting
// AES key wrap
if (!AESKeyWrapSupported(i_Index))
{
return TRUE;
}
// AES Key Wrap Mode
{
const char* sURL = GetHTTPSURL(
{
return FALSE;
}
}
log_printf("CAgentLoadBalancer::GetKWKID(): KWK IDhex=%s\n",
sizeof (UTF8_KEYID));
#endif
return TRUE;
};
const char * const i_sURL,
CPublicKey * const o_opPublicKEK)
{
NULL,
if (!bSuccess)
{
NULL,
return FALSE;
}
// Validate the response structure
if (bSuccess)
{
{
NULL,
NULL);
}
else
{
if (!bSuccess)
{
NULL,
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,
const char * const i_sURL,
{
int bSuccess;
char sHexWrappedKWK[512];
log_printf("CAgentLoadBalancer::RegisterKWK(): Wrapped KWK hex=%s, len=%d\n",
#endif
{
return FALSE;
}
if (bSuccess)
{
// verify response
if (oResponse.AgentKWKID &&
{
log_printf("CAgentLoadBalancer::RegisterKWK(): KWK ID hex=%s\n",
sizeof (UTF8_KEYID));
#endif
}
else
{
NULL,
}
}
else
{
NULL,
}
// Note: Clean up SOAP must happen in caller, not here
return bSuccess;
};
bool CAgentLoadBalancer::AESKeyUnwrap (
int * const io_pIndex,
const WRAPPED_KEY i_pAESWrappedKey,
{
FATAL_ASSERT(*io_pIndex >= 0);
{
NULL,
NULL);
return false;
}
log_printf("CAgentLoadBalancer::AESKeyUnwrap(): KWK hex=%s\n",
sHexKWK);
#endif
o_pPlainTextKey, 4) != 0)
{
NULL,
NULL);
return false;
}
return true;
}
/*---------------------------------------------------------------------------
* Function: KMSClient_SortClusterArray
*
*--------------------------------------------------------------------------*/
{
int i;
// adjust loads according to availability, site and FIPS compatibility
for (i = 0; i < i_pProfile->m_iClusterNum; i++)
{
{
}
else
{
}
i_pProfile->m_wsEntitySiteID) != 0)
{
}
else
{
}
if ( m_bFIPS &&
{
}
else
{
}
}
// sort ascending by load
// gnome sort: the simplest sort algoritm
//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)
{
{
i++;
}
else
{
}
}
}