1790 lines
48 KiB
C++
1790 lines
48 KiB
C++
//----------------------------------------------------------------------------
|
|
//
|
|
// Secure channel support.
|
|
// Code lifted from the SDK sample security\ssl.
|
|
//
|
|
// Copyright (C) Microsoft Corporation, 2000.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "pch.hpp"
|
|
|
|
HMODULE g_hSecurity;
|
|
SecurityFunctionTable g_SecurityFunc;
|
|
|
|
HCERTSTORE g_hMyCertStore;
|
|
|
|
enum
|
|
{
|
|
SEQ_INTERNAL = 0xffffff00
|
|
};
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Basic schannel support functions.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
void
|
|
DbgDumpBuffers(PCSTR Name, SecBufferDesc* Desc)
|
|
{
|
|
#if 0
|
|
ULONG i;
|
|
|
|
g_NtDllCalls.DbgPrint("%s desc %p has %d buffers\n",
|
|
Name, Desc, Desc->cBuffers);
|
|
for (i = 0; i < Desc->cBuffers; i++)
|
|
{
|
|
g_NtDllCalls.DbgPrint(" type %d, %X bytes at %p\n",
|
|
Desc->pBuffers[i].BufferType,
|
|
Desc->pBuffers[i].cbBuffer,
|
|
Desc->pBuffers[i].pvBuffer);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if 0
|
|
#define DSCHAN(Args) g_NtDllCalls.DbgPrint Args
|
|
#define DumpBuffers(Name, Desc) DbgDumpBuffers(Name, Desc)
|
|
#else
|
|
#define DSCHAN(Args)
|
|
#define DumpBuffers(Name, Desc)
|
|
#endif
|
|
|
|
#if 0
|
|
#define DSCHAN_IO(Args) g_NtDllCalls.DbgPrint Args
|
|
#define DumpBuffersIo(Name, Desc) DbgDumpBuffers(Name, Desc)
|
|
#else
|
|
#define DSCHAN_IO(Args)
|
|
#define DumpBuffersIo(Name, Desc)
|
|
#endif
|
|
|
|
HRESULT
|
|
LoadSecurityLibrary(void)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if ((Status = InitDynamicCalls(&g_Crypt32CallsDesc)) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
PSecurityFunctionTable pSecurityFunc;
|
|
INIT_SECURITY_INTERFACE pInitSecurityInterface;
|
|
|
|
if (g_hSecurity != NULL)
|
|
{
|
|
// Already loaded.
|
|
return S_OK;
|
|
}
|
|
|
|
if (g_Crypt32Calls.CertOpenStore == NULL)
|
|
{
|
|
// Unable to load crypt32.dll.
|
|
return HRESULT_FROM_WIN32(ERROR_MOD_NOT_FOUND);
|
|
}
|
|
|
|
g_hSecurity = LoadLibrary("security.dll");
|
|
if (g_hSecurity == NULL)
|
|
{
|
|
goto EH_Fail;
|
|
}
|
|
|
|
pInitSecurityInterface = (INIT_SECURITY_INTERFACE)
|
|
GetProcAddress(g_hSecurity, "InitSecurityInterfaceA");
|
|
if (pInitSecurityInterface == NULL)
|
|
{
|
|
goto EH_Dll;
|
|
}
|
|
|
|
pSecurityFunc = pInitSecurityInterface();
|
|
if (pSecurityFunc == NULL)
|
|
{
|
|
goto EH_Dll;
|
|
}
|
|
|
|
memcpy(&g_SecurityFunc, pSecurityFunc, sizeof(g_SecurityFunc));
|
|
|
|
return S_OK;
|
|
|
|
EH_Dll:
|
|
FreeLibrary(g_hSecurity);
|
|
g_hSecurity = NULL;
|
|
EH_Fail:
|
|
return WIN32_LAST_STATUS();
|
|
}
|
|
|
|
HRESULT
|
|
CreateCredentials(LPSTR pszUserName,
|
|
BOOL fMachineStore,
|
|
BOOL Server,
|
|
ULONG dwProtocol,
|
|
SCHANNEL_CRED* ScCreds,
|
|
PCredHandle phCreds)
|
|
{
|
|
TimeStamp tsExpiry;
|
|
SECURITY_STATUS Status;
|
|
PCCERT_CONTEXT pCertContext = NULL;
|
|
|
|
// Open the "MY" certificate store.
|
|
if (g_hMyCertStore == NULL)
|
|
{
|
|
if (fMachineStore)
|
|
{
|
|
g_hMyCertStore = g_Crypt32Calls.
|
|
CertOpenStore(CERT_STORE_PROV_SYSTEM,
|
|
X509_ASN_ENCODING,
|
|
0,
|
|
CERT_SYSTEM_STORE_LOCAL_MACHINE,
|
|
L"MY");
|
|
}
|
|
else
|
|
{
|
|
g_hMyCertStore = g_Crypt32Calls.
|
|
CertOpenSystemStore(0, "MY");
|
|
}
|
|
|
|
if (!g_hMyCertStore)
|
|
{
|
|
Status = WIN32_LAST_STATUS();
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If a user name is specified, then attempt to find a client
|
|
// certificate. Otherwise, just create a NULL credential.
|
|
//
|
|
|
|
if (pszUserName != NULL && *pszUserName)
|
|
{
|
|
// Find certificate. Note that this sample just searches for a
|
|
// certificate that contains the user name somewhere in the subject
|
|
// name. A real application should be a bit less casual.
|
|
pCertContext = g_Crypt32Calls.
|
|
CertFindCertificateInStore(g_hMyCertStore,
|
|
X509_ASN_ENCODING,
|
|
0,
|
|
CERT_FIND_SUBJECT_STR_A,
|
|
pszUserName,
|
|
NULL);
|
|
if (pCertContext == NULL)
|
|
{
|
|
Status = WIN32_LAST_STATUS();
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Build Schannel credential structure. Currently, this sample only
|
|
// specifies the protocol to be used (and optionally the certificate,
|
|
// of course). Real applications may wish to specify other parameters
|
|
// as well.
|
|
//
|
|
|
|
ZeroMemory(ScCreds, sizeof(*ScCreds));
|
|
|
|
ScCreds->dwVersion = SCHANNEL_CRED_VERSION;
|
|
|
|
if (pCertContext != NULL)
|
|
{
|
|
ScCreds->cCreds = 1;
|
|
ScCreds->paCred = &pCertContext;
|
|
}
|
|
|
|
ScCreds->grbitEnabledProtocols = dwProtocol;
|
|
|
|
if (!Server)
|
|
{
|
|
if (pCertContext != NULL)
|
|
{
|
|
ScCreds->dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
|
|
}
|
|
else
|
|
{
|
|
ScCreds->dwFlags |= SCH_CRED_USE_DEFAULT_CREDS;
|
|
}
|
|
ScCreds->dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION;
|
|
}
|
|
|
|
|
|
//
|
|
// Create an SSPI credential.
|
|
//
|
|
|
|
//
|
|
// NOTE: In theory, an application could enumerate the security packages
|
|
// until it finds one with attributes it likes. Some applications
|
|
// (such as IIS) enumerate the packages and call AcquireCredentialsHandle
|
|
// on each until it finds one that accepts the SCHANNEL_CRED structure.
|
|
// If an application has its heart set on using SSL, like this sample
|
|
// does, then just hardcoding the UNISP_NAME package name when calling
|
|
// AcquireCredentialsHandle is not a bad thing.
|
|
//
|
|
|
|
Status = g_SecurityFunc.AcquireCredentialsHandle(
|
|
NULL, // Name of principal
|
|
UNISP_NAME_A, // Name of package
|
|
Server ? // Flags indicating use
|
|
SECPKG_CRED_INBOUND :
|
|
SECPKG_CRED_OUTBOUND,
|
|
NULL, // Pointer to logon ID
|
|
ScCreds, // Package specific data
|
|
NULL, // Pointer to GetKey() func
|
|
NULL, // Value to pass to GetKey()
|
|
phCreds, // (out) Cred Handle
|
|
&tsExpiry); // (out) Lifetime (optional)
|
|
|
|
//
|
|
// Free the certificate context. Schannel has already made its own copy.
|
|
//
|
|
|
|
if (pCertContext)
|
|
{
|
|
g_Crypt32Calls.CertFreeCertificateContext(pCertContext);
|
|
}
|
|
|
|
Exit:
|
|
DSCHAN(("CreateCredentials returns %X\n", Status));
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
VerifyRemoteCertificate(PCtxtHandle Context,
|
|
PSTR pszServerName,
|
|
DWORD dwCertFlags)
|
|
{
|
|
SSL_EXTRA_CERT_CHAIN_POLICY_PARA SslPara;
|
|
CERT_CHAIN_POLICY_PARA PolicyPara;
|
|
CERT_CHAIN_POLICY_STATUS PolicyStatus;
|
|
CERT_CHAIN_PARA ChainPara;
|
|
PCCERT_CHAIN_CONTEXT pChain = NULL;
|
|
PCCERT_CONTEXT pCert = NULL;
|
|
HRESULT Status;
|
|
PWSTR pwszServerName;
|
|
DWORD cchServerName;
|
|
|
|
// Read the remote certificate.
|
|
if ((Status = g_SecurityFunc.
|
|
QueryContextAttributes(Context,
|
|
SECPKG_ATTR_REMOTE_CERT_CONTEXT,
|
|
&pCert)) != S_OK)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (pCert == NULL)
|
|
{
|
|
Status = SEC_E_WRONG_PRINCIPAL;
|
|
goto EH_Cert;
|
|
}
|
|
|
|
if (pszServerName != NULL && *pszServerName)
|
|
{
|
|
//
|
|
// Convert server name to unicode.
|
|
//
|
|
|
|
cchServerName = MultiByteToWideChar(CP_ACP, 0, pszServerName,
|
|
-1, NULL, 0);
|
|
pwszServerName = (PWSTR)
|
|
LocalAlloc(LMEM_FIXED, cchServerName * sizeof(WCHAR));
|
|
if (pwszServerName == NULL)
|
|
{
|
|
Status = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto EH_Cert;
|
|
}
|
|
cchServerName = MultiByteToWideChar(CP_ACP, 0, pszServerName,
|
|
-1, pwszServerName, cchServerName);
|
|
if (cchServerName == 0)
|
|
{
|
|
Status = SEC_E_WRONG_PRINCIPAL;
|
|
goto EH_Name;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pwszServerName = NULL;
|
|
}
|
|
|
|
//
|
|
// Build certificate chain.
|
|
//
|
|
|
|
ZeroMemory(&ChainPara, sizeof(ChainPara));
|
|
ChainPara.cbSize = sizeof(ChainPara);
|
|
|
|
if (!g_Crypt32Calls.CertGetCertificateChain(NULL,
|
|
pCert,
|
|
NULL,
|
|
pCert->hCertStore,
|
|
&ChainPara,
|
|
0,
|
|
NULL,
|
|
&pChain))
|
|
{
|
|
Status = WIN32_LAST_STATUS();
|
|
goto EH_Name;
|
|
}
|
|
|
|
|
|
//
|
|
// Validate certificate chain.
|
|
//
|
|
|
|
ZeroMemory(&SslPara, sizeof(SslPara));
|
|
SslPara.cbStruct = sizeof(SslPara);
|
|
SslPara.dwAuthType = pwszServerName == NULL ?
|
|
AUTHTYPE_CLIENT : AUTHTYPE_SERVER;
|
|
SslPara.fdwChecks = dwCertFlags;
|
|
SslPara.pwszServerName = pwszServerName;
|
|
|
|
ZeroMemory(&PolicyPara, sizeof(PolicyPara));
|
|
PolicyPara.cbSize = sizeof(PolicyPara);
|
|
PolicyPara.pvExtraPolicyPara = &SslPara;
|
|
|
|
ZeroMemory(&PolicyStatus, sizeof(PolicyStatus));
|
|
PolicyStatus.cbSize = sizeof(PolicyStatus);
|
|
|
|
if (!g_Crypt32Calls.CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL,
|
|
pChain,
|
|
&PolicyPara,
|
|
&PolicyStatus))
|
|
{
|
|
Status = WIN32_LAST_STATUS();
|
|
goto EH_Chain;
|
|
}
|
|
|
|
if (PolicyStatus.dwError)
|
|
{
|
|
Status = PolicyStatus.dwError;
|
|
}
|
|
else
|
|
{
|
|
Status = S_OK;
|
|
}
|
|
|
|
EH_Chain:
|
|
g_Crypt32Calls.CertFreeCertificateChain(pChain);
|
|
EH_Name:
|
|
if (pwszServerName != NULL)
|
|
{
|
|
LocalFree(pwszServerName);
|
|
}
|
|
EH_Cert:
|
|
g_Crypt32Calls.CertFreeCertificateContext(pCert);
|
|
Exit:
|
|
DSCHAN(("VerifyRemoteCertificate returns %X\n", Status));
|
|
return Status;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Schannel wrapper transport.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#define SecHandleIsValid(Handle) \
|
|
((Handle)->dwLower != -1 || (Handle)->dwUpper != -1)
|
|
|
|
DbgRpcSecureChannelTransport::
|
|
DbgRpcSecureChannelTransport(ULONG ThisTransport,
|
|
ULONG BaseTransport)
|
|
{
|
|
m_Name = g_DbgRpcTransportNames[ThisTransport];
|
|
m_ThisTransport = ThisTransport;
|
|
m_BaseTransport = BaseTransport;
|
|
m_Stream = NULL;
|
|
SecInvalidateHandle(&m_Creds);
|
|
m_OwnCreds = FALSE;
|
|
SecInvalidateHandle(&m_Context);
|
|
m_OwnContext = FALSE;
|
|
m_BufferUsed = 0;
|
|
m_Server = FALSE;
|
|
}
|
|
|
|
DbgRpcSecureChannelTransport::~DbgRpcSecureChannelTransport(void)
|
|
{
|
|
if (SecHandleIsValid(&m_Context))
|
|
{
|
|
if (m_Server)
|
|
{
|
|
DisconnectFromClient();
|
|
}
|
|
else
|
|
{
|
|
DisconnectFromServer();
|
|
}
|
|
}
|
|
|
|
delete m_Stream;
|
|
if (m_OwnContext && SecHandleIsValid(&m_Context))
|
|
{
|
|
g_SecurityFunc.DeleteSecurityContext(&m_Context);
|
|
}
|
|
if (m_OwnCreds && SecHandleIsValid(&m_Creds))
|
|
{
|
|
g_SecurityFunc.FreeCredentialsHandle(&m_Creds);
|
|
}
|
|
}
|
|
|
|
ULONG
|
|
DbgRpcSecureChannelTransport::GetNumberParameters(void)
|
|
{
|
|
return 2 + (m_Stream != NULL ? m_Stream->GetNumberParameters() : 0);
|
|
}
|
|
|
|
void
|
|
DbgRpcSecureChannelTransport::GetParameter(ULONG Index, PSTR Name, PSTR Value)
|
|
{
|
|
switch(Index)
|
|
{
|
|
case 0:
|
|
if (m_Protocol)
|
|
{
|
|
strcpy(Name, "Proto");
|
|
switch(m_Protocol)
|
|
{
|
|
case SP_PROT_PCT1:
|
|
strcpy(Name, "PCT1");
|
|
break;
|
|
case SP_PROT_SSL2:
|
|
strcpy(Name, "SSL2");
|
|
break;
|
|
case SP_PROT_SSL3:
|
|
strcpy(Name, "SSL3");
|
|
break;
|
|
case SP_PROT_TLS1:
|
|
strcpy(Name, "TLS1");
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 1:
|
|
if (m_User[0])
|
|
{
|
|
strcpy(Name, m_MachineStore ? "MachUser" : "CertUser");
|
|
strcpy(Value, m_User);
|
|
}
|
|
break;
|
|
default:
|
|
if (m_Stream != NULL)
|
|
{
|
|
m_Stream->GetParameter(Index - 2, Name, Value);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
DbgRpcSecureChannelTransport::ResetParameters(void)
|
|
{
|
|
m_Protocol = 0;
|
|
m_User[0] = 0;
|
|
m_MachineStore = FALSE;
|
|
|
|
if (m_Stream == NULL)
|
|
{
|
|
m_Stream = DbgRpcNewTransport(m_BaseTransport);
|
|
}
|
|
|
|
if (m_Stream != NULL)
|
|
{
|
|
m_Stream->ResetParameters();
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
DbgRpcSecureChannelTransport::SetParameter(PCSTR Name, PCSTR Value)
|
|
{
|
|
if (m_Stream == NULL)
|
|
{
|
|
// Force all initialization to fail.
|
|
return FALSE;
|
|
}
|
|
|
|
if (!_stricmp(Name, "proto"))
|
|
{
|
|
if (Value == NULL)
|
|
{
|
|
DbgRpcError("%s parameters: "
|
|
"the protocol name was not specified correctly\n",
|
|
m_Name);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!_stricmp(Value, "pct1"))
|
|
{
|
|
m_Protocol = SP_PROT_PCT1;
|
|
}
|
|
else if (!_stricmp(Value, "ssl2"))
|
|
{
|
|
m_Protocol = SP_PROT_SSL2;
|
|
}
|
|
else if (!_stricmp(Value, "ssl3"))
|
|
{
|
|
m_Protocol = SP_PROT_SSL3;
|
|
}
|
|
else if (!_stricmp(Value, "tls1"))
|
|
{
|
|
m_Protocol = SP_PROT_TLS1;
|
|
}
|
|
else
|
|
{
|
|
DbgRpcError("%s parameters: unknown protocol '%s'\n", Value,
|
|
m_Name);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else if (!_stricmp(Name, "machuser"))
|
|
{
|
|
if (Value == NULL)
|
|
{
|
|
DbgRpcError("%s parameters: "
|
|
"the user name was not specified correctly\n",
|
|
m_Name);
|
|
return FALSE;
|
|
}
|
|
|
|
m_User[0] = 0;
|
|
strncat(m_User, Value, sizeof(m_User) - 1);
|
|
m_MachineStore = TRUE;
|
|
}
|
|
else if (!_stricmp(Name, "certuser"))
|
|
{
|
|
if (Value == NULL)
|
|
{
|
|
DbgRpcError("%s parameters: "
|
|
"the user name was not specified correctly\n",
|
|
m_Name);
|
|
return FALSE;
|
|
}
|
|
|
|
m_User[0] = 0;
|
|
strncat(m_User, Value, sizeof(m_User) - 1);
|
|
m_MachineStore = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (!m_Stream->SetParameter(Name, Value))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
DbgRpcTransport*
|
|
DbgRpcSecureChannelTransport::Clone(void)
|
|
{
|
|
DbgRpcTransport* Stream = m_Stream->Clone();
|
|
if (Stream == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
DbgRpcSecureChannelTransport* Trans =
|
|
new DbgRpcSecureChannelTransport(m_ThisTransport, m_BaseTransport);
|
|
if (Trans != NULL)
|
|
{
|
|
Trans->m_Stream = Stream;
|
|
Trans->m_Creds = m_Creds;
|
|
Trans->m_OwnCreds = FALSE;
|
|
Trans->m_Context = m_Context;
|
|
Trans->m_OwnContext = FALSE;
|
|
Trans->m_Protocol = m_Protocol;
|
|
strcpy(Trans->m_User, m_User);
|
|
Trans->m_MachineStore = m_MachineStore;
|
|
Trans->m_Sizes = m_Sizes;
|
|
Trans->m_MaxChunk = m_MaxChunk;
|
|
Trans->m_Server = m_Server;
|
|
}
|
|
else
|
|
{
|
|
delete Stream;
|
|
}
|
|
return Trans;
|
|
}
|
|
|
|
HRESULT
|
|
DbgRpcSecureChannelTransport::CreateServer(void)
|
|
{
|
|
HRESULT Status;
|
|
|
|
if ((Status = LoadSecurityLibrary()) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
if ((Status = CreateCredentials(m_User, m_MachineStore, TRUE,
|
|
m_Protocol, &m_ScCreds, &m_Creds)) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
m_OwnCreds = TRUE;
|
|
|
|
if ((Status = m_Stream->CreateServer()) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
m_Server = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
DbgRpcSecureChannelTransport::AcceptConnection(DbgRpcTransport** ClientTrans,
|
|
PSTR Identity)
|
|
{
|
|
HRESULT Status;
|
|
DbgRpcTransport* Stream;
|
|
|
|
if ((Status = m_Stream->AcceptConnection(&Stream, Identity)) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
DbgRpcSecureChannelTransport* Trans =
|
|
new DbgRpcSecureChannelTransport(m_ThisTransport, m_BaseTransport);
|
|
if (Trans == NULL)
|
|
{
|
|
delete Stream;
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
Trans->m_Stream = Stream;
|
|
Trans->m_Creds = m_Creds;
|
|
Trans->m_OwnCreds = FALSE;
|
|
Trans->m_Server = TRUE;
|
|
|
|
if ((Status = Trans->AuthenticateClientConnection()) != S_OK)
|
|
{
|
|
goto EH_Trans;
|
|
}
|
|
|
|
if ((Status = Trans->GetSizes()) != S_OK)
|
|
{
|
|
goto EH_Trans;
|
|
}
|
|
|
|
// Attempt to validate client certificate.
|
|
if ((Status = VerifyRemoteCertificate(&Trans->m_Context, NULL, 0)) != S_OK)
|
|
{
|
|
goto EH_Trans;
|
|
}
|
|
|
|
*ClientTrans = Trans;
|
|
return S_OK;
|
|
|
|
EH_Trans:
|
|
delete Trans;
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
DbgRpcSecureChannelTransport::ConnectServer(void)
|
|
{
|
|
HRESULT Status = m_Stream->ConnectServer();
|
|
if (Status != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if ((Status = LoadSecurityLibrary()) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
if ((Status = CreateCredentials(m_User, m_MachineStore, FALSE,
|
|
m_Protocol, &m_ScCreds, &m_Creds)) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
m_OwnCreds = TRUE;
|
|
|
|
if ((Status = InitiateServerConnection(m_Stream->m_ServerName)) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if ((Status = AuthenticateServerConnection()) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if ((Status = GetSizes()) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
// Attempt to validate server certificate.
|
|
if ((Status = VerifyRemoteCertificate(&m_Context,
|
|
m_Stream->m_ServerName, 0)) != S_OK)
|
|
{
|
|
// If this fails with CERT_E_CN_NO_MATCH it's most
|
|
// likely that the server name wasn't given as a fully
|
|
// qualified machine name. We may just want to ignore that error.
|
|
return Status;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
ULONG
|
|
DbgRpcSecureChannelTransport::Read(ULONG Seq, PVOID Buffer, ULONG Len)
|
|
{
|
|
SecBufferDesc Message;
|
|
SecBuffer Buffers[4];
|
|
DWORD Status;
|
|
ULONG Complete;
|
|
|
|
DSCHAN_IO(("Start read(%X) with %X bytes cached\n",
|
|
Len, m_BufferUsed));
|
|
|
|
//
|
|
// Initialize security buffer structs
|
|
//
|
|
|
|
Message.ulVersion = SECBUFFER_VERSION;
|
|
Message.cBuffers = 4;
|
|
Message.pBuffers = Buffers;
|
|
|
|
//
|
|
// Receive the data from the client.
|
|
//
|
|
|
|
Complete = 0;
|
|
|
|
while (Complete < Len)
|
|
{
|
|
do
|
|
{
|
|
// Pass in the data we have so far.
|
|
Buffers[0].pvBuffer = m_Buffer;
|
|
Buffers[0].cbBuffer = m_BufferUsed;
|
|
Buffers[0].BufferType = SECBUFFER_DATA;
|
|
|
|
// Provide extra buffers for header, trailer
|
|
// and possibly extra data.
|
|
Buffers[1].BufferType = SECBUFFER_EMPTY;
|
|
Buffers[2].BufferType = SECBUFFER_EMPTY;
|
|
Buffers[3].BufferType = SECBUFFER_EMPTY;
|
|
|
|
Status = g_SecurityFunc.DecryptMessage(&m_Context, &Message,
|
|
Seq, NULL);
|
|
|
|
DSCHAN_IO(("Read DecryptMessage on %X bytes returns %X\n",
|
|
m_BufferUsed, Status));
|
|
DumpBuffersIo("Read", &Message);
|
|
|
|
if (Status == SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
DSCHAN_IO((" Missing %X bytes\n", Buffers[1].cbBuffer));
|
|
|
|
ULONG Read = StreamRead(Seq, m_Buffer + m_BufferUsed,
|
|
sizeof(m_Buffer) - m_BufferUsed);
|
|
if (Read == 0)
|
|
{
|
|
return Complete;
|
|
}
|
|
|
|
m_BufferUsed += Read;
|
|
}
|
|
else if (Status == SEC_I_RENEGOTIATE)
|
|
{
|
|
// The server wants to perform another handshake
|
|
// sequence.
|
|
|
|
if ((Status = AuthenticateServerConnection()) != S_OK)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while (Status == SEC_E_INCOMPLETE_MESSAGE);
|
|
|
|
if (Status != S_OK)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Buffers 0,1,2 should be header, data, trailer.
|
|
DBG_ASSERT(Buffers[1].BufferType == SECBUFFER_DATA);
|
|
|
|
DSCHAN_IO((" %X bytes of %X read\n",
|
|
Buffers[1].cbBuffer, Len));
|
|
|
|
memcpy((PUCHAR)Buffer + Complete,
|
|
Buffers[1].pvBuffer, Buffers[1].cbBuffer);
|
|
Complete += Buffers[1].cbBuffer;
|
|
|
|
// Check for extra data in buffer 3.
|
|
if (Buffers[3].BufferType == SECBUFFER_EXTRA)
|
|
{
|
|
DSCHAN_IO((" %X bytes extra\n"));
|
|
|
|
memmove(m_Buffer, Buffers[3].pvBuffer, Buffers[3].cbBuffer);
|
|
m_BufferUsed = Buffers[3].cbBuffer;
|
|
}
|
|
else
|
|
{
|
|
m_BufferUsed = 0;
|
|
}
|
|
}
|
|
|
|
DSCHAN_IO((" Read returns %X bytes\n", Complete));
|
|
return Complete;
|
|
}
|
|
|
|
ULONG
|
|
DbgRpcSecureChannelTransport::Write(ULONG Seq, PVOID Buffer, ULONG Len)
|
|
{
|
|
SecBufferDesc Message;
|
|
SecBuffer Buffers[3];
|
|
DWORD Status;
|
|
ULONG Complete;
|
|
|
|
DSCHAN_IO(("Start write(%X) with %X bytes cached\n",
|
|
Len, m_BufferUsed));
|
|
|
|
Message.ulVersion = SECBUFFER_VERSION;
|
|
Message.cBuffers = 3;
|
|
Message.pBuffers = Buffers;
|
|
|
|
Complete = 0;
|
|
|
|
while (Complete < Len)
|
|
{
|
|
ULONG Chunk;
|
|
|
|
//
|
|
// Set up header, data and trailer buffers so
|
|
// that EncryptMessage has room for everything
|
|
// in one contiguous buffer.
|
|
//
|
|
|
|
Buffers[0].pvBuffer = m_Buffer + m_BufferUsed;
|
|
Buffers[0].cbBuffer = m_Sizes.cbHeader;
|
|
Buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
|
|
|
|
//
|
|
// Data is encrypted in-place so copy data
|
|
// from the user's buffer into the working buffer.
|
|
// Part of the working buffer may be taken up
|
|
// by queued data so work with what's left.
|
|
//
|
|
|
|
if (Len > m_MaxChunk - m_BufferUsed)
|
|
{
|
|
Chunk = m_MaxChunk - m_BufferUsed;
|
|
}
|
|
else
|
|
{
|
|
Chunk = Len;
|
|
}
|
|
|
|
DSCHAN_IO((" write %X bytes of %X\n", Chunk, Len));
|
|
|
|
Buffers[1].pvBuffer =
|
|
(PUCHAR)Buffers[0].pvBuffer + Buffers[0].cbBuffer;
|
|
Buffers[1].cbBuffer = Chunk;
|
|
Buffers[1].BufferType = SECBUFFER_DATA;
|
|
memcpy(Buffers[1].pvBuffer, (PUCHAR)Buffer + Complete, Chunk);
|
|
|
|
Buffers[2].pvBuffer =
|
|
(PUCHAR)Buffers[1].pvBuffer + Buffers[1].cbBuffer;
|
|
Buffers[2].cbBuffer = m_Sizes.cbTrailer;
|
|
Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
|
|
|
|
Status = g_SecurityFunc.EncryptMessage(&m_Context, 0, &Message, Seq);
|
|
if (Status != S_OK)
|
|
{
|
|
break;
|
|
}
|
|
|
|
DumpBuffersIo("Write encrypt", &Message);
|
|
|
|
ULONG Total, Written;
|
|
|
|
Total = Buffers[0].cbBuffer + Buffers[1].cbBuffer +
|
|
Buffers[2].cbBuffer;
|
|
Written = StreamWrite(Seq, Buffers[0].pvBuffer, Total);
|
|
if (Written != Total)
|
|
{
|
|
break;
|
|
}
|
|
|
|
Complete += Chunk;
|
|
}
|
|
|
|
DSCHAN_IO((" Write returns %X bytes\n", Complete));
|
|
return Complete;
|
|
}
|
|
|
|
HRESULT
|
|
DbgRpcSecureChannelTransport::GetSizes(void)
|
|
{
|
|
HRESULT Status;
|
|
|
|
//
|
|
// Find out how big the header will be:
|
|
//
|
|
|
|
if ((Status = g_SecurityFunc.
|
|
QueryContextAttributes(&m_Context, SECPKG_ATTR_STREAM_SIZES,
|
|
&m_Sizes)) != S_OK)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
// Compute the largest chunk that can be encrypted at
|
|
// once in the transport's data buffer.
|
|
m_MaxChunk = sizeof(m_Buffer) - (m_Sizes.cbHeader + m_Sizes.cbTrailer);
|
|
if (m_MaxChunk > m_Sizes.cbMaximumMessage)
|
|
{
|
|
m_MaxChunk = m_Sizes.cbMaximumMessage;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT
|
|
DbgRpcSecureChannelTransport::AuthenticateClientConnection(void)
|
|
{
|
|
TimeStamp tsExpiry;
|
|
SECURITY_STATUS Status;
|
|
SecBufferDesc InBuffer;
|
|
SecBufferDesc OutBuffer;
|
|
SecBuffer InBuffers[2];
|
|
SecBuffer OutBuffers[1];
|
|
BOOL fInitContext = TRUE;
|
|
DWORD dwSSPIFlags, dwSSPIOutFlags;
|
|
ULONG Seq;
|
|
|
|
Status = SEC_E_SECPKG_NOT_FOUND; //default error if we run out of packages
|
|
|
|
dwSSPIFlags = ASC_REQ_SEQUENCE_DETECT |
|
|
ASC_REQ_REPLAY_DETECT |
|
|
ASC_REQ_CONFIDENTIALITY |
|
|
ASC_REQ_EXTENDED_ERROR |
|
|
ASC_REQ_ALLOCATE_MEMORY |
|
|
ASC_REQ_STREAM |
|
|
ASC_REQ_MUTUAL_AUTH;
|
|
|
|
//
|
|
// Set buffers for AcceptSecurityContext call
|
|
//
|
|
|
|
InBuffer.cBuffers = 2;
|
|
InBuffer.pBuffers = InBuffers;
|
|
InBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
Status = SEC_I_CONTINUE_NEEDED;
|
|
m_BufferUsed = 0;
|
|
|
|
while ( Status == SEC_I_CONTINUE_NEEDED ||
|
|
Status == SEC_E_INCOMPLETE_MESSAGE ||
|
|
Status == SEC_I_INCOMPLETE_CREDENTIALS)
|
|
{
|
|
if (0 == m_BufferUsed || Status == SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
ULONG Read = StreamRead(SEQ_INTERNAL, m_Buffer + m_BufferUsed,
|
|
sizeof(m_Buffer) - m_BufferUsed);
|
|
if (Read == 0)
|
|
{
|
|
Status = HRESULT_FROM_WIN32(ERROR_READ_FAULT);
|
|
goto Exit;
|
|
}
|
|
else
|
|
{
|
|
m_BufferUsed += Read;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// InBuffers[1] is for getting extra data that
|
|
// SSPI/SCHANNEL doesn't proccess on this
|
|
// run around the loop.
|
|
//
|
|
|
|
InBuffers[0].pvBuffer = m_Buffer;
|
|
InBuffers[0].cbBuffer = m_BufferUsed;
|
|
InBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
|
|
InBuffers[1].pvBuffer = NULL;
|
|
InBuffers[1].cbBuffer = 0;
|
|
InBuffers[1].BufferType = SECBUFFER_EMPTY;
|
|
|
|
|
|
//
|
|
// Initialize these so if we fail, pvBuffer contains NULL,
|
|
// so we don't try to free random garbage at the quit
|
|
//
|
|
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
OutBuffers[0].cbBuffer = 0;
|
|
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
|
|
Status = g_SecurityFunc.AcceptSecurityContext(
|
|
&m_Creds,
|
|
(fInitContext ? NULL : &m_Context),
|
|
&InBuffer,
|
|
dwSSPIFlags,
|
|
SECURITY_NATIVE_DREP,
|
|
(fInitContext ? &m_Context : NULL),
|
|
&OutBuffer,
|
|
&dwSSPIOutFlags,
|
|
&tsExpiry);
|
|
|
|
DSCHAN(("ASC on %X bytes returns %X\n",
|
|
m_BufferUsed, Status));
|
|
DumpBuffers("ASC in", &InBuffer);
|
|
DumpBuffers("ASC out", &OutBuffer);
|
|
|
|
if (SUCCEEDED(Status))
|
|
{
|
|
fInitContext = FALSE;
|
|
m_OwnContext = TRUE;
|
|
}
|
|
|
|
if ( Status == SEC_E_OK ||
|
|
Status == SEC_I_CONTINUE_NEEDED ||
|
|
(FAILED(Status) &&
|
|
(0 != (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))))
|
|
{
|
|
if (OutBuffers[0].cbBuffer != 0 &&
|
|
OutBuffers[0].pvBuffer != NULL )
|
|
{
|
|
ULONG Written;
|
|
|
|
DSCHAN((" write back %X bytes\n", OutBuffers[0].cbBuffer));
|
|
|
|
//
|
|
// Send response to server if there is one
|
|
//
|
|
Written = StreamWrite(SEQ_INTERNAL, OutBuffers[0].pvBuffer,
|
|
OutBuffers[0].cbBuffer);
|
|
|
|
g_SecurityFunc.FreeContextBuffer( OutBuffers[0].pvBuffer );
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
|
|
if (Written != OutBuffers[0].cbBuffer)
|
|
{
|
|
Status = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT);
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if ( Status == SEC_E_OK )
|
|
{
|
|
if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
|
|
{
|
|
DSCHAN_IO((" ASC returns with %X extra bytes\n",
|
|
InBuffers[1].cbBuffer));
|
|
|
|
memmove(m_Buffer,
|
|
m_Buffer + (m_BufferUsed - InBuffers[1].cbBuffer),
|
|
InBuffers[1].cbBuffer);
|
|
m_BufferUsed = InBuffers[1].cbBuffer;
|
|
}
|
|
else
|
|
{
|
|
m_BufferUsed = 0;
|
|
}
|
|
|
|
goto Exit;
|
|
}
|
|
else if (FAILED(Status) && (Status != SEC_E_INCOMPLETE_MESSAGE))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if ( Status != SEC_E_INCOMPLETE_MESSAGE &&
|
|
Status != SEC_I_INCOMPLETE_CREDENTIALS)
|
|
{
|
|
if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
|
|
{
|
|
DSCHAN_IO((" ASC loops with %X extra bytes\n",
|
|
InBuffers[1].cbBuffer));
|
|
|
|
memmove(m_Buffer,
|
|
m_Buffer + (m_BufferUsed - InBuffers[1].cbBuffer),
|
|
InBuffers[1].cbBuffer);
|
|
m_BufferUsed = InBuffers[1].cbBuffer;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// prepare for next receive
|
|
//
|
|
|
|
m_BufferUsed = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
DSCHAN(("AuthClient returns %X\n", Status));
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
DbgRpcSecureChannelTransport::InitiateServerConnection(LPSTR pszServerName)
|
|
{
|
|
SecBufferDesc OutBuffer;
|
|
SecBuffer OutBuffers[1];
|
|
DWORD dwSSPIFlags;
|
|
DWORD dwSSPIOutFlags;
|
|
TimeStamp tsExpiry;
|
|
SECURITY_STATUS Status;
|
|
DWORD cbData;
|
|
|
|
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
|
|
ISC_REQ_REPLAY_DETECT |
|
|
ISC_REQ_CONFIDENTIALITY |
|
|
ISC_REQ_EXTENDED_ERROR |
|
|
ISC_REQ_ALLOCATE_MEMORY |
|
|
ISC_REQ_STREAM |
|
|
ISC_REQ_MUTUAL_AUTH;
|
|
|
|
//
|
|
// Initiate a ClientHello message and generate a token.
|
|
//
|
|
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
OutBuffers[0].cbBuffer = 0;
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
Status = g_SecurityFunc.InitializeSecurityContextA(
|
|
&m_Creds,
|
|
NULL,
|
|
pszServerName,
|
|
dwSSPIFlags,
|
|
0,
|
|
SECURITY_NATIVE_DREP,
|
|
NULL,
|
|
0,
|
|
&m_Context,
|
|
&OutBuffer,
|
|
&dwSSPIOutFlags,
|
|
&tsExpiry);
|
|
|
|
DSCHAN(("First ISC returns %X\n", Status));
|
|
DumpBuffers("First ISC out", &OutBuffer);
|
|
|
|
if (Status != SEC_I_CONTINUE_NEEDED)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
m_OwnContext = TRUE;
|
|
|
|
// Send response to server if there is one.
|
|
if (OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL)
|
|
{
|
|
DSCHAN((" write back %X bytes\n", OutBuffers[0].cbBuffer));
|
|
|
|
cbData = StreamWrite(SEQ_INTERNAL, OutBuffers[0].pvBuffer,
|
|
OutBuffers[0].cbBuffer);
|
|
if(cbData == 0)
|
|
{
|
|
g_SecurityFunc.FreeContextBuffer(OutBuffers[0].pvBuffer);
|
|
if (m_OwnContext)
|
|
{
|
|
g_SecurityFunc.DeleteSecurityContext(&m_Context);
|
|
SecInvalidateHandle(&m_Context);
|
|
}
|
|
Status = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT);
|
|
goto Exit;
|
|
}
|
|
|
|
// Free output buffer.
|
|
g_SecurityFunc.FreeContextBuffer(OutBuffers[0].pvBuffer);
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
}
|
|
|
|
Status = S_OK;
|
|
|
|
Exit:
|
|
DSCHAN(("InitServer returns %X\n", Status));
|
|
return Status;
|
|
}
|
|
|
|
HRESULT
|
|
DbgRpcSecureChannelTransport::AuthenticateServerConnection(void)
|
|
{
|
|
SecBufferDesc InBuffer;
|
|
SecBuffer InBuffers[2];
|
|
SecBufferDesc OutBuffer;
|
|
SecBuffer OutBuffers[1];
|
|
DWORD dwSSPIFlags;
|
|
DWORD dwSSPIOutFlags;
|
|
TimeStamp tsExpiry;
|
|
SECURITY_STATUS Status;
|
|
DWORD cbData;
|
|
ULONG ReadNeeded;
|
|
|
|
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
|
|
ISC_REQ_REPLAY_DETECT |
|
|
ISC_REQ_CONFIDENTIALITY |
|
|
ISC_REQ_EXTENDED_ERROR |
|
|
ISC_REQ_ALLOCATE_MEMORY |
|
|
ISC_REQ_STREAM;
|
|
|
|
m_BufferUsed = 0;
|
|
ReadNeeded = 1;
|
|
|
|
|
|
//
|
|
// Loop until the handshake is finished or an error occurs.
|
|
//
|
|
|
|
Status = SEC_I_CONTINUE_NEEDED;
|
|
|
|
while(Status == SEC_I_CONTINUE_NEEDED ||
|
|
Status == SEC_E_INCOMPLETE_MESSAGE ||
|
|
Status == SEC_I_INCOMPLETE_CREDENTIALS)
|
|
{
|
|
|
|
//
|
|
// Read data from server.
|
|
//
|
|
|
|
if (0 == m_BufferUsed || Status == SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
if (ReadNeeded > 0)
|
|
{
|
|
cbData = StreamRead(SEQ_INTERNAL, m_Buffer + m_BufferUsed,
|
|
sizeof(m_Buffer) - m_BufferUsed);
|
|
if(cbData == 0)
|
|
{
|
|
Status = HRESULT_FROM_WIN32(ERROR_READ_FAULT);
|
|
break;
|
|
}
|
|
|
|
m_BufferUsed += cbData;
|
|
}
|
|
else
|
|
{
|
|
ReadNeeded = 1;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Set up the input buffers. Buffer 0 is used to pass in data
|
|
// received from the server. Schannel will consume some or all
|
|
// of this. Leftover data (if any) will be placed in buffer 1 and
|
|
// given a buffer type of SECBUFFER_EXTRA.
|
|
//
|
|
|
|
InBuffers[0].pvBuffer = m_Buffer;
|
|
InBuffers[0].cbBuffer = m_BufferUsed;
|
|
InBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
|
|
InBuffers[1].pvBuffer = NULL;
|
|
InBuffers[1].cbBuffer = 0;
|
|
InBuffers[1].BufferType = SECBUFFER_EMPTY;
|
|
|
|
InBuffer.cBuffers = 2;
|
|
InBuffer.pBuffers = InBuffers;
|
|
InBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
//
|
|
// Set up the output buffers. These are initialized to NULL
|
|
// so as to make it less likely we'll attempt to free random
|
|
// garbage later.
|
|
//
|
|
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
OutBuffers[0].BufferType= SECBUFFER_TOKEN;
|
|
OutBuffers[0].cbBuffer = 0;
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
//
|
|
// Call InitializeSecurityContext.
|
|
//
|
|
|
|
Status = g_SecurityFunc.InitializeSecurityContextA(
|
|
&m_Creds,
|
|
&m_Context,
|
|
NULL,
|
|
dwSSPIFlags,
|
|
0,
|
|
SECURITY_NATIVE_DREP,
|
|
&InBuffer,
|
|
0,
|
|
NULL,
|
|
&OutBuffer,
|
|
&dwSSPIOutFlags,
|
|
&tsExpiry);
|
|
|
|
DSCHAN(("ISC on %X bytes returns %X\n",
|
|
m_BufferUsed, Status));
|
|
DumpBuffers("ISC in", &InBuffer);
|
|
DumpBuffers("ISC out", &OutBuffer);
|
|
|
|
//
|
|
// If InitializeSecurityContext was successful (or if the error was
|
|
// one of the special extended ones), send the contends of the output
|
|
// buffer to the server.
|
|
//
|
|
|
|
if(Status == SEC_E_OK ||
|
|
Status == SEC_I_CONTINUE_NEEDED ||
|
|
FAILED(Status) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))
|
|
{
|
|
if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL)
|
|
{
|
|
DSCHAN((" write back %X bytes\n", OutBuffers[0].cbBuffer));
|
|
|
|
cbData = StreamWrite(SEQ_INTERNAL, OutBuffers[0].pvBuffer,
|
|
OutBuffers[0].cbBuffer);
|
|
if(cbData == 0)
|
|
{
|
|
g_SecurityFunc.FreeContextBuffer(OutBuffers[0].pvBuffer);
|
|
if (m_OwnContext)
|
|
{
|
|
g_SecurityFunc.DeleteSecurityContext(&m_Context);
|
|
SecInvalidateHandle(&m_Context);
|
|
}
|
|
Status = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT);
|
|
goto Exit;
|
|
}
|
|
|
|
// Free output buffer.
|
|
g_SecurityFunc.FreeContextBuffer(OutBuffers[0].pvBuffer);
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE,
|
|
// then we need to read more data from the server and try again.
|
|
//
|
|
|
|
if(Status == SEC_E_INCOMPLETE_MESSAGE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// If InitializeSecurityContext returned SEC_E_OK, then the
|
|
// handshake completed successfully.
|
|
//
|
|
|
|
if(Status == SEC_E_OK)
|
|
{
|
|
//
|
|
// If the "extra" buffer contains data, this is encrypted application
|
|
// protocol layer stuff. It needs to be saved. The application layer
|
|
// will later decrypt it with DecryptMessage.
|
|
//
|
|
|
|
if(InBuffers[1].BufferType == SECBUFFER_EXTRA)
|
|
{
|
|
DSCHAN_IO((" ISC returns with %X extra bytes\n",
|
|
InBuffers[1].cbBuffer));
|
|
|
|
memmove(m_Buffer,
|
|
m_Buffer + (m_BufferUsed - InBuffers[1].cbBuffer),
|
|
InBuffers[1].cbBuffer);
|
|
m_BufferUsed = InBuffers[1].cbBuffer;
|
|
}
|
|
else
|
|
{
|
|
m_BufferUsed = 0;
|
|
}
|
|
|
|
//
|
|
// Bail out to quit
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// Check for fatal error.
|
|
//
|
|
|
|
if(FAILED(Status))
|
|
{
|
|
break;
|
|
}
|
|
|
|
|
|
//
|
|
// If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
|
|
// then the server just requested client authentication.
|
|
//
|
|
|
|
if(Status == SEC_I_INCOMPLETE_CREDENTIALS)
|
|
{
|
|
DSCHAN(("Get new client credentials\n"));
|
|
|
|
//
|
|
// Display trusted issuers info.
|
|
//
|
|
|
|
GetNewClientCredentials();
|
|
|
|
//
|
|
// Now would be a good time perhaps to prompt the user to select
|
|
// a client certificate and obtain a new credential handle,
|
|
// but I don't have the energy nor inclination.
|
|
//
|
|
// As this is currently written, Schannel will send a "no
|
|
// certificate" alert to the server in place of a certificate.
|
|
// The server might be cool with this, or it might drop the
|
|
// connection.
|
|
//
|
|
|
|
// Go around again.
|
|
ReadNeeded = 0;
|
|
Status = SEC_I_CONTINUE_NEEDED;
|
|
continue;
|
|
}
|
|
|
|
|
|
//
|
|
// Copy any leftover data from the "extra" buffer, and go around
|
|
// again.
|
|
//
|
|
|
|
if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
|
|
{
|
|
DSCHAN((" ISC loops with %X extra bytes\n",
|
|
InBuffers[1].cbBuffer));
|
|
|
|
memmove(m_Buffer,
|
|
m_Buffer + (m_BufferUsed - InBuffers[1].cbBuffer),
|
|
InBuffers[1].cbBuffer);
|
|
m_BufferUsed = InBuffers[1].cbBuffer;
|
|
}
|
|
else
|
|
{
|
|
m_BufferUsed = 0;
|
|
}
|
|
}
|
|
|
|
// Delete the security context in the case of a fatal error.
|
|
if(FAILED(Status))
|
|
{
|
|
if (m_OwnContext)
|
|
{
|
|
g_SecurityFunc.DeleteSecurityContext(&m_Context);
|
|
SecInvalidateHandle(&m_Context);
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
DSCHAN(("AuthServer returns %X\n", Status));
|
|
return Status;
|
|
}
|
|
|
|
void
|
|
DbgRpcSecureChannelTransport::GetNewClientCredentials(void)
|
|
{
|
|
CredHandle hCreds;
|
|
SecPkgContext_IssuerListInfoEx IssuerListInfo;
|
|
PCCERT_CHAIN_CONTEXT pChainContext;
|
|
CERT_CHAIN_FIND_BY_ISSUER_PARA FindByIssuerPara;
|
|
PCCERT_CONTEXT pCertContext;
|
|
TimeStamp tsExpiry;
|
|
SECURITY_STATUS Status;
|
|
|
|
//
|
|
// Read list of trusted issuers from schannel.
|
|
//
|
|
|
|
Status = g_SecurityFunc.QueryContextAttributes(&m_Context,
|
|
SECPKG_ATTR_ISSUER_LIST_EX,
|
|
(PVOID)&IssuerListInfo);
|
|
if (Status != SEC_E_OK)
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Enumerate the client certificates.
|
|
//
|
|
|
|
ZeroMemory(&FindByIssuerPara, sizeof(FindByIssuerPara));
|
|
|
|
FindByIssuerPara.cbSize = sizeof(FindByIssuerPara);
|
|
FindByIssuerPara.pszUsageIdentifier = szOID_PKIX_KP_CLIENT_AUTH;
|
|
FindByIssuerPara.dwKeySpec = 0;
|
|
FindByIssuerPara.cIssuer = IssuerListInfo.cIssuers;
|
|
FindByIssuerPara.rgIssuer = IssuerListInfo.aIssuers;
|
|
|
|
pChainContext = NULL;
|
|
|
|
while(TRUE)
|
|
{
|
|
// Find a certificate chain.
|
|
pChainContext = g_Crypt32Calls.
|
|
CertFindChainInStore(g_hMyCertStore,
|
|
X509_ASN_ENCODING,
|
|
0,
|
|
CERT_CHAIN_FIND_BY_ISSUER,
|
|
&FindByIssuerPara,
|
|
pChainContext);
|
|
if(pChainContext == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Get pointer to leaf certificate context.
|
|
pCertContext = pChainContext->rgpChain[0]->rgpElement[0]->pCertContext;
|
|
|
|
// Create schannel credential.
|
|
m_ScCreds.cCreds = 1;
|
|
m_ScCreds.paCred = &pCertContext;
|
|
|
|
Status = g_SecurityFunc.AcquireCredentialsHandleA(
|
|
NULL, // Name of principal
|
|
UNISP_NAME_A, // Name of package
|
|
SECPKG_CRED_OUTBOUND, // Flags indicating use
|
|
NULL, // Pointer to logon ID
|
|
&m_ScCreds, // Package specific data
|
|
NULL, // Pointer to GetKey() func
|
|
NULL, // Value to pass to GetKey()
|
|
&hCreds, // (out) Cred Handle
|
|
&tsExpiry); // (out) Lifetime (optional)
|
|
if(Status != SEC_E_OK)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Destroy the old credentials.
|
|
if (m_OwnCreds)
|
|
{
|
|
g_SecurityFunc.FreeCredentialsHandle(&m_Creds);
|
|
}
|
|
|
|
// XXX drewb - This doesn't really work if this
|
|
// isn't the credential owner.
|
|
m_Creds = hCreds;
|
|
break;
|
|
}
|
|
|
|
Exit:
|
|
DSCHAN(("GetNewClientCredentials returns %X\n", Status));
|
|
}
|
|
|
|
void
|
|
DbgRpcSecureChannelTransport::DisconnectFromClient(void)
|
|
{
|
|
DWORD dwType;
|
|
PBYTE pbMessage;
|
|
DWORD cbMessage;
|
|
DWORD cbData;
|
|
|
|
SecBufferDesc OutBuffer;
|
|
SecBuffer OutBuffers[1];
|
|
DWORD dwSSPIFlags;
|
|
DWORD dwSSPIOutFlags;
|
|
TimeStamp tsExpiry;
|
|
DWORD Status;
|
|
|
|
//
|
|
// Notify schannel that we are about to close the connection.
|
|
//
|
|
|
|
dwType = SCHANNEL_SHUTDOWN;
|
|
|
|
OutBuffers[0].pvBuffer = &dwType;
|
|
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
OutBuffers[0].cbBuffer = sizeof(dwType);
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
Status = g_SecurityFunc.ApplyControlToken(&m_Context, &OutBuffer);
|
|
if(FAILED(Status))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Build an SSL close notify message.
|
|
//
|
|
|
|
dwSSPIFlags = ASC_REQ_SEQUENCE_DETECT |
|
|
ASC_REQ_REPLAY_DETECT |
|
|
ASC_REQ_CONFIDENTIALITY |
|
|
ASC_REQ_EXTENDED_ERROR |
|
|
ASC_REQ_ALLOCATE_MEMORY |
|
|
ASC_REQ_STREAM;
|
|
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
OutBuffers[0].cbBuffer = 0;
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
Status = g_SecurityFunc.AcceptSecurityContext(
|
|
&m_Creds,
|
|
&m_Context,
|
|
NULL,
|
|
dwSSPIFlags,
|
|
SECURITY_NATIVE_DREP,
|
|
NULL,
|
|
&OutBuffer,
|
|
&dwSSPIOutFlags,
|
|
&tsExpiry);
|
|
|
|
DSCHAN(("DisASC returns %X\n", Status));
|
|
DumpBuffers("DisASC out", &OutBuffer);
|
|
|
|
if(FAILED(Status))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
pbMessage = (PBYTE)OutBuffers[0].pvBuffer;
|
|
cbMessage = OutBuffers[0].cbBuffer;
|
|
|
|
//
|
|
// Send the close notify message to the client.
|
|
//
|
|
|
|
if (pbMessage != NULL && cbMessage != 0)
|
|
{
|
|
DSCHAN((" write back %X bytes\n", cbMessage));
|
|
|
|
cbData = StreamWrite(SEQ_INTERNAL, pbMessage, cbMessage);
|
|
if (cbData == 0)
|
|
{
|
|
Status = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT);
|
|
goto cleanup;
|
|
}
|
|
|
|
// Free output buffer.
|
|
g_SecurityFunc.FreeContextBuffer(pbMessage);
|
|
}
|
|
|
|
cleanup:
|
|
DSCHAN(("DisconnectFromClient returns %X\n", Status));
|
|
}
|
|
|
|
void
|
|
DbgRpcSecureChannelTransport::DisconnectFromServer(void)
|
|
{
|
|
DWORD dwType;
|
|
PBYTE pbMessage;
|
|
DWORD cbMessage;
|
|
DWORD cbData;
|
|
|
|
SecBufferDesc OutBuffer;
|
|
SecBuffer OutBuffers[1];
|
|
DWORD dwSSPIFlags;
|
|
DWORD dwSSPIOutFlags;
|
|
TimeStamp tsExpiry;
|
|
DWORD Status;
|
|
|
|
//
|
|
// Notify schannel that we are about to close the connection.
|
|
//
|
|
|
|
dwType = SCHANNEL_SHUTDOWN;
|
|
|
|
OutBuffers[0].pvBuffer = &dwType;
|
|
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
OutBuffers[0].cbBuffer = sizeof(dwType);
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
Status = g_SecurityFunc.ApplyControlToken(&m_Context, &OutBuffer);
|
|
if(FAILED(Status))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Build an SSL close notify message.
|
|
//
|
|
|
|
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
|
|
ISC_REQ_REPLAY_DETECT |
|
|
ISC_REQ_CONFIDENTIALITY |
|
|
ISC_REQ_EXTENDED_ERROR |
|
|
ISC_REQ_ALLOCATE_MEMORY |
|
|
ISC_REQ_STREAM;
|
|
|
|
OutBuffers[0].pvBuffer = NULL;
|
|
OutBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
OutBuffers[0].cbBuffer = 0;
|
|
|
|
OutBuffer.cBuffers = 1;
|
|
OutBuffer.pBuffers = OutBuffers;
|
|
OutBuffer.ulVersion = SECBUFFER_VERSION;
|
|
|
|
Status = g_SecurityFunc.InitializeSecurityContextA(
|
|
&m_Creds,
|
|
&m_Context,
|
|
NULL,
|
|
dwSSPIFlags,
|
|
0,
|
|
SECURITY_NATIVE_DREP,
|
|
NULL,
|
|
0,
|
|
&m_Context,
|
|
&OutBuffer,
|
|
&dwSSPIOutFlags,
|
|
&tsExpiry);
|
|
|
|
DSCHAN(("DisISC returns %X\n", Status));
|
|
DumpBuffers("DisISC out", &OutBuffer);
|
|
|
|
if(FAILED(Status))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
pbMessage = (PBYTE)OutBuffers[0].pvBuffer;
|
|
cbMessage = OutBuffers[0].cbBuffer;
|
|
|
|
|
|
//
|
|
// Send the close notify message to the server.
|
|
//
|
|
|
|
if(pbMessage != NULL && cbMessage != 0)
|
|
{
|
|
DSCHAN((" write back %X bytes\n", cbMessage));
|
|
|
|
cbData = StreamWrite(SEQ_INTERNAL, pbMessage, cbMessage);
|
|
if (cbData == 0)
|
|
{
|
|
Status = HRESULT_FROM_WIN32(ERROR_WRITE_FAULT);
|
|
goto cleanup;
|
|
}
|
|
|
|
// Free output buffer.
|
|
g_SecurityFunc.FreeContextBuffer(pbMessage);
|
|
}
|
|
|
|
cleanup:
|
|
DSCHAN(("DisconnectFromServer returns %X\n", Status));
|
|
}
|