windows-nt/Source/XPSP1/NT/ds/security/protocols/schannel/spbase/keyxmspk.c

1950 lines
58 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1995.
//
// File: keyxmspk.c
//
// Contents:
//
// Classes:
//
// Functions:
//
// History: 09-23-97 jbanes LSA integration stuff.
//
//----------------------------------------------------------------------------
#include <spbase.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
// PROV_RSA_SCHANNEL handle used when building ClientHello messages.
HCRYPTPROV g_hRsaSchannel = 0;
PROV_ENUMALGS_EX * g_pRsaSchannelAlgs = NULL;
DWORD g_cRsaSchannelAlgs = 0;
SP_STATUS
Ssl3ParseServerKeyExchange(
PSPContext pContext,
PBYTE pbMessage,
DWORD cbMessage,
HCRYPTKEY hServerPublic,
HCRYPTKEY *phNewServerPublic);
SP_STATUS
PkcsFinishMasterKey(
PSPContext pContext,
HCRYPTKEY hMasterKey);
SP_STATUS
WINAPI
PkcsGenerateServerExchangeValue(
SPContext * pContext, // in
PUCHAR pServerExchangeValue, // out
DWORD * pcbServerExchangeValue // in/out
);
SP_STATUS
WINAPI
PkcsGenerateClientExchangeValue(
SPContext * pContext, // in
PUCHAR pServerExchangeValue, // in
DWORD cbServerExchangeValue, // in
PUCHAR pClientClearValue, // out
DWORD * pcbClientClearValue, // in/out
PUCHAR pClientExchangeValue, // out
DWORD * pcbClientExchangeValue // in/out
);
SP_STATUS
WINAPI
PkcsGenerateServerMasterKey(
SPContext * pContext, // in
PUCHAR pClientClearValue, // in
DWORD cbClientClearValue, // in
PUCHAR pClientExchangeValue, // in
DWORD cbClientExchangeValue // in
);
KeyExchangeSystem keyexchPKCS = {
SP_EXCH_RSA_PKCS1,
"RSA",
// PkcsPrivateFromBlob,
PkcsGenerateServerExchangeValue,
PkcsGenerateClientExchangeValue,
PkcsGenerateServerMasterKey,
};
VOID
ReverseMemCopy(
PUCHAR Dest,
PUCHAR Source,
ULONG Size)
{
PUCHAR p;
p = Dest + Size - 1;
do
{
*p-- = *Source++;
} while (p >= Dest);
}
SP_STATUS
GenerateSsl3KeyPair(
PSPContext pContext, // in
DWORD dwKeySize, // in
HCRYPTPROV *phEphemeralProv, // out
HCRYPTKEY * phEphemeralKey) // out
{
HCRYPTPROV * phEphemProv;
PCRYPT_KEY_PROV_INFO pProvInfo = NULL;
PSPCredentialGroup pCredGroup;
PSPCredential pCred;
DWORD cbSize;
SP_STATUS pctRet;
pCredGroup = pContext->RipeZombie->pServerCred;
if(pCredGroup == NULL)
{
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
LockCredential(pCredGroup);
pCred = pContext->RipeZombie->pActiveServerCred;
if(pCred == NULL)
{
pctRet = SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
goto cleanup;
}
if(dwKeySize == 512)
{
phEphemProv = &pCred->hEphem512Prov;
}
else if(dwKeySize == 1024)
{
phEphemProv = &pCred->hEphem1024Prov;
}
else
{
pctRet = SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
goto cleanup;
}
//
// Obtain CSP context.
//
if(*phEphemProv == 0)
{
// Read the certificate context's "key info" property.
if(CertGetCertificateContextProperty(pCred->pCert,
CERT_KEY_PROV_INFO_PROP_ID,
NULL,
&cbSize))
{
pProvInfo = SPExternalAlloc(cbSize);
if(pProvInfo == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
goto cleanup;
}
if(!CertGetCertificateContextProperty(pCred->pCert,
CERT_KEY_PROV_INFO_PROP_ID,
pProvInfo,
&cbSize))
{
DebugLog((SP_LOG_ERROR, "Error 0x%x reading CERT_KEY_PROV_INFO_PROP_ID\n",GetLastError()));
SPExternalFree(pProvInfo);
pProvInfo = NULL;
}
}
// Obtain a "verify only" csp context.
if(pProvInfo)
{
// If the private key belongs to one of the Microsoft PROV_RSA_FULL
// CSPs, then manually divert it to the Microsoft PROV_RSA_SCHANNEL
// CSP. This works because both CSP types use the same private key
// storage scheme.
if(pProvInfo->dwProvType == PROV_RSA_FULL)
{
if(lstrcmpW(pProvInfo->pwszProvName, MS_DEF_PROV_W) == 0 ||
lstrcmpW(pProvInfo->pwszProvName, MS_STRONG_PROV_W) == 0 ||
lstrcmpW(pProvInfo->pwszProvName, MS_ENHANCED_PROV_W) == 0)
{
DebugLog((DEB_WARN, "Force CSP type to PROV_RSA_SCHANNEL.\n"));
pProvInfo->pwszProvName = MS_DEF_RSA_SCHANNEL_PROV_W;
pProvInfo->dwProvType = PROV_RSA_SCHANNEL;
}
}
if(!SchCryptAcquireContextW(phEphemProv,
NULL,
pProvInfo->pwszProvName,
pProvInfo->dwProvType,
CRYPT_VERIFYCONTEXT,
pCred->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pctRet = SEC_E_NO_CREDENTIALS;
goto cleanup;
}
SPExternalFree(pProvInfo);
pProvInfo = NULL;
}
else
{
if(!SchCryptAcquireContextW(phEphemProv,
NULL,
NULL,
PROV_RSA_SCHANNEL,
CRYPT_VERIFYCONTEXT,
pCred->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pctRet = SEC_E_NO_CREDENTIALS;
goto cleanup;
}
}
}
//
// Obtain handle to private key.
//
if(!SchCryptGetUserKey(*phEphemProv,
AT_KEYEXCHANGE,
phEphemeralKey,
pCred->dwCapiFlags))
{
// Key does not exist, so attempt to create one.
DebugLog((DEB_TRACE, "Creating %d-bit ephemeral key.\n", dwKeySize));
if(!SchCryptGenKey(*phEphemProv,
AT_KEYEXCHANGE,
(dwKeySize << 16),
phEphemeralKey,
pCred->dwCapiFlags))
{
DebugLog((DEB_ERROR, "Error 0x%x generating ephemeral key\n", GetLastError()));
pctRet = SEC_E_NO_CREDENTIALS;
goto cleanup;
}
DebugLog((DEB_TRACE, "Ephemeral key created okay.\n"));
}
*phEphemeralProv = *phEphemProv;
pctRet = PCT_ERR_OK;
cleanup:
if(pProvInfo)
{
SPExternalFree(pProvInfo);
}
UnlockCredential(pCredGroup);
return pctRet;
}
//+---------------------------------------------------------------------------
//
// Function: PkcsGenerateServerExchangeValue
//
// Synopsis: Create a ServerKeyExchange message, containing an ephemeral
// RSA key.
//
// Arguments: [pContext] -- Schannel context.
// [pServerExchangeValue] --
// [pcbServerExchangeValue] --
//
// History: 10-09-97 jbanes Added CAPI integration.
//
// Notes: This routine is called by the server-side only.
//
// In the case of SSL3 or TLS, the ServerKeyExchange message
// consists of the following structure, signed with the
// server's private key.
//
// struct {
// opaque rsa_modulus<1..2^16-1>;
// opaque rsa_exponent<1..2^16-1>;
// } Server RSA Params;
//
// This message is only sent when the server's private key
// is greater then 512 bits and an export cipher suite is
// being negotiated.
//
//----------------------------------------------------------------------------
SP_STATUS
WINAPI
PkcsGenerateServerExchangeValue(
PSPContext pContext, // in
PBYTE pServerExchangeValue, // out
DWORD * pcbServerExchangeValue) // in/out
{
PSPCredential pCred;
HCRYPTKEY hServerKey;
HCRYPTPROV hEphemeralProv;
HCRYPTKEY hEphemeralKey;
DWORD cbData;
DWORD cbServerModulus;
PBYTE pbBlob = NULL;
DWORD cbBlob;
BLOBHEADER * pBlobHeader = NULL;
RSAPUBKEY * pRsaPubKey = NULL;
PBYTE pbModulus = NULL;
DWORD cbModulus;
DWORD cbExp;
PBYTE pbMessage = NULL;
DWORD cbSignature;
HCRYPTHASH hHash;
BYTE rgbHashValue[CB_MD5_DIGEST_LEN + CB_SHA_DIGEST_LEN];
UINT i;
SP_STATUS pctRet;
BOOL fImpersonating = FALSE;
UNICipherMap * pCipherSuite;
DWORD cbAllowedKeySize;
pCred = pContext->RipeZombie->pActiveServerCred;
if(pCred == NULL)
{
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
pContext->fExchKey = FALSE;
if(pContext->RipeZombie->fProtocol == SP_PROT_SSL2_SERVER ||
pContext->RipeZombie->fProtocol == SP_PROT_PCT1_SERVER)
{
// There is no ServerExchangeValue for SSL2 or PCT1
*pcbServerExchangeValue = 0;
return PCT_ERR_OK;
}
if(pContext->RipeZombie->fProtocol != SP_PROT_SSL3_SERVER &&
pContext->RipeZombie->fProtocol != SP_PROT_TLS1_SERVER)
{
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
//
// Determine if ServerKeyExchange message is necessary.
//
pCipherSuite = &UniAvailableCiphers[pContext->dwPendingCipherSuiteIndex];
if(pCipherSuite->dwFlags & DOMESTIC_CIPHER_SUITE)
{
// Message not necessary.
*pcbServerExchangeValue = 0;
return PCT_ERR_OK;
}
if(pCred->hProv == 0)
{
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
fImpersonating = SslImpersonateClient();
if(!SchCryptGetUserKey(pCred->hProv,
pCred->dwKeySpec,
&hServerKey,
pContext->RipeZombie->dwCapiFlags))
{
DebugLog((DEB_ERROR, "Error 0x%x obtaining handle to server public key\n",
GetLastError()));
pctRet = PCT_INT_INTERNAL_ERROR;
goto cleanup;
}
cbData = sizeof(DWORD);
if(!SchCryptGetKeyParam(hServerKey,
KP_BLOCKLEN,
(PBYTE)&cbServerModulus,
&cbData,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
SchCryptDestroyKey(hServerKey, pContext->RipeZombie->dwCapiFlags);
pctRet = PCT_INT_INTERNAL_ERROR;
goto cleanup;
}
SchCryptDestroyKey(hServerKey, pContext->RipeZombie->dwCapiFlags);
if(pCipherSuite->dwFlags & EXPORT56_CIPHER_SUITE)
{
cbAllowedKeySize = 1024;
}
else
{
cbAllowedKeySize = 512;
}
if(cbServerModulus <= cbAllowedKeySize)
{
// Message not necessary.
*pcbServerExchangeValue = 0;
pctRet = PCT_ERR_OK;
goto cleanup;
}
// Convert size from bits to bytes.
cbServerModulus /= 8;
pContext->fExchKey = TRUE;
if(fImpersonating)
{
RevertToSelf();
fImpersonating = FALSE;
}
//
// Compute approximate size of ServerKeyExchange message.
//
if(pServerExchangeValue == NULL)
{
*pcbServerExchangeValue =
2 + cbAllowedKeySize / 8 + // modulus
2 + sizeof(DWORD) + // exponent
2 + cbServerModulus; // signature
pctRet = PCT_ERR_OK;
goto cleanup;
}
//
// Get handle to 512-bit ephemeral RSA key. Generate it if
// we haven't already.
//
pctRet = GenerateSsl3KeyPair(pContext,
cbAllowedKeySize,
&hEphemeralProv,
&hEphemeralKey);
if(pctRet != PCT_ERR_OK)
{
SP_LOG_RESULT(pctRet);
goto cleanup;
}
//
// Export ephemeral key.
//
if(!SchCryptExportKey(hEphemeralKey,
0,
PUBLICKEYBLOB,
0,
NULL,
&cbBlob,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pctRet = PCT_INT_INTERNAL_ERROR;
goto cleanup;
}
pbBlob = SPExternalAlloc(cbBlob);
if(pbBlob == NULL)
{
pctRet = SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
goto cleanup;
}
if(!SchCryptExportKey(hEphemeralKey,
0,
PUBLICKEYBLOB,
0,
pbBlob,
&cbBlob,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
SPExternalFree(pbBlob);
pctRet = PCT_INT_INTERNAL_ERROR;
goto cleanup;
}
//
// Destroy handle to ephemeral key. Don't release the ephemeral hProv
// though--that's owned by the credential.
SchCryptDestroyKey(hEphemeralKey, pContext->RipeZombie->dwCapiFlags);
//
// Build message from key blob.
//
pBlobHeader = (BLOBHEADER *)pbBlob;
pRsaPubKey = (RSAPUBKEY *)(pBlobHeader + 1);
pbModulus = (BYTE *)(pRsaPubKey + 1);
cbModulus = pRsaPubKey->bitlen / 8;
pbMessage = pServerExchangeValue;
pbMessage[0] = MSBOF(cbModulus);
pbMessage[1] = LSBOF(cbModulus);
pbMessage += 2;
ReverseMemCopy(pbMessage, pbModulus, cbModulus);
pbMessage += cbModulus;
// Don't laugh, this works - pete
cbExp = ((pRsaPubKey->pubexp & 0xff000000) ? 4 :
((pRsaPubKey->pubexp & 0x00ff0000) ? 3 :
((pRsaPubKey->pubexp & 0x0000ff00) ? 2 : 1)));
pbMessage[0] = MSBOF(cbExp);
pbMessage[1] = LSBOF(cbExp);
pbMessage += 2;
ReverseMemCopy(pbMessage, (PBYTE)&pRsaPubKey->pubexp, cbExp);
pbMessage += cbExp;
SPExternalFree(pbBlob);
pbBlob = NULL;
fImpersonating = SslImpersonateClient();
// Generate hash values
ComputeServerExchangeHashes(
pContext,
pServerExchangeValue,
(int)(pbMessage - pServerExchangeValue),
rgbHashValue,
rgbHashValue + CB_MD5_DIGEST_LEN);
// Sign hash value.
if(!SchCryptCreateHash(pCred->hProv,
CALG_SSL3_SHAMD5,
0,
0,
&hHash,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pctRet = PCT_INT_INTERNAL_ERROR;
goto cleanup;
}
if(!SchCryptSetHashParam(hHash,
HP_HASHVAL,
rgbHashValue,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pctRet = PCT_INT_INTERNAL_ERROR;
goto cleanup;
}
DebugLog((DEB_TRACE, "Signing server_key_exchange message.\n"));
cbSignature = cbServerModulus;
if(!SchCryptSignHash(hHash,
pCred->dwKeySpec,
NULL,
0,
pbMessage + 2,
&cbSignature,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pctRet = PCT_INT_INTERNAL_ERROR;
goto cleanup;
}
DebugLog((DEB_TRACE, "Server_key_exchange message signed successfully.\n"));
SchCryptDestroyHash(hHash, pContext->RipeZombie->dwCapiFlags);
pbMessage[0] = MSBOF(cbSignature);
pbMessage[1] = LSBOF(cbSignature);
pbMessage += 2;
// Reverse signature.
for(i = 0; i < cbSignature / 2; i++)
{
BYTE n = pbMessage[i];
pbMessage[i] = pbMessage[cbSignature - i -1];
pbMessage[cbSignature - i -1] = n;
}
pbMessage += cbSignature;
*pcbServerExchangeValue = (DWORD)(pbMessage - pServerExchangeValue);
// Use ephemeral key for the new connection.
pContext->RipeZombie->hMasterProv = hEphemeralProv;
pContext->RipeZombie->dwFlags |= SP_CACHE_FLAG_MASTER_EPHEM;
pctRet = PCT_ERR_OK;
cleanup:
if(fImpersonating)
{
RevertToSelf();
}
return pctRet;
}
SP_STATUS
WINAPI
PkcsGenerateClientExchangeValue(
SPContext * pContext, // in
PUCHAR pServerExchangeValue, // in
DWORD cbServerExchangeValue, // in
PUCHAR pClientClearValue, // out
DWORD * pcbClientClearValue, // in/out
PUCHAR pClientExchangeValue, // out
DWORD * pcbClientExchangeValue) // in/out
{
PSPCredentialGroup pCred;
DWORD cbSecret;
DWORD cbMasterKey;
HCRYPTKEY hServerPublic = 0;
DWORD dwGenFlags = 0;
DWORD dwExportFlags = 0;
SP_STATUS pctRet = PCT_ERR_OK;
BLOBHEADER *pPublicBlob;
DWORD cbPublicBlob;
DWORD cbHeader;
ALG_ID Algid = 0;
DWORD cbData;
DWORD cbEncryptedKey;
DWORD dwEnabledProtocols;
DWORD dwHighestProtocol;
if(pContext->RipeZombie->hMasterProv == 0)
{
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
pCred = pContext->pCredGroup;
if(pCred == NULL)
{
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
// We're doing a full handshake.
pContext->Flags |= CONTEXT_FLAG_FULL_HANDSHAKE;
//
// Determine highest supported protocol.
//
dwEnabledProtocols = pContext->dwClientEnabledProtocols;
if(dwEnabledProtocols & SP_PROT_TLS1_CLIENT)
{
dwHighestProtocol = TLS1_CLIENT_VERSION;
}
else if(dwEnabledProtocols & SP_PROT_SSL3_CLIENT)
{
dwHighestProtocol = SSL3_CLIENT_VERSION;
}
else
{
dwHighestProtocol = SSL2_CLIENT_VERSION;
}
// Get key length.
cbSecret = pContext->pPendingCipherInfo->cbSecret;
//
// Import server's public key.
//
pPublicBlob = pContext->RipeZombie->pRemotePublic->pPublic;
cbPublicBlob = pContext->RipeZombie->pRemotePublic->cbPublic;
cbEncryptedKey = sizeof(BLOBHEADER) + sizeof(ALG_ID) + cbPublicBlob;
if(pClientExchangeValue == NULL)
{
*pcbClientExchangeValue = cbEncryptedKey;
pctRet = PCT_ERR_OK;
goto done;
}
if(*pcbClientExchangeValue < cbEncryptedKey)
{
*pcbClientExchangeValue = cbEncryptedKey;
pctRet = SP_LOG_RESULT(PCT_INT_BUFF_TOO_SMALL);
goto done;
}
if(!SchCryptImportKey(pContext->RipeZombie->hMasterProv,
(PBYTE)pPublicBlob,
cbPublicBlob,
0,
0,
&hServerPublic,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pctRet = PCT_INT_INTERNAL_ERROR;
goto done;
}
//
// Do protocol specific stuff.
//
switch(pContext->RipeZombie->fProtocol)
{
case SP_PROT_PCT1_CLIENT:
Algid = CALG_PCT1_MASTER;
dwGenFlags = CRYPT_EXPORTABLE;
// Generate the clear key value.
if(cbSecret < PCT1_MASTER_KEY_SIZE)
{
pContext->RipeZombie->cbClearKey = PCT1_MASTER_KEY_SIZE - cbSecret;
GenerateRandomBits( pContext->RipeZombie->pClearKey,
pContext->RipeZombie->cbClearKey);
*pcbClientClearValue = pContext->RipeZombie->cbClearKey;
CopyMemory( pClientClearValue,
pContext->RipeZombie->pClearKey,
pContext->RipeZombie->cbClearKey);
}
else
{
*pcbClientClearValue = pContext->RipeZombie->cbClearKey = 0;
}
break;
case SP_PROT_SSL2_CLIENT:
Algid = CALG_SSL2_MASTER;
dwGenFlags = CRYPT_EXPORTABLE;
cbMasterKey = pContext->pPendingCipherInfo->cbKey;
dwGenFlags |= ((cbSecret << 3) << 16);
// Generate the clear key value.
pContext->RipeZombie->cbClearKey = cbMasterKey - cbSecret;
if(pContext->RipeZombie->cbClearKey > 0)
{
GenerateRandomBits(pContext->RipeZombie->pClearKey,
pContext->RipeZombie->cbClearKey);
CopyMemory(pClientClearValue,
pContext->RipeZombie->pClearKey,
pContext->RipeZombie->cbClearKey);
}
*pcbClientClearValue = pContext->RipeZombie->cbClearKey;
if(dwEnabledProtocols & (SP_PROT_SSL3 | SP_PROT_TLS1))
{
// If we're a client doing SSL2, and
// SSL3 is enabled, then for some reason
// the server requested SSL2. Maybe
// A man in the middle changed the server
// version in the server hello to roll
// back. Pad with 8 0x03's so the server
// can detect this.
dwExportFlags = CRYPT_SSL2_FALLBACK;
}
break;
case SP_PROT_TLS1_CLIENT:
Algid = CALG_TLS1_MASTER;
// drop through to SSL3
case SP_PROT_SSL3_CLIENT:
dwGenFlags = CRYPT_EXPORTABLE;
if(0 == Algid)
{
Algid = CALG_SSL3_MASTER;
}
// Generate the clear key value (always empty).
pContext->RipeZombie->cbClearKey = 0;
if(pcbClientClearValue) *pcbClientClearValue = 0;
if(cbServerExchangeValue && pServerExchangeValue)
{
// In ssl3, we look at the server exchange value.
// It may be a 512-bit public key, signed
// by the server public key. In this case, we need to
// use that as our master_secret encryption key.
HCRYPTKEY hNewServerPublic;
pctRet = Ssl3ParseServerKeyExchange(pContext,
pServerExchangeValue,
cbServerExchangeValue,
hServerPublic,
&hNewServerPublic);
if(pctRet != PCT_ERR_OK)
{
goto done;
}
// Destroy public key from certificate.
SchCryptDestroyKey(hServerPublic, pContext->RipeZombie->dwCapiFlags);
// Use public key from ServerKeyExchange instead.
hServerPublic = hNewServerPublic;
}
break;
default:
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
// Generate the master_secret.
if(!SchCryptGenKey(pContext->RipeZombie->hMasterProv,
Algid,
dwGenFlags,
&pContext->RipeZombie->hMasterKey,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pctRet = PCT_INT_INTERNAL_ERROR;
goto done;
}
#if 1
// This is currently commented out because when connecting to a server running
// an old version of schannel (NT4 SP3 or so), then we will connect using SSL3,
// but the highest supported protocol is 0x0301. This confuses the server and
// it drops the connection.
// Set highest supported protocol. The CSP will place this version number
// in the pre_master_secret.
if(!SchCryptSetKeyParam(pContext->RipeZombie->hMasterKey,
KP_HIGHEST_VERSION,
(PBYTE)&dwHighestProtocol,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
}
#endif
// Encrypt the master_secret.
DebugLog((DEB_TRACE, "Encrypt the master secret.\n"));
if(!SchCryptExportKey(pContext->RipeZombie->hMasterKey,
hServerPublic,
SIMPLEBLOB,
dwExportFlags,
pClientExchangeValue,
&cbEncryptedKey,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pctRet = PCT_INT_INTERNAL_ERROR;
goto done;
}
DebugLog((DEB_TRACE, "Master secret encrypted successfully.\n"));
// Determine size of key exchange key.
cbData = sizeof(DWORD);
if(!SchCryptGetKeyParam(hServerPublic,
KP_BLOCKLEN,
(PBYTE)&pContext->RipeZombie->dwExchStrength,
&cbData,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pContext->RipeZombie->dwExchStrength = 0;
}
// Strip off the blob header and copy the encrypted master_secret
// to the output buffer. Note that it is also converted to big endian.
cbHeader = sizeof(BLOBHEADER) + sizeof(ALG_ID);
cbEncryptedKey -= cbHeader;
if(pContext->RipeZombie->fProtocol == SP_PROT_TLS1_CLIENT)
{
MoveMemory(pClientExchangeValue + 2, pClientExchangeValue + cbHeader, cbEncryptedKey);
ReverseInPlace(pClientExchangeValue + 2, cbEncryptedKey);
pClientExchangeValue[0] = MSBOF(cbEncryptedKey);
pClientExchangeValue[1] = LSBOF(cbEncryptedKey);
*pcbClientExchangeValue = 2 + cbEncryptedKey;
}
else
{
MoveMemory(pClientExchangeValue, pClientExchangeValue + cbHeader, cbEncryptedKey);
ReverseInPlace(pClientExchangeValue, cbEncryptedKey);
*pcbClientExchangeValue = cbEncryptedKey;
}
// Build the session keys.
pctRet = MakeSessionKeys(pContext,
pContext->RipeZombie->hMasterProv,
pContext->RipeZombie->hMasterKey);
if(pctRet != PCT_ERR_OK)
{
goto done;
}
// Update perf counter.
InterlockedIncrement(&g_cClientHandshakes);
pctRet = PCT_ERR_OK;
done:
if(hServerPublic) SchCryptDestroyKey(hServerPublic, pContext->RipeZombie->dwCapiFlags);
return pctRet;
}
SP_STATUS
GenerateRandomMasterKey(
PSPContext pContext,
HCRYPTKEY * phMasterKey)
{
DWORD dwGenFlags = 0;
ALG_ID Algid = 0;
DWORD cbSecret;
cbSecret = pContext->pPendingCipherInfo->cbSecret;
switch(pContext->RipeZombie->fProtocol)
{
case SP_PROT_PCT1_SERVER:
Algid = CALG_PCT1_MASTER;
dwGenFlags = CRYPT_EXPORTABLE;
break;
case SP_PROT_SSL2_SERVER:
Algid = CALG_SSL2_MASTER;
dwGenFlags = CRYPT_EXPORTABLE;
dwGenFlags |= ((cbSecret << 3) << 16);
break;
case SP_PROT_TLS1_SERVER:
Algid = CALG_TLS1_MASTER;
dwGenFlags = CRYPT_EXPORTABLE;
break;
case SP_PROT_SSL3_SERVER:
Algid = CALG_SSL3_MASTER;
dwGenFlags = CRYPT_EXPORTABLE;
break;
default:
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
// Generate the master_secret.
if(!SchCryptGenKey(pContext->RipeZombie->hMasterProv,
Algid,
dwGenFlags,
phMasterKey,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
return PCT_ERR_OK;
}
//+---------------------------------------------------------------------------
//
// Function: PkcsGenerateServerMasterKey
//
// Synopsis: Decrypt the master secret (from the ClientKeyExchange message)
// and derive the session keys from it.
//
// Arguments: [pContext] -- Schannel context.
// [pClientClearValue] -- Not used.
// [cbClientClearValue] -- Not used.
// [pClientExchangeValue] -- Pointer PKCS #2 block.
// [cbClientExchangeValue] -- Length of block.
//
// History: 10-02-97 jbanes Created.
//
// Notes: This routine is called by the server-side only.
//
//----------------------------------------------------------------------------
SP_STATUS
WINAPI
PkcsGenerateServerMasterKey(
PSPContext pContext, // in, out
PUCHAR pClientClearValue, // in
DWORD cbClientClearValue, // in
PUCHAR pClientExchangeValue, // in
DWORD cbClientExchangeValue) // in
{
PSPCredentialGroup pCred;
PBYTE pbBlob = NULL;
DWORD cbBlob;
ALG_ID Algid;
HCRYPTKEY hMasterKey;
HCRYPTKEY hExchKey = 0;
DWORD dwFlags = 0;
SP_STATUS pctRet;
DWORD cbData;
DWORD dwEnabledProtocols;
DWORD dwHighestProtocol;
BOOL fImpersonating = FALSE;
pCred = pContext->RipeZombie->pServerCred;
if(pCred == NULL)
{
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
dwEnabledProtocols = (g_ProtEnabled & pCred->grbitEnabledProtocols);
if(dwEnabledProtocols & SP_PROT_TLS1_SERVER)
{
dwHighestProtocol = TLS1_CLIENT_VERSION;
}
else if(dwEnabledProtocols & SP_PROT_SSL3_SERVER)
{
dwHighestProtocol = SSL3_CLIENT_VERSION;
}
else
{
dwHighestProtocol = SSL2_CLIENT_VERSION;
}
// We're doing a full handshake.
pContext->Flags |= CONTEXT_FLAG_FULL_HANDSHAKE;
// Determine encryption algid
switch(pContext->RipeZombie->fProtocol)
{
case SP_PROT_PCT1_SERVER:
Algid = CALG_PCT1_MASTER;
CopyMemory(pContext->RipeZombie->pClearKey,
pClientClearValue,
cbClientClearValue);
pContext->RipeZombie->cbClearKey = cbClientClearValue;
break;
case SP_PROT_SSL2_SERVER:
Algid = CALG_SSL2_MASTER;
if(dwEnabledProtocols & (SP_PROT_SSL3 | SP_PROT_TLS1))
{
// We're a server doing SSL2, and we also support SSL3.
// If the encryption block contains the 8 0x03 padding
// bytes, then abort the connection.
dwFlags = CRYPT_SSL2_FALLBACK;
}
CopyMemory(pContext->RipeZombie->pClearKey,
pClientClearValue,
cbClientClearValue);
pContext->RipeZombie->cbClearKey = cbClientClearValue;
break;
case SP_PROT_SSL3_SERVER:
Algid = CALG_SSL3_MASTER;
break;
case SP_PROT_TLS1_SERVER:
Algid = CALG_TLS1_MASTER;
break;
default:
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
// Remove (pseudo-optional) vector in front of the encrypted master key.
if(pContext->RipeZombie->fProtocol == SP_PROT_SSL3_SERVER ||
pContext->RipeZombie->fProtocol == SP_PROT_TLS1_SERVER)
{
DWORD cbMsg = MAKEWORD(pClientExchangeValue[1], pClientExchangeValue[0]);
if(cbMsg + 2 == cbClientExchangeValue)
{
pClientExchangeValue += 2;
cbClientExchangeValue -= 2;
}
}
// Allocate memory for blob.
cbBlob = sizeof(BLOBHEADER) + sizeof(ALG_ID) + cbClientExchangeValue;
pbBlob = SPExternalAlloc(cbBlob);
if(pbBlob == NULL)
{
return SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
}
// Build SIMPLEBLOB.
{
BLOBHEADER *pBlobHeader = (BLOBHEADER *)pbBlob;
ALG_ID *pAlgid = (ALG_ID *)(pBlobHeader + 1);
BYTE *pData = (BYTE *)(pAlgid + 1);
pBlobHeader->bType = SIMPLEBLOB;
pBlobHeader->bVersion = CUR_BLOB_VERSION;
pBlobHeader->reserved = 0;
pBlobHeader->aiKeyAlg = Algid;
*pAlgid = CALG_RSA_KEYX;
ReverseMemCopy(pData, pClientExchangeValue, cbClientExchangeValue);
}
DebugLog((DEB_TRACE, "Decrypt the master secret.\n"));
if(!(pContext->RipeZombie->dwFlags & SP_CACHE_FLAG_MASTER_EPHEM))
{
fImpersonating = SslImpersonateClient();
}
// Decrypt the master_secret.
if(!SchCryptGetUserKey(pContext->RipeZombie->hMasterProv,
AT_KEYEXCHANGE,
&hExchKey,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pctRet = PCT_INT_INTERNAL_ERROR;
goto cleanup;
}
if(!SchCryptImportKey(pContext->RipeZombie->hMasterProv,
pbBlob,
cbBlob,
hExchKey,
dwFlags,
&hMasterKey,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
DebugLog((DEB_TRACE, "Master secret did not decrypt correctly.\n"));
// Guard against the PKCS#1 attack by generating a
// random master key.
pctRet = GenerateRandomMasterKey(pContext, &hMasterKey);
if(pctRet != PCT_ERR_OK)
{
pctRet = PCT_INT_INTERNAL_ERROR;
goto cleanup;
}
}
else
{
DebugLog((DEB_TRACE, "Master secret decrypted successfully.\n"));
// Set highest supported protocol. The CSP will use this to check for
// version fallback attacks.
if(!SchCryptSetKeyParam(hMasterKey,
KP_HIGHEST_VERSION,
(PBYTE)&dwHighestProtocol,
CRYPT_SERVER,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
if(GetLastError() == NTE_BAD_VER)
{
pctRet = SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
SchCryptDestroyKey(hMasterKey, pContext->RipeZombie->dwCapiFlags);
goto cleanup;
}
}
}
pContext->RipeZombie->hMasterKey = hMasterKey;
// Determine size of key exchange key.
cbData = sizeof(DWORD);
if(!SchCryptGetKeyParam(hExchKey,
KP_BLOCKLEN,
(PBYTE)&pContext->RipeZombie->dwExchStrength,
&cbData,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
pContext->RipeZombie->dwExchStrength = 0;
}
SchCryptDestroyKey(hExchKey, pContext->RipeZombie->dwCapiFlags);
hExchKey = 0;
// Build the session keys.
pctRet = MakeSessionKeys(pContext,
pContext->RipeZombie->hMasterProv,
hMasterKey);
if(pctRet != PCT_ERR_OK)
{
SP_LOG_RESULT(pctRet);
goto cleanup;
}
// Update perf counter.
InterlockedIncrement(&g_cServerHandshakes);
pctRet = PCT_ERR_OK;
cleanup:
if(fImpersonating)
{
RevertToSelf();
}
if(pbBlob != NULL)
{
SPExternalFree(pbBlob);
}
if(hExchKey)
{
SchCryptDestroyKey(hExchKey, pContext->RipeZombie->dwCapiFlags);
}
return pctRet;
}
//+---------------------------------------------------------------------------
//
// Function: PkcsFinishMasterKey
//
// Synopsis: Complete the derivation of the master key by programming the
// CSP with the (protocol dependent) auxilary plaintext
// information.
//
// Arguments: [pContext] -- Schannel context.
// [hMasterKey] -- Handle to master key.
//
// History: 10-03-97 jbanes Created.
//
// Notes: This routine is called by the server-side only.
//
//----------------------------------------------------------------------------
SP_STATUS
PkcsFinishMasterKey(
PSPContext pContext, // in, out
HCRYPTKEY hMasterKey) // in
{
PCipherInfo pCipherInfo = NULL;
PHashInfo pHashInfo = NULL;
SCHANNEL_ALG Algorithm;
BOOL fExportable = TRUE;
DWORD dwCipherFlags;
if(pContext->RipeZombie->hMasterProv == 0)
{
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
// Get pointer to pending cipher system.
pCipherInfo = pContext->pPendingCipherInfo;
// Get pointer to pending hash system.
pHashInfo = pContext->pPendingHashInfo;
// Determine whether this is an "exportable" cipher.
if(pContext->dwPendingCipherSuiteIndex)
{
// Use cipher suite flags (SSL3 & TLS).
dwCipherFlags = UniAvailableCiphers[pContext->dwPendingCipherSuiteIndex].dwFlags;
if(dwCipherFlags & DOMESTIC_CIPHER_SUITE)
{
fExportable = FALSE;
}
}
else
{
// Use key length (PCT & SSL2).
if(pCipherInfo->dwStrength > 40)
{
fExportable = FALSE;
}
}
// Specify encryption algorithm.
if(pCipherInfo->aiCipher != CALG_NULLCIPHER)
{
ZeroMemory(&Algorithm, sizeof(Algorithm));
Algorithm.dwUse = SCHANNEL_ENC_KEY;
Algorithm.Algid = pCipherInfo->aiCipher;
Algorithm.cBits = pCipherInfo->cbSecret * 8;
if(fExportable)
{
Algorithm.dwFlags = INTERNATIONAL_USAGE;
}
if(!SchCryptSetKeyParam(hMasterKey,
KP_SCHANNEL_ALG,
(PBYTE)&Algorithm,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
}
// Specify hash algorithm.
Algorithm.dwUse = SCHANNEL_MAC_KEY;
Algorithm.Algid = pHashInfo->aiHash;
Algorithm.cBits = pHashInfo->cbCheckSum * 8;
if(!SchCryptSetKeyParam(hMasterKey,
KP_SCHANNEL_ALG,
(PBYTE)&Algorithm,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
// Finish creating the master_secret.
switch(pContext->RipeZombie->fProtocol)
{
case SP_PROT_PCT1_CLIENT:
case SP_PROT_PCT1_SERVER:
{
CRYPT_DATA_BLOB Data;
// Specify clear key value.
if(pContext->RipeZombie->cbClearKey)
{
Data.pbData = pContext->RipeZombie->pClearKey;
Data.cbData = pContext->RipeZombie->cbClearKey;
if(!SchCryptSetKeyParam(hMasterKey,
KP_CLEAR_KEY,
(BYTE*)&Data,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
}
// Specify the CH_CHALLENGE_DATA.
Data.pbData = pContext->pChallenge;
Data.cbData = pContext->cbChallenge;
if(!SchCryptSetKeyParam(hMasterKey,
KP_CLIENT_RANDOM,
(BYTE*)&Data,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
// Specify the SH_CONNECTION_ID_DATA.
Data.pbData = pContext->pConnectionID;
Data.cbData = pContext->cbConnectionID;
if(!SchCryptSetKeyParam(hMasterKey,
KP_SERVER_RANDOM,
(BYTE*)&Data,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
// Specify the SH_CERTIFICATE_DATA.
Data.pbData = pContext->RipeZombie->pbServerCertificate;
Data.cbData = pContext->RipeZombie->cbServerCertificate;
if(!SchCryptSetKeyParam(hMasterKey,
KP_CERTIFICATE,
(BYTE*)&Data,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
break;
}
case SP_PROT_SSL2_CLIENT:
case SP_PROT_SSL2_SERVER:
{
CRYPT_DATA_BLOB Data;
// Specify clear key value.
if(pContext->RipeZombie->cbClearKey)
{
Data.pbData = pContext->RipeZombie->pClearKey;
Data.cbData = pContext->RipeZombie->cbClearKey;
if(!SchCryptSetKeyParam(hMasterKey,
KP_CLEAR_KEY,
(BYTE*)&Data,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
}
// Specify the CH_CHALLENGE_DATA.
Data.pbData = pContext->pChallenge;
Data.cbData = pContext->cbChallenge;
if(!SchCryptSetKeyParam(hMasterKey,
KP_CLIENT_RANDOM,
(BYTE*)&Data,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
// Specify the SH_CONNECTION_ID_DATA.
Data.pbData = pContext->pConnectionID;
Data.cbData = pContext->cbConnectionID;
if(!SchCryptSetKeyParam(hMasterKey,
KP_SERVER_RANDOM,
(BYTE*)&Data,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
break;
}
case SP_PROT_SSL3_CLIENT:
case SP_PROT_SSL3_SERVER:
case SP_PROT_TLS1_CLIENT:
case SP_PROT_TLS1_SERVER:
{
CRYPT_DATA_BLOB Data;
// Specify client_random.
Data.pbData = pContext->rgbS3CRandom;
Data.cbData = CB_SSL3_RANDOM;
if(!SchCryptSetKeyParam(hMasterKey,
KP_CLIENT_RANDOM,
(BYTE*)&Data,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
// Specify server_random.
Data.pbData = pContext->rgbS3SRandom;
Data.cbData = CB_SSL3_RANDOM;
if(!SchCryptSetKeyParam(hMasterKey,
KP_SERVER_RANDOM,
(BYTE*)&Data,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
return PCT_INT_INTERNAL_ERROR;
}
break;
}
default:
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
return PCT_ERR_OK;
}
//+---------------------------------------------------------------------------
//
// Function: MakeSessionKeys
//
// Synopsis: Derive the session keys from the completed master key.
//
// Arguments: [pContext] -- Schannel context.
// [hProv] --
// [hMasterKey] -- Handle to master key.
//
// History: 10-03-97 jbanes Created.
//
// Notes: This routine is called by the server-side only.
//
//----------------------------------------------------------------------------
SP_STATUS
MakeSessionKeys(
PSPContext pContext, // in
HCRYPTPROV hProv, // in
HCRYPTKEY hMasterKey) // in
{
HCRYPTHASH hMasterHash = 0;
HCRYPTKEY hLocalMasterKey = 0;
BOOL fClient;
SP_STATUS pctRet;
//
// Duplicate the master key if we're doing a reconnect handshake. This
// will allow us to set the client_random and server_random properties
// on the key without having to worry about different threads
// interferring with each other.
//
if((pContext->Flags & CONTEXT_FLAG_FULL_HANDSHAKE) == 0)
{
if(!CryptDuplicateKey(hMasterKey, NULL, 0, &hLocalMasterKey))
{
pctRet = SP_LOG_RESULT(GetLastError());
goto cleanup;
}
hMasterKey = hLocalMasterKey;
}
// Finish the master_secret.
pctRet = PkcsFinishMasterKey(pContext, hMasterKey);
if(pctRet != PCT_ERR_OK)
{
SP_LOG_RESULT(pctRet);
goto cleanup;
}
fClient = !(pContext->RipeZombie->fProtocol & SP_PROT_SERVERS);
// Create the master hash object from the master_secret key.
if(!SchCryptCreateHash(hProv,
CALG_SCHANNEL_MASTER_HASH,
hMasterKey,
0,
&hMasterHash,
0))
{
pctRet = SP_LOG_RESULT(GetLastError());
goto cleanup;
}
// Derive read key from the master hash object.
if(pContext->hPendingReadKey)
{
SchCryptDestroyKey(pContext->hPendingReadKey, 0);
pContext->hPendingReadKey = 0;
}
if(pContext->pPendingCipherInfo->aiCipher != CALG_NULLCIPHER)
{
if(!SchCryptDeriveKey(hProv,
CALG_SCHANNEL_ENC_KEY,
hMasterHash,
CRYPT_EXPORTABLE | (fClient ? CRYPT_SERVER : 0),
&pContext->hPendingReadKey,
0))
{
pctRet = SP_LOG_RESULT(GetLastError());
goto cleanup;
}
}
// Derive write key from the master hash object.
if(pContext->hPendingWriteKey)
{
SchCryptDestroyKey(pContext->hPendingWriteKey, 0);
pContext->hPendingWriteKey = 0;
}
if(pContext->pPendingCipherInfo->aiCipher != CALG_NULLCIPHER)
{
if(!SchCryptDeriveKey(hProv,
CALG_SCHANNEL_ENC_KEY,
hMasterHash,
CRYPT_EXPORTABLE | (fClient ? 0 : CRYPT_SERVER),
&pContext->hPendingWriteKey,
0))
{
pctRet = SP_LOG_RESULT(GetLastError());
goto cleanup;
}
}
if((pContext->RipeZombie->fProtocol & SP_PROT_SSL2) ||
(pContext->RipeZombie->fProtocol & SP_PROT_PCT1))
{
// Set the IV on the client and server encryption keys
if(!SchCryptSetKeyParam(pContext->hPendingReadKey,
KP_IV,
pContext->RipeZombie->pKeyArgs,
0,
0))
{
pctRet = SP_LOG_RESULT(GetLastError());
goto cleanup;
}
if(!SchCryptSetKeyParam(pContext->hPendingWriteKey,
KP_IV,
pContext->RipeZombie->pKeyArgs,
0,
0))
{
pctRet = SP_LOG_RESULT(GetLastError());
goto cleanup;
}
}
if(pContext->RipeZombie->fProtocol & SP_PROT_SSL2)
{
// SSL 2.0 uses same set of keys for both encryption and MAC.
pContext->hPendingReadMAC = 0;
pContext->hPendingWriteMAC = 0;
}
else
{
// Derive read MAC from the master hash object.
if(pContext->hPendingReadMAC)
{
SchCryptDestroyKey(pContext->hPendingReadMAC, 0);
}
if(!SchCryptDeriveKey(hProv,
CALG_SCHANNEL_MAC_KEY,
hMasterHash,
CRYPT_EXPORTABLE | (fClient ? CRYPT_SERVER : 0),
&pContext->hPendingReadMAC,
0))
{
pctRet = SP_LOG_RESULT(GetLastError());
goto cleanup;
}
// Derive write MAC from the master hash object.
if(pContext->hPendingWriteMAC)
{
SchCryptDestroyKey(pContext->hPendingWriteMAC, 0);
}
if(!SchCryptDeriveKey(hProv,
CALG_SCHANNEL_MAC_KEY,
hMasterHash,
CRYPT_EXPORTABLE | (fClient ? 0 : CRYPT_SERVER),
&pContext->hPendingWriteMAC,
0))
{
pctRet = SP_LOG_RESULT(GetLastError());
goto cleanup;
}
}
pctRet = PCT_ERR_OK;
cleanup:
if(hMasterHash)
{
SchCryptDestroyHash(hMasterHash, 0);
}
if(hLocalMasterKey)
{
CryptDestroyKey(hLocalMasterKey);
}
return pctRet;
}
//+---------------------------------------------------------------------------
//
// Function: Ssl3ParseServerKeyExchange
//
// Synopsis: Parse the ServerKeyExchange message and import modulus and
// exponent into a CryptoAPI public key.
//
// Arguments: [pContext] -- Schannel context.
//
// [pbMessage] -- Pointer to message.
//
// [cbMessage] -- Message length.
//
// [hServerPublic] -- Handle to public key from server's
// certificate. This is used to verify
// the message's signature.
//
// [phNewServerPublic] -- (output) Handle to new public key.
//
//
// History: 10-23-97 jbanes Created.
//
// Notes: This routine is called by the client-side only.
//
// The format of the ServerKeyExchange message is:
//
// struct {
// select (KeyExchangeAlgorithm) {
// case diffie_hellman:
// ServerDHParams params;
// Signature signed_params;
// case rsa:
// ServerRSAParams params;
// Signature signed_params;
// case fortezza_dms:
// ServerFortezzaParams params;
// };
// } ServerKeyExchange;
//
// struct {
// opaque rsa_modulus<1..2^16-1>;
// opaque rsa_exponent<1..2^16-1>;
// } ServerRSAParams;
//
//----------------------------------------------------------------------------
SP_STATUS
Ssl3ParseServerKeyExchange(
PSPContext pContext, // in
PBYTE pbMessage, // in
DWORD cbMessage, // in
HCRYPTKEY hServerPublic, // in
HCRYPTKEY *phNewServerPublic) // out
{
PBYTE pbModulus = NULL;
DWORD cbModulus;
PBYTE pbExponent = NULL;
DWORD cbExponent;
PBYTE pbServerParams = NULL;
DWORD cbServerParams;
DWORD dwExponent;
SP_STATUS pctRet;
DWORD i;
if(pbMessage == NULL || cbMessage == 0)
{
*phNewServerPublic = 0;
return PCT_ERR_OK;
}
// Mark start of ServerRSAParams structure.
// This is used to build hash values.
pbServerParams = pbMessage;
// Modulus length
cbModulus = MAKEWORD(pbMessage[1], pbMessage[0]);
pbMessage += 2;
// Since the modulus is encoded as an INTEGER, it is padded with a leading
// zero if its most significant bit is one. Remove this padding, if
// present.
if(pbMessage[0] == 0)
{
cbModulus -= 1;
pbMessage += 1;
}
if(cbModulus < 512/8 || cbModulus > 1024/8)
{
return SP_LOG_RESULT(PCT_INT_ILLEGAL_MSG);
}
// Modulus
pbModulus = pbMessage;
pbMessage += cbModulus;
// Exponent length
cbExponent = MAKEWORD(pbMessage[1], pbMessage[0]);
if(cbExponent < 1 || cbExponent > 4)
{
return SP_LOG_RESULT(PCT_INT_ILLEGAL_MSG);
}
pbMessage += 2;
// Exponent
pbExponent = pbMessage;
pbMessage += cbExponent;
// form a (little endian) DWORD from exponent data
dwExponent = 0;
for(i = 0; i < cbExponent; i++)
{
dwExponent <<= 8;
dwExponent |= pbExponent[i];
}
// Compute length of ServerRSAParams structure.
cbServerParams = (DWORD)(pbMessage - pbServerParams);
//
// digitally-signed struct {
// select(SignatureAlgorithm) {
// case anonymous: struct { };
// case rsa:
// opaque md5_hash[16];
// opaque sha_hash[20];
// case dsa:
// opaque sha_hash[20];
// };
// } Signature;
//
{
BYTE rgbHashValue[CB_MD5_DIGEST_LEN + CB_SHA_DIGEST_LEN];
PBYTE pbSignature;
DWORD cbSignature;
HCRYPTHASH hHash;
PBYTE pbLocalBuffer;
DWORD cbLocalBuffer;
// Signature block length
cbSignature = ((INT)pbMessage[0] << 8) + pbMessage[1];
pbMessage += 2;
pbSignature = pbMessage;
// Allocate buffer for RSA operation.
cbLocalBuffer = cbSignature;
pbLocalBuffer = SPExternalAlloc(cbLocalBuffer);
if(pbLocalBuffer == NULL)
{
return SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
}
// Reverse the signature.
ReverseMemCopy(pbLocalBuffer, pbSignature, cbSignature);
// Compute MD5 and SHA hash values.
ComputeServerExchangeHashes(pContext,
pbServerParams,
cbServerParams,
rgbHashValue,
rgbHashValue + CB_MD5_DIGEST_LEN);
if(!SchCryptCreateHash(pContext->RipeZombie->hMasterProv,
CALG_SSL3_SHAMD5,
0,
0,
&hHash,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
SPExternalFree(pbLocalBuffer);
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
// set hash value
if(!SchCryptSetHashParam(hHash,
HP_HASHVAL,
rgbHashValue,
0,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
SchCryptDestroyHash(hHash, pContext->RipeZombie->dwCapiFlags);
SPExternalFree(pbLocalBuffer);
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
DebugLog((DEB_TRACE, "Verify server_key_exchange message signature.\n"));
if(!SchCryptVerifySignature(hHash,
pbLocalBuffer,
cbSignature,
hServerPublic,
NULL,
0,
pContext->RipeZombie->dwCapiFlags))
{
DebugLog((DEB_WARN, "Signature Verify Failed: %x\n", GetLastError()));
SchCryptDestroyHash(hHash, pContext->RipeZombie->dwCapiFlags);
SPExternalFree(pbLocalBuffer);
return SP_LOG_RESULT(PCT_ERR_INTEGRITY_CHECK_FAILED);
}
DebugLog((DEB_TRACE, "Server_key_exchange message signature verified okay.\n"));
SchCryptDestroyHash(hHash, pContext->RipeZombie->dwCapiFlags);
SPExternalFree(pbLocalBuffer);
}
//
// Import ephemeral public key into CSP.
//
{
BLOBHEADER *pBlobHeader;
RSAPUBKEY *pRsaPubKey;
PBYTE pbBlob;
DWORD cbBlob;
// Allocate memory for PUBLICKEYBLOB.
cbBlob = sizeof(BLOBHEADER) + sizeof(RSAPUBKEY) + cbModulus;
pbBlob = SPExternalAlloc(cbBlob);
if(pbBlob == NULL)
{
return SP_LOG_RESULT(SEC_E_INSUFFICIENT_MEMORY);
}
// Build PUBLICKEYBLOB from modulus and exponent.
pBlobHeader = (BLOBHEADER *)pbBlob;
pRsaPubKey = (RSAPUBKEY *)(pBlobHeader + 1);
pBlobHeader->bType = PUBLICKEYBLOB;
pBlobHeader->bVersion = CUR_BLOB_VERSION;
pBlobHeader->reserved = 0;
pBlobHeader->aiKeyAlg = CALG_RSA_KEYX;
pRsaPubKey->magic = 0x31415352; // RSA1
pRsaPubKey->bitlen = cbModulus * 8;
pRsaPubKey->pubexp = dwExponent;
ReverseMemCopy((PBYTE)(pRsaPubKey + 1), pbModulus, cbModulus);
if(!SchCryptImportKey(pContext->RipeZombie->hMasterProv,
pbBlob,
cbBlob,
0,
0,
phNewServerPublic,
pContext->RipeZombie->dwCapiFlags))
{
SP_LOG_RESULT(GetLastError());
SPExternalFree(pbBlob);
return SP_LOG_RESULT(PCT_INT_INTERNAL_ERROR);
}
SPExternalFree(pbBlob);
}
return PCT_ERR_OK;
}