Berin, I'm attaching Win32 SSL URI resolver class and test code. I haven't still finished testing.
I have some doubts. I have noticed that XSECURIResolver interface doesn't have send() method and resolveURI() returns BinInputStream. Without send() I'm not sure how we can use it in SOAP? Best regards, Milan > -----Original Message----- > From: Milan Tomic [mailto:[EMAIL PROTECTED] > Sent: Saturday, January 22, 2005 10:27 AM > To: security-dev@xml.apache.org > Subject: RE: XSECSOAPRequestorSimple::doRequest() > > > > OK, I will implement XSECURIResolver for the Windows SSL stream. > > Greetings, > Milan > > > > -----Original Message----- > > From: Berin Lautenbach [mailto:[EMAIL PROTECTED] > > Sent: Saturday, January 22, 2005 9:58 AM > > To: Milan Tomic > > Subject: Re: XSECSOAPRequestorSimple::doRequest() > > > > > > That sounds reasonable. But some random thoughts :>. > > > > The Java library has a register of URI resolvers. I wonder > > if we should > > do the same thing that are global to the library. Thatway, > the SOAP > > class could simply call the resolver for the particular URI and get > > returned a class that would do the connection etc. (Currently the > > caller needs to explicitly set the correct resolver for signature > > checking which is a real PITA) > > > > So rather than modify XSECBinHTTPURIInputStream, you could simply > > re-implement XSECURIResolver for the Windows SSL stream. We > > can fix the > > SOAP resolver so you can just pass a resolver to it and that way it > > doesn't have to have anything about understanding certs - the > > only class > > that needs to is the resolver. > > > > When we get fancy, we can great a reolver library class that > > holds all > > instantiated resolvers, and the SOAP handler simply makes a > > call to the > > library that hands back the correct resolver for the URI. > > > > Does that sound reasonable? > > > > We should probably start putting this on security-dev so others can > > comment as well! > > > > Cheers, > > Berin > > > > Milan Tomic wrote: > > > > > Hi, > > > > > > After taking a look into XSEC code > > > (xsec/utils/winutils/XSECBinHTTPURIInputStream) I have > > realized that > > > WinSock API was used instead of WinINET API. I have found some SSL > > > examples in MS Platform SDK, and I'm working on > implementing it. My > > > aproach is that XSECBinHTTPURIInputStream class should have > > > setClientCertificate(PCCERT_CONTEXT) method and this class > > should use > > > it for SSL in case URL starts with "https". If it doesn't start it > > > should act as it already is for "http" addresses. > > > XSECSOAPRequestorSimpleWin32 class should also have > > > setClientCertificate() method to be able to transfer > certificate to > > > the XSECBinHTTPURIInputStream class. XSECSOAPRequestorSimpleWin32 > > > class user in ctor specify URL and s/he knows if it starts with > > > "https" and should somehow find proper cert (e.g. ask user > > to select > > > it). I couldn't think of anything better... > > > > > > Best regards, > > > Milan >
/* * Copyright 2002-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * imitations under the License. */ /* * XSEC * * XSECURIResolverSSLWin32 := Virtual Interface class that takes a URI and * creates a binary input stream from it. * * Author(s): Milan Tomic * */ #ifndef XSECURIRESOLVERSSLWIN32_INCLUDE #define XSECURIRESOLVERSSLWIN32_INCLUDE #include <xsec/framework/XSECURIResolver.hpp> #include <xsec/utils/XSECSafeBuffer.hpp> #include <xercesc/util/XMLUri.hpp> #define SECURITY_WIN32 #include <security.h> #include <schannel.h> #include <winsock.h> /** * @ingroup pubsig */ /[EMAIL PROTECTED]/ /** * @brief URIResolver implementation class based on WinSock functions. * * This class provides Windows API implementation of the XSECURIResolver * class. * */ class DSIG_EXPORT XSECURIResolverSSLWin32 : public XSECURIResolver { public: /** * \brief Protocol type */ enum ProtocolType { PROTO_PCT1 = 1, PROTO_SSL2 = 2, PROTO_SSL3 = 3, PROTO_TLS1 = 4 }; /** * \brief Key exchange algorithm type */ enum KeyExchType { KEY_EXCH_RSA = 1, KEY_EXCH_DH = 2, }; /** @name Constructors and Destructors */ //@{ /** * \brief Constructor * * @param baseURI Provide a URI that relative URIs can be * matched to. */ XSECURIResolverSSLWin32(); virtual ~XSECURIResolverSSLWin32(); //@} /** @name Interface Methods */ //@{ /** * \brief Create a BYTE_STREAM from a URI. * * The resolver is required to take the input URI and * dereference it to an actual stream of octets. * * @param uri The string containing the URI to be de-referenced. * @returns The octet stream corresponding to the URI. */ virtual XERCES_CPP_NAMESPACE_QUALIFIER BinInputStream * resolveURI(const XMLCh * uri); /** * \brief Clone the resolver to be installed in a new object. * * When URIResolvers are passed into signatures and other * objects, they are cloned and control of the original object * is left with the caller. * */ virtual XSECURIResolver * clone(void); //@} /** @name XSECURIResolverSSLWin32 Specific Methods */ //@{ /** * \brief Assignes client SSL cert. Note that we DON'T own it. * * @param pCertContext Certificate to be used for client * authentication */ void setClientCertificate(PCCERT_CONTEXT pCertContext); /** * \brief Assignes security protocol. * * @param protocol Protocol to be used */ void setProtocol(ProtocolType protocol); /** * \brief Assignes key exchange algorithm type * * @param aiKeyExch key exchange algorithm to be used */ void setKeyExchAlg(KeyExchType aiKeyExch); //@} private: //Loads proper Windows SSL dll bool loadSecurityLibrary(void); //Unloads Windows SSL dll void unloadSecurityLibrary(void); //Create credentials using certificate provided SECURITY_STATUS createCredentials(PCredHandle phCreds); //Connects us to the remote server int connectToServer(LPSTR pszServerName, // in int iPortNumber, // in SOCKET * pSocket); // out //Disconnects us from remote server long disconnectFromServer( SOCKET Socket, PCredHandle phCreds, CtxtHandle * phContext); //Performs SSL handshake SECURITY_STATUS performClientHandshake( SOCKET Socket, // in PCredHandle phCreds, // in LPSTR pszServerName, // in CtxtHandle * phContext, // out SecBuffer * pExtraData); // out //Perform handshake loop SECURITY_STATUS clientHandshakeLoop( SOCKET Socket, // in PCredHandle phCreds, // in CtxtHandle * phContext, // in, out BOOL fDoInitialRead, // in SecBuffer * pExtraData); // out //Download data SECURITY_STATUS httpsGetData( SOCKET Socket, // in PCredHandle phCreds, // in CtxtHandle * phContext, // in LPSTR hostName, // in LPSTR path, // in LPSTR query, // in LPSTR fragment, // in unsigned short portNumber, // in safeBuffer &sb); // in //Verify server certificate using CAPI DWORD verifyServerCertificate( PCCERT_CONTEXT pServerCert, PSTR pszServerName, DWORD dwCertFlags); //Get new client credentials void getNewClientCredentials(CredHandle *phCreds, CtxtHandle *phContext); SCHANNEL_CRED m_SchannelCred; PSecurityFunctionTable m_pSSPI; HMODULE m_hSecurity; //SSL dll handle PCCERT_CONTEXT m_pCertContext; //Client certificate DWORD m_dwProtocol; ALG_ID m_aiKeyExch; }; #endif /* XSECURIRESOLVERSSLWIN32_INCLUDE */
/* * Copyright 2002-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * imitations under the License. */ /* * XSEC * * XSECURIResolverSSLWin32 := Virtual Interface class that takes a URI and * creates a binary input stream from it. * * Author(s): Milan Tomic * */ #include <xsec/framework/XSECDefs.hpp> #include <xsec/framework/win32/XSECURIResolverSSLWin32.hpp> #include <xsec/framework/XSECError.hpp> #include <xercesc/framework/MemBufInputSource.hpp> #include <xercesc/util/XMLString.hpp> #include <xercesc/util/Janitor.hpp> XERCES_CPP_NAMESPACE_USE #include <wincrypt.h> #include <wintrust.h> #include <sspi.h> #define IO_BUFFER_SIZE 0x10000 #define DLL_NAME TEXT("Secur32.dll") #define NT4_DLL_NAME TEXT("Security.dll") // -------------------------------------------------------------------------------- // Constructors and Destructors // -------------------------------------------------------------------------------- XSECURIResolverSSLWin32::XSECURIResolverSSLWin32() : m_aiKeyExch(CALG_RSA_KEYX), m_dwProtocol(SP_PROT_SSL2) { }; XSECURIResolverSSLWin32::~XSECURIResolverSSLWin32() { } // -------------------------------------------------------------------------------- // Interface Methods // -------------------------------------------------------------------------------- XERCES_CPP_NAMESPACE_QUALIFIER BinInputStream * XSECURIResolverSSLWin32::resolveURI(const XMLCh * uri) { XSEC_USING_XERCES(MemBufInputSource); XSEC_USING_XERCES(BinInputStream); const XMLUri urlSource(uri); MemBufInputSource * mbs = NULL; // Use Xerces MemBuf Input source BinInputStream * is = NULL; // To handle the actual input safeBuffer sb; // // Pull all of the parts of the URL out of th urlSource object, and transcode them // and transcode them back to ASCII. // const XMLCh* hostName = urlSource.getHost(); char* hostNameAsCharStar = XMLString::transcode(hostName); ArrayJanitor<char> janBuf1(hostNameAsCharStar); const XMLCh* path = urlSource.getPath(); char* pathAsCharStar = XMLString::transcode(path); ArrayJanitor<char> janBuf2(pathAsCharStar); const XMLCh* fragment = urlSource.getFragment(); char* fragmentAsCharStar = 0; if (fragment) fragmentAsCharStar = XMLString::transcode(fragment); ArrayJanitor<char> janBuf3(fragmentAsCharStar); const XMLCh* query = urlSource.getQueryString(); char* queryAsCharStar = 0; if (query) queryAsCharStar = XMLString::transcode(query); ArrayJanitor<char> janBuf4(queryAsCharStar); unsigned short portNumber = (unsigned short) urlSource.getPort(); // // SSL code starts here // WSADATA WsaData; SOCKET Socket = INVALID_SOCKET; CredHandle hClientCreds; CtxtHandle hContext; BOOL fCredsInitialized = FALSE; BOOL fContextInitialized = FALSE; SecBuffer ExtraData; SECURITY_STATUS Status; PCCERT_CONTEXT pRemoteCertContext = NULL; if(!loadSecurityLibrary()) { goto cleanup; } // // Initialize the WinSock subsystem. // if(WSAStartup(0x0101, &WsaData) == SOCKET_ERROR) { goto cleanup; } // // Create credentials. // if(createCredentials(&hClientCreds)) { goto cleanup; } fCredsInitialized = TRUE; // // Connect to server. // if(connectToServer(hostNameAsCharStar, portNumber, &Socket)) { goto cleanup; } // // Perform handshake // if(performClientHandshake(Socket, &hClientCreds, hostNameAsCharStar, &hContext, &ExtraData)) { goto cleanup; } fContextInitialized = TRUE; // // Authenticate server's credentials. // // Get server's certificate. Status = m_pSSPI->QueryContextAttributes(&hContext, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext); if(Status != SEC_E_OK) { goto cleanup; } // Attempt to validate server certificate. Status = verifyServerCertificate(pRemoteCertContext, hostNameAsCharStar, 0); if(Status) { // The server certificate did not validate correctly. At this // point, we cannot tell if we are connecting to the correct // server, or if we are connecting to a "man in the middle" // attack server. // It is therefore best if we abort the connection. goto cleanup; } // Free the server certificate context. CertFreeCertificateContext(pRemoteCertContext); pRemoteCertContext = NULL; // // Read file from server. // if(httpsGetData(Socket, &hClientCreds, &hContext, hostNameAsCharStar, pathAsCharStar, queryAsCharStar, fragmentAsCharStar, portNumber, sb)) { goto cleanup; } // // Send a close_notify alert to the server and // close down the connection. // if(disconnectFromServer(Socket, &hClientCreds, &hContext)) { goto cleanup; } fContextInitialized = FALSE; Socket = INVALID_SOCKET; // // SSL code ends here // mbs = new MemBufInputSource((const unsigned char*)sb.rawCharBuffer(), sb.sbRawBufferSize(), "XSECURIResolverSSLWin32"); // makeStream can (and is quite likely to) throw an exception //Janitor<MemBufInputSource> j_mbs(mbs); try { is = mbs->makeStream(); } catch (...) { delete mbs; } cleanup: // Free the server certificate context. if(pRemoteCertContext) { CertFreeCertificateContext(pRemoteCertContext); pRemoteCertContext = NULL; } // Free SSPI context handle. if(fContextInitialized) { m_pSSPI->DeleteSecurityContext(&hContext); fContextInitialized = FALSE; } // Free SSPI credentials handle. if(fCredsInitialized) { m_pSSPI->FreeCredentialsHandle(&hClientCreds); fCredsInitialized = FALSE; } // Close socket. if(Socket != INVALID_SOCKET) { closesocket(Socket); } // Shutdown WinSock subsystem. WSACleanup(); unloadSecurityLibrary(); if (is != NULL) { return is; } else { throw XSECException(XSECException::ErrorOpeningURI, "An error occurred in XSECURIResolverSSLWin32"); } } XSECURIResolver * XSECURIResolverSSLWin32::clone(void) { XSECURIResolverSSLWin32 * ret = new XSECURIResolverSSLWin32(); return ret; } // -------------------------------------------------------------------------------- // Specific Methods // -------------------------------------------------------------------------------- bool XSECURIResolverSSLWin32::loadSecurityLibrary(void) { INIT_SECURITY_INTERFACE pInitSecurityInterface; OSVERSIONINFO VerInfo; // // Find out which security DLL to use, depending on // whether we are on Win2K, NT or Win9x // VerInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); if (!GetVersionEx (&VerInfo)) { return FALSE; } if (VerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT && VerInfo.dwMajorVersion == 4) { m_hSecurity = LoadLibrary(NT4_DLL_NAME); } else if (VerInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS || VerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT ) { m_hSecurity = LoadLibrary(DLL_NAME); } else { return FALSE; } if(m_hSecurity == NULL) { return FALSE; } pInitSecurityInterface = (INIT_SECURITY_INTERFACE)GetProcAddress( m_hSecurity, "InitSecurityInterfaceA"); if(pInitSecurityInterface == NULL) { return FALSE; } m_pSSPI = pInitSecurityInterface(); if(m_pSSPI == NULL) { return FALSE; } return TRUE; } void XSECURIResolverSSLWin32::unloadSecurityLibrary(void) { FreeLibrary(m_hSecurity); m_hSecurity = NULL; } SECURITY_STATUS XSECURIResolverSSLWin32::createCredentials(PCredHandle phCreds) { TimeStamp tsExpiry; SECURITY_STATUS Status; DWORD cSupportedAlgs = 0; ALG_ID rgbSupportedAlgs[16]; // // 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(&m_SchannelCred, sizeof(m_SchannelCred)); m_SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; m_SchannelCred.cCreds = 1; m_SchannelCred.paCred = &m_pCertContext; m_SchannelCred.grbitEnabledProtocols = m_dwProtocol; if(m_aiKeyExch) { rgbSupportedAlgs[cSupportedAlgs++] = m_aiKeyExch; } if(cSupportedAlgs) { m_SchannelCred.cSupportedAlgs = cSupportedAlgs; m_SchannelCred.palgSupportedAlgs = rgbSupportedAlgs; } m_SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; // The SCH_CRED_MANUAL_CRED_VALIDATION flag is specified because // this sample verifies the server certificate manually. // Applications that expect to run on WinNT, Win9x, or WinME // should specify this flag and also manually verify the server // certificate. Applications running on newer versions of Windows can // leave off this flag, in which case the InitializeSecurityContext // function will validate the server certificate automatically. m_SchannelCred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; // // Create an SSPI credential. // Status = m_pSSPI->AcquireCredentialsHandleA( NULL, // Name of principal UNISP_NAME_A, // Name of package SECPKG_CRED_OUTBOUND, // Flags indicating use NULL, // Pointer to logon ID &m_SchannelCred, // Package specific data NULL, // Pointer to GetKey() func NULL, // Value to pass to GetKey() phCreds, // (out) Cred Handle &tsExpiry); // (out) Lifetime (optional) if(Status != SEC_E_OK) { //error } return Status; } int XSECURIResolverSSLWin32::connectToServer( LPSTR pszServerName, // in int iPortNumber, // in SOCKET * pSocket) // out { SOCKET Socket; struct sockaddr_in sin; struct hostent *hp; Socket = socket(PF_INET, SOCK_STREAM, 0); if(Socket == INVALID_SOCKET) { return WSAGetLastError(); } sin.sin_family = AF_INET; sin.sin_port = htons((u_short)iPortNumber); if((hp = gethostbyname(pszServerName)) == NULL) { return WSAGetLastError(); } else { memcpy(&sin.sin_addr, hp->h_addr, 4); } if(connect(Socket, (struct sockaddr *)&sin, sizeof(sin)) == SOCKET_ERROR) { closesocket(Socket); return WSAGetLastError(); } *pSocket = Socket; return SEC_E_OK; } long XSECURIResolverSSLWin32::disconnectFromServer( SOCKET Socket, PCredHandle phCreds, CtxtHandle * phContext) { DWORD dwType; const char * 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 = m_pSSPI->ApplyControlToken(phContext, &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_RET_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 = m_pSSPI->InitializeSecurityContextA( phCreds, phContext, NULL, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, NULL, 0, phContext, &OutBuffer, &dwSSPIOutFlags, &tsExpiry); if(FAILED(Status)) { goto cleanup; } pbMessage = (const char *)OutBuffers[0].pvBuffer; cbMessage = OutBuffers[0].cbBuffer; // // Send the close notify message to the server. // if(pbMessage != NULL && cbMessage != 0) { cbData = send(Socket, pbMessage, cbMessage, 0); if(cbData == SOCKET_ERROR || cbData == 0) { Status = WSAGetLastError(); goto cleanup; } // Free output buffer. m_pSSPI->FreeContextBuffer((void*)pbMessage); } cleanup: // Free the security context. m_pSSPI->DeleteSecurityContext(phContext); // Close the socket. closesocket(Socket); return Status; } SECURITY_STATUS XSECURIResolverSSLWin32::performClientHandshake( SOCKET Socket, // in PCredHandle phCreds, // in LPSTR pszServerName, // in CtxtHandle * phContext, // out SecBuffer * pExtraData) // out { SecBufferDesc OutBuffer; SecBuffer OutBuffers[1]; DWORD dwSSPIFlags; DWORD dwSSPIOutFlags; TimeStamp tsExpiry; SECURITY_STATUS scRet; DWORD cbData; dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; // // 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; scRet = m_pSSPI->InitializeSecurityContextA( phCreds, NULL, pszServerName, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, NULL, 0, phContext, &OutBuffer, &dwSSPIOutFlags, &tsExpiry); if(scRet != SEC_I_CONTINUE_NEEDED) { return scRet; } // Send response to server if there is one. if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL) { cbData = send(Socket, (const char *)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0); if(cbData == SOCKET_ERROR || cbData == 0) { m_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); m_pSSPI->DeleteSecurityContext(phContext); return SEC_E_INTERNAL_ERROR; } // Free output buffer. m_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); OutBuffers[0].pvBuffer = NULL; } return clientHandshakeLoop(Socket, phCreds, phContext, TRUE, pExtraData); } SECURITY_STATUS XSECURIResolverSSLWin32::clientHandshakeLoop( SOCKET Socket, // in PCredHandle phCreds, // in CtxtHandle * phContext, // in, out BOOL fDoInitialRead, // in SecBuffer * pExtraData) // out { SecBufferDesc InBuffer; SecBuffer InBuffers[2]; SecBufferDesc OutBuffer; SecBuffer OutBuffers[1]; DWORD dwSSPIFlags; DWORD dwSSPIOutFlags; TimeStamp tsExpiry; SECURITY_STATUS scRet; DWORD cbData; char * IoBuffer; DWORD cbIoBuffer; BOOL fDoRead; dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; // // Allocate data buffer. // IoBuffer = (char *)LocalAlloc(LMEM_FIXED, IO_BUFFER_SIZE); if(IoBuffer == NULL) { return SEC_E_INTERNAL_ERROR; } cbIoBuffer = 0; fDoRead = fDoInitialRead; // // Loop until the handshake is finished or an error occurs. // scRet = SEC_I_CONTINUE_NEEDED; while(scRet == SEC_I_CONTINUE_NEEDED || scRet == SEC_E_INCOMPLETE_MESSAGE || scRet == SEC_I_INCOMPLETE_CREDENTIALS) { // // Read data from server. // if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) { if(fDoRead) { cbData = recv(Socket, IoBuffer + cbIoBuffer, IO_BUFFER_SIZE - cbIoBuffer, 0); if(cbData == SOCKET_ERROR) { scRet = SEC_E_INTERNAL_ERROR; break; } else if(cbData == 0) { scRet = SEC_E_INTERNAL_ERROR; break; } cbIoBuffer += cbData; } else { fDoRead = TRUE; } } // // 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 = IoBuffer; InBuffers[0].cbBuffer = cbIoBuffer; 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. // scRet = m_pSSPI->InitializeSecurityContextA(phCreds, phContext, NULL, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, &InBuffer, 0, NULL, &OutBuffer, &dwSSPIOutFlags, &tsExpiry); // // 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(scRet == SEC_E_OK || scRet == SEC_I_CONTINUE_NEEDED || FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR)) { if(OutBuffers[0].cbBuffer != 0 && OutBuffers[0].pvBuffer != NULL) { cbData = send(Socket, (const char *)OutBuffers[0].pvBuffer, OutBuffers[0].cbBuffer, 0); if(cbData == SOCKET_ERROR || cbData == 0) { m_pSSPI->FreeContextBuffer(OutBuffers[0].pvBuffer); m_pSSPI->DeleteSecurityContext(phContext); return SEC_E_INTERNAL_ERROR; } // Free output buffer. m_pSSPI->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(scRet == SEC_E_INCOMPLETE_MESSAGE) { continue; } // // If InitializeSecurityContext returned SEC_E_OK, then the // handshake completed successfully. // if(scRet == 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) { pExtraData->pvBuffer = LocalAlloc(LMEM_FIXED, InBuffers[1].cbBuffer); if(pExtraData->pvBuffer == NULL) { return SEC_E_INTERNAL_ERROR; } MoveMemory(pExtraData->pvBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer); pExtraData->cbBuffer = InBuffers[1].cbBuffer; pExtraData->BufferType = SECBUFFER_TOKEN; } else { pExtraData->pvBuffer = NULL; pExtraData->cbBuffer = 0; pExtraData->BufferType = SECBUFFER_EMPTY; } // // Bail out to quit // break; } // // Check for fatal error. // if(FAILED(scRet)) { break; } // // If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS, // then the server just requested client authentication. // if(scRet == SEC_I_INCOMPLETE_CREDENTIALS) { // // Busted. The server has requested client authentication and // the credential we supplied didn't contain a client certificate. // // // This function will read the list of trusted certificate // authorities ("issuers") that was received from the server // and attempt to find a suitable client certificate that // was issued by one of these. If this function is successful, // then we will connect using the new certificate. Otherwise, // we will attempt to connect anonymously (using our current // credentials). // getNewClientCredentials(phCreds, phContext); // Go around again. fDoRead = FALSE; scRet = SEC_I_CONTINUE_NEEDED; continue; } // // Copy any leftover data from the "extra" buffer, and go around // again. // if ( InBuffers[1].BufferType == SECBUFFER_EXTRA ) { MoveMemory(IoBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer); cbIoBuffer = InBuffers[1].cbBuffer; } else { cbIoBuffer = 0; } } // Delete the security context in the case of a fatal error. if(FAILED(scRet)) { m_pSSPI->DeleteSecurityContext(phContext); } LocalFree(IoBuffer); return scRet; } SECURITY_STATUS XSECURIResolverSSLWin32::httpsGetData( SOCKET Socket, // in PCredHandle phCreds, // in CtxtHandle * phContext, // in LPSTR hostName, // in LPSTR path, // in LPSTR query, // in LPSTR fragment, // in unsigned short portNumber, // in safeBuffer &sb) // in { SecPkgContext_StreamSizes Sizes; SECURITY_STATUS scRet; SecBufferDesc Message; SecBuffer Buffers[4]; SecBuffer * pDataBuffer; SecBuffer * pExtraBuffer; SecBuffer ExtraBuffer; char *pbIoBuffer; DWORD cbIoBuffer; DWORD cbIoBufferLength; char *pbMessage; DWORD cbMessage; DWORD cbData; int i; // // Read stream encryption properties. // scRet = m_pSSPI->QueryContextAttributes(phContext, SECPKG_ATTR_STREAM_SIZES, &Sizes); if(scRet != SEC_E_OK) { return scRet; } // // Allocate a working buffer. The plaintext sent to EncryptMessage // should never be more than 'Sizes.cbMaximumMessage', so a buffer // size of this plus the header and trailer sizes should be safe enough. // cbIoBufferLength = Sizes.cbHeader + Sizes.cbMaximumMessage + Sizes.cbTrailer; pbIoBuffer = (char *)LocalAlloc(LMEM_FIXED, cbIoBufferLength); if(pbIoBuffer == NULL) { return SEC_E_INTERNAL_ERROR; } // // Build an HTTP request to send to the server. // // Remove the trailing backslash from the filename, should one exist. if(path && strlen(path) > 1 && path[strlen(path) - 1] == '/') { path[strlen(path)-1] = 0; } // Build the HTTP request offset into the data buffer by "header size" // bytes. This enables Schannel to perform the encryption in place, // which is a significant performance win. pbMessage = pbIoBuffer + Sizes.cbHeader; // Build HTTP request. Note that I'm assuming that this is less than // the maximum message size. If it weren't, it would have to be broken up. strcpy(pbMessage, "GET "); if (path != 0) strcat(pbMessage, path); if (query != 0) { // Tack on a ? before the fragment strcat(pbMessage,"?"); strcat(pbMessage, query); } if (fragment != 0) { strcat(pbMessage, fragment); } strcat(pbMessage, " HTTP/1.0\r\n"); strcat(pbMessage, "Host: "); strcat(pbMessage, hostName); if (portNumber != 80) { strcat(pbMessage, ":"); int i = (int) strlen(pbMessage); _itoa(portNumber, pbMessage+i, 10); } cbMessage = (DWORD)strlen(pbMessage); // // Encrypt the HTTP request. // Buffers[0].pvBuffer = pbIoBuffer; Buffers[0].cbBuffer = Sizes.cbHeader; Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; Buffers[1].pvBuffer = pbMessage; Buffers[1].cbBuffer = cbMessage; Buffers[1].BufferType = SECBUFFER_DATA; Buffers[2].pvBuffer = pbMessage + cbMessage; Buffers[2].cbBuffer = Sizes.cbTrailer; Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; Buffers[3].BufferType = SECBUFFER_EMPTY; Message.ulVersion = SECBUFFER_VERSION; Message.cBuffers = 4; Message.pBuffers = Buffers; scRet = m_pSSPI->EncryptMessage(phContext, 0, &Message, 0); if(FAILED(scRet)) { return scRet; } // // Send the encrypted data to the server. // cbData = send(Socket, pbIoBuffer, Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer, 0); if(cbData == SOCKET_ERROR || cbData == 0) { m_pSSPI->DeleteSecurityContext(phContext); return SEC_E_INTERNAL_ERROR; } //printf("%d bytes of application data sent\n", cbData); // // Read data from server until done. // cbIoBuffer = 0; while(TRUE) { // // Read some data. // if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) { cbData = recv(Socket, pbIoBuffer + cbIoBuffer, cbIoBufferLength - cbIoBuffer, 0); if(cbData == SOCKET_ERROR) { scRet = SEC_E_INTERNAL_ERROR; break; } else if(cbData == 0) { // Server disconnected. if(cbIoBuffer) { scRet = SEC_E_INTERNAL_ERROR; return scRet; } else { break; } } else { cbIoBuffer += cbData; } } // // Attempt to decrypt the received data. // Buffers[0].pvBuffer = pbIoBuffer; Buffers[0].cbBuffer = cbIoBuffer; Buffers[0].BufferType = SECBUFFER_DATA; Buffers[1].BufferType = SECBUFFER_EMPTY; Buffers[2].BufferType = SECBUFFER_EMPTY; Buffers[3].BufferType = SECBUFFER_EMPTY; Message.ulVersion = SECBUFFER_VERSION; Message.cBuffers = 4; Message.pBuffers = Buffers; scRet = m_pSSPI->DecryptMessage(phContext, &Message, 0, NULL); if(scRet == SEC_E_INCOMPLETE_MESSAGE) { // The input buffer contains only a fragment of an // encrypted record. Loop around and read some more // data. continue; } // Server signalled end of session if(scRet == SEC_I_CONTEXT_EXPIRED) break; if( scRet != SEC_E_OK && scRet != SEC_I_RENEGOTIATE && scRet != SEC_I_CONTEXT_EXPIRED) { return scRet; } // Locate data and (optional) extra buffers. pDataBuffer = NULL; pExtraBuffer = NULL; for(i = 1; i < 4; i++) { if(pDataBuffer == NULL && Buffers[i].BufferType == SECBUFFER_DATA) { pDataBuffer = &Buffers[i]; } if(pExtraBuffer == NULL && Buffers[i].BufferType == SECBUFFER_EXTRA) { pExtraBuffer = &Buffers[i]; } } // Display or otherwise process the decrypted data. if(pDataBuffer) { sb.sbMemcpyIn(pDataBuffer->pvBuffer, pDataBuffer->cbBuffer); } // Move any "extra" data to the input buffer. if(pExtraBuffer) { MoveMemory(pbIoBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer); cbIoBuffer = pExtraBuffer->cbBuffer; } else { cbIoBuffer = 0; } if(scRet == SEC_I_RENEGOTIATE) { // The server wants to perform another handshake // sequence. scRet = clientHandshakeLoop(Socket, phCreds, phContext, FALSE, &ExtraBuffer); if(scRet != SEC_E_OK) { return scRet; } // Move any "extra" data to the input buffer. if(ExtraBuffer.pvBuffer) { MoveMemory(pbIoBuffer, ExtraBuffer.pvBuffer, ExtraBuffer.cbBuffer); cbIoBuffer = ExtraBuffer.cbBuffer; } } } return SEC_E_OK; } DWORD XSECURIResolverSSLWin32::verifyServerCertificate( PCCERT_CONTEXT pServerCert, PSTR pszServerName, DWORD dwCertFlags) { HTTPSPolicyCallbackData polHttps; CERT_CHAIN_POLICY_PARA PolicyPara; CERT_CHAIN_POLICY_STATUS PolicyStatus; CERT_CHAIN_PARA ChainPara; PCCERT_CHAIN_CONTEXT pChainContext = NULL; LPSTR rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH, szOID_SERVER_GATED_CRYPTO, szOID_SGC_NETSCAPE }; DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR); PWSTR pwszServerName = NULL; DWORD cchServerName; DWORD Status; if(pServerCert == NULL) { Status = SEC_E_WRONG_PRINCIPAL; goto cleanup; } // // Convert server name to unicode. // if(pszServerName == NULL || strlen(pszServerName) == 0) { Status = SEC_E_WRONG_PRINCIPAL; goto cleanup; } cchServerName = MultiByteToWideChar(CP_ACP, 0, pszServerName, -1, NULL, 0); pwszServerName = (unsigned short *)LocalAlloc(LMEM_FIXED, cchServerName * sizeof(WCHAR)); if(pwszServerName == NULL) { Status = SEC_E_INSUFFICIENT_MEMORY; goto cleanup; } cchServerName = MultiByteToWideChar(CP_ACP, 0, pszServerName, -1, pwszServerName, cchServerName); if(cchServerName == 0) { Status = SEC_E_WRONG_PRINCIPAL; goto cleanup; } // // Build certificate chain. // ZeroMemory(&ChainPara, sizeof(ChainPara)); ChainPara.cbSize = sizeof(ChainPara); ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages; ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages; if(!CertGetCertificateChain( NULL, pServerCert, NULL, pServerCert->hCertStore, &ChainPara, 0, NULL, &pChainContext)) { Status = GetLastError(); goto cleanup; } // // Validate certificate chain. // ZeroMemory(&polHttps, sizeof(HTTPSPolicyCallbackData)); polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData); polHttps.dwAuthType = AUTHTYPE_SERVER; polHttps.fdwChecks = dwCertFlags; polHttps.pwszServerName = pwszServerName; memset(&PolicyPara, 0, sizeof(PolicyPara)); PolicyPara.cbSize = sizeof(PolicyPara); PolicyPara.pvExtraPolicyPara = &polHttps; memset(&PolicyStatus, 0, sizeof(PolicyStatus)); PolicyStatus.cbSize = sizeof(PolicyStatus); if(!CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_SSL, pChainContext, &PolicyPara, &PolicyStatus)) { Status = GetLastError(); goto cleanup; } if(PolicyStatus.dwError) { Status = PolicyStatus.dwError; goto cleanup; } Status = SEC_E_OK; cleanup: if(pChainContext) { CertFreeCertificateChain(pChainContext); } if(pwszServerName) { LocalFree(pwszServerName); } return Status; } void XSECURIResolverSSLWin32::getNewClientCredentials(CredHandle *phCreds, CtxtHandle *phContext) { 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 = m_pSSPI->QueryContextAttributes(phContext, SECPKG_ATTR_ISSUER_LIST_EX, (PVOID)&IssuerListInfo); if(Status != SEC_E_OK) { return; } // // 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; HCERTSTORE hMyCertStore = NULL; // Open the "MY" certificate store, which is where Internet Explorer // stores its client certificates. hMyCertStore = CertOpenSystemStore(0, "MY"); if(!hMyCertStore) { return; } while(TRUE) { // Find a certificate chain. pChainContext = CertFindChainInStore(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_SchannelCred.dwVersion = SCHANNEL_CRED_VERSION; m_SchannelCred.cCreds = 1; m_SchannelCred.paCred = &pCertContext; Status = m_pSSPI->AcquireCredentialsHandleA( NULL, // Name of principal UNISP_NAME_A, // Name of package SECPKG_CRED_OUTBOUND, // Flags indicating use NULL, // Pointer to logon ID &m_SchannelCred, // 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. m_pSSPI->FreeCredentialsHandle(phCreds); *phCreds = hCreds; // // As you can see, this sample code maintains a single credential // handle, replacing it as necessary. This is a little unusual. // // Many applications maintain a global credential handle that's // anonymous (that is, it doesn't contain a client certificate), // which is used to connect to all servers. If a particular server // should require client authentication, then a new credential // is created for use when connecting to that server. The global // anonymous credential is retained for future connections to // other servers. // // Maintaining a single anonymous credential that's used whenever // possible is most efficient, since creating new credentials all // the time is rather expensive. // break; } // Close "MY" certificate store. if(hMyCertStore) { CertCloseStore(hMyCertStore, 0); } } void XSECURIResolverSSLWin32::setClientCertificate(PCCERT_CONTEXT pCertContext) { m_pCertContext = pCertContext; } void XSECURIResolverSSLWin32::setProtocol(ProtocolType protocol) { switch(protocol) { case PROTO_PCT1: m_dwProtocol = SP_PROT_PCT1; break; case PROTO_SSL2: m_dwProtocol = SP_PROT_SSL2; break; case PROTO_SSL3: m_dwProtocol = SP_PROT_SSL3; break; case PROTO_TLS1: m_dwProtocol = SP_PROT_TLS1; break; default: m_dwProtocol = SP_PROT_SSL2; break; } } void XSECURIResolverSSLWin32::setKeyExchAlg(KeyExchType aiKeyExch) { switch(aiKeyExch) { case KEY_EXCH_RSA: m_aiKeyExch = CALG_RSA_KEYX; break; case KEY_EXCH_DH: m_aiKeyExch = CALG_DH_EPHEM; break; default: m_aiKeyExch = CALG_RSA_KEYX; break; } }
#include <xsec/utils/XSECPlatformUtils.hpp> #include <xercesc/util/PlatformUtils.hpp> #include <xsec/framework/XSECProvider.hpp> #include <xsec/framework/Win32/XSECURIResolverSSLWin32.hpp> #include <iostream> #include <string> using namespace std; XSEC_USING_XERCES(XMLPlatformUtils); XERCES_CPP_NAMESPACE_USE void main() { XMLPlatformUtils ::Initialize(); XSECPlatformUtils::Initialise(); { PCCERT_CONTEXT pCertContext = NULL; HCERTSTORE hMyCertStore = CertOpenSystemStore(0, "MY"); if(!hMyCertStore) { cout << "**** Error returned by CertOpenSystemStore\n" << GetLastError(); return; } pCertContext = CertFindCertificateInStore(hMyCertStore, X509_ASN_ENCODING, 0, CERT_FIND_SUBJECT_STR_A, "bankpass_cript", NULL); if(pCertContext == NULL) { cout << "**** Error returned by CertFindCertificateInStore\n" << GetLastError(); return; } XSECURIResolverSSLWin32 * res = new XSECURIResolverSSLWin32(); res->setClientCertificate(pCertContext); BinInputStream * is = res->resolveURI(L"localhost:443/file.txt"); unsigned int read = 1; XMLByte buf[4096]; char txt[4096]; while (read) { read = is->readBytes(buf, 4096); strncpy(txt, (const char*)buf, read); cout << txt; } if(hMyCertStore) { CertCloseStore(hMyCertStore, 0); } } XSECPlatformUtils::Terminate(); XMLPlatformUtils ::Terminate(); }