/*
Module : SSLWrappers.h
Purpose: Defines the interface for a C++ wrapper class for a SSL SChannel Security Support Provider 
         Interface (SSPI) on Windows
History: PJN / 22-10-2014 1. Initial public release
         PJN / 26-11-2014 1. Changed the CSSL class to contain a credentials reference rather than an actual 
                          instance. A new class called CCachedCredentials is what the CSSL maintains a 
                          reference to.
         PJN / 16-12-2014 1. Removed the unused variable CCachedCredentials::m_Credentials.
                          2. Removed the memset call and the setting of the dwVersion member variable of the 
                          m_sslCredentials member variable in the AcquireClient and AcquireServer methods of 
                          CCachedCredentials. This allows client code to set specific values into this structure 
                          prior to it being used in these methods.
                          3. The default value for CSSL::m_dwAuditFlags is now 0 for debug builds in addition to 
                          release builds.
                          4. Addition of a CSSL::GetSecPkgStreamSizes method to allow access to the internal
                          m_SecPkgSizes member variable
                          5. Addition of a CSSL::SendEncrypted method which handles making multiple calls to 
                          SendEncryptedMessage if the data to send exceeds the maximum SSL message size.
         PJN / 08-02-2015 1. Updated copyright details
                          2. Fixed a bug in the CSSL constructor where the "m_lExtraReadData" was not being 
                          initialized to 0. This causes a bug later when GetEncryptedMessage is called and
                          you have compiled your application without the VC 2013 /sdl compiler flag which 
                          resulted in the bug being hidden as /sdl auto initializes member variables. Thanks
                          to Bostjan Erzen for reporting this bug
         PJN / 04-11-2015 1. Updated the code to compile cleanly in VC 2015.
                          2. Fixed a bug in CSSL::VerifyServerCertificate where the code was using wrong math
                          to calculate the size of the pCerts local variable.
                          3. Fixed a bug in the demo app's DisplayCertChain method when calculating the size
                          of the buffer to pass to CertNameToStr API.
                          4. Reworked various places which allocates heap memory to use ATL::CHeapPtr instead 
                          of raw calls to HeapAlloc.
         PJN / 16-10-2016 1. Updated copyright details.
                          2. Updated the sample app to compile using the latest version of the author's 
                          CWSocket class.
                          3. Binaries of the demo app are now included in the SSLWrappers zip file download.
         PJN / 22-10-2016 1. Updated the sample app to do better logging of the certificate & connection
                          details. The sample app now logs details if the cipher used is CALG_ECDH_EPHEM.
                          2. Fixed up the audit text in some of the code when allocating heap memory.
         PJN / 03-02-2017 1. Updated copyright details.
                          2. Removed unnecessary demo code from the demo app included in the download. As 
                          reported by Geoff Stevens, this demo code which calls the ExportSecurityContext and
                          FreeContextBuffer APIs seems to cause heap corruption inside the FreeContextBuffer
                          call. The issue only seems to occur if the FreeContextBuffer call occurs very 
                          quickly after the ExportSecurityContext call. If the call to FreeContextBuffer 
                          occurs more than 100 ms later than ExportSecurityContext then the heap corruption 
                          does not occur. I was not able to reproduce the issue and as the code was just
                          demo code, I have simply removed it from the demo app.
                          3. Fixed a number of typos in the text when calling the Audit methods.
         PJN / 15-04-2017 1. Updated the sample app to log more information about the connection in the 
                          DisplayConnectionInfo function. New details logged include SECPKGCONTEXT_CONNECTION_INFO_EX_V1,
                          SECPKG_ATTR_CIPHER_INFO, SECPKG_ATTR_CIPHER_STRENGTHS, SECPKG_ATTR_SUPPORTED_ALGS, 
                          SECPKG_ATTR_SUPPORTED_PROTOCOLS, SECPKG_ATTR_APP_DATA, SECPKG_ATTR_AUTHORITY, 
                          SECPKG_ATTR_EAP_KEY_BLOCK, SECPKG_ATTR_FLAGS, SECPKG_ATTR_LIFESPAN,
                          SECPKG_ATTR_NAMES, SECPKG_ATTR_SESSION_INFO, SECPKG_ATTR_SESSION_KEY,
                          SECPKG_ATTR_SIZES, SECPKG_ATTR_STREAM_SIZES, SECPKG_ATTR_SUPPORTED_SIGNATURES, 
                          SECPKG_ATTR_TARGET_INFORMATION, SECPKG_ATTR_KEYING_MATERIAL, SECPKG_ATTR_SRTP_PARAMETERS,
                          SECPKG_ATTR_TOKEN_BINDING & SECPKG_ATTR_KEYING_MATERIAL_TOKEN_BINDING attributes. This also 
                          includes provisional logging of TLS v1.3 details. At the moment Windows 10 v1703 does not 
                          support TLS v1.3 but I would expect Windows 10 to include support for this protocol sometime 
                          in 2017.
                          2. Reviewed schannel.h header file from Windows 10 v15063 SDK for any changes to SChannel
                          in Windows 10 v1703.
         PJN / 07-06-2018 1. Updated copyright details.
                          2. Fixed a number of C++ core guidelines compiler warnings. These changes mean that
                          the code will now only compile on VC 2017 or later.
                          3. Replaced CMessage class with std::vector<BYTE>
         PJN / 22-08-2018 1. Fixed a number of compiler warnings when using VS 2017 15.8.1
         PJN / 09-04-2019 1. Updated copyright details.
                          2. Fixed a number of compiler warnings when using VS 2019.
         PJN / 12-09-2019 1. Fixed a number of compiler warnings when the code is compiled with VS 2019 Preview
         PJN / 10-11-2019 1. Updated initialization of various structs to use C++ 11 list initialization
                          2. Replaced BOOL with bool in a number of places throughout the codebase
         PJN / 04-01-2020 1. Fixed more Clang-Tidy static code analysis warnings in the code.
         PJN / 12-04-2020 1. Fixed more Clang-Tidy static code analysis warnings in the code.
         PJN / 06-11-2020 1. Fixed an issue where the m_lExtraReadData member variable is not reset in the SSLConnect and 
                          SSLAccept methods. This can lead to problems where a CSSL instance is reused to connect
                          to another server or a TLS server connects to a new client. This issue was occurring in the SMTP
                          code of the author when connecting to some SMTP servers which were using a pure SSL/TLS 
                          connection as opposed to STARTTLS. Thanks to Koen De Wolf for reporting this issue and helping 
                          fix it.

Copyright (c) 2014 - 2020 by PJ Naughter (Web: www.naughter.com, Email: pjna@naughter.com)

All rights reserved.

Copyright / Usage Details:

You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) 
when your product is released in binary form. You are allowed to modify the source code in any way you want 
except you cannot modify the copyright details at the top of each module. If you want to distribute source 
code with your application, then you are only allowed to distribute versions released by the author. This is 
to maintain a single distribution point for the source code. 

*/


////////////////////////// Macros / Defines ///////////////////////////////////

#pragma once

#ifndef __SSLWRAPPERS_H__
#define __SSLWRAPPERS_H__


#pragma comment(lib, "Secur32.lib") //Automatically link in the Security dll


////////////////////////// Includes ///////////////////////////////////////////

#ifndef __SSPI_H__
#define SECURITY_WIN32
#pragma message("To avoid this message, please put sspi.h in your pre compiled header (normally stdafx.h)")
#include <sspi.h>
#endif //#ifndef __SSPI_H__
#ifndef __SCHANNEL_H__
#pragma message("To avoid this message, please put schannel.h in your pre compiled header (normally stdafx.h)")
#include <schannel.h>
#endif //#ifndef __SCHANNEL_H__
#ifndef __ATLBASE_H__
#pragma message("To avoid this message, please put atlbase.h in your pre compiled header (normally stdafx.h)")
#include <atlbase.h>
#endif //#ifndef __ATLBASE_H__
#ifndef __ATLSTR_H__
#pragma message("To avoid this message, please put atlstr.h in your pre compiled header (normally stdafx.h)")
#include <atlstr.h>
#endif //#ifndef __ATLBASE_H__
#ifndef _ARRAY_
#pragma message("To avoid this message, please put array in your pre compiled header (normally stdafx.h)")
#include <array>
#endif //#ifndef _ARRAY_
#include "CryptoWrappersCertificate.h" //If you get a compilation error about this missing header file, then you need to download my CryptoWrappers wrapper classes from http://www.naughter.com/cryptowrappers.html
#include "CryptoWrappersChain.h" //If you get a compilation error about this missing header file, then you need to download my CryptoWrappers wrapper classes from http://www.naughter.com/cryptowrappers.html
#include "CryptoWrappersStore.h" //If you get a compilation error about this missing header file, then you need to download my CryptoWrappers wrapper classes from http://www.naughter.com/cryptowrappers.html


////////////////////////// Classes ////////////////////////////////////////////

namespace SSLWrappers
{


//Encapsulates a SChannel CredHandle
class CCredentials
{
public:
//Constructors / Destructors
  CCredentials() noexcept
  {
    m_Handle.dwUpper = 0;
    m_Handle.dwLower = 0;
  }

  CCredentials(_In_ const CCredentials&) = delete;

  CCredentials(_In_ CCredentials&& credentials) noexcept
  {
    m_Handle.dwUpper = 0;
    m_Handle.dwLower = 0;
    Attach(credentials.Detach());
  }
  
  explicit CCredentials(_In_ const CredHandle& handle) noexcept
  {
    m_Handle.dwUpper = handle.dwUpper;
    m_Handle.dwLower = handle.dwLower;
  }

  ~CCredentials() noexcept
  {
    if (ValidHandle())
      Free();
  }
  
//Methods
  CCredentials& operator=(_In_ const CCredentials&) = delete;

  CCredentials& operator=(_In_ CCredentials&& credentials) noexcept
  {
    if (ValidHandle())
      Free();
    Attach(credentials.Detach());

    return *this;
  }

  _NODISCARD CredHandle Handle() const noexcept
  {
    return m_Handle;
  }

  void Attach(_In_ const CredHandle& handle) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_Handle.dwUpper == 0);
#pragma warning(suppress: 26477)
    ATLASSERT(m_Handle.dwLower == 0);

    m_Handle.dwUpper = handle.dwUpper;
    m_Handle.dwLower = handle.dwLower;
  }

  CredHandle Detach() noexcept
  {
    CredHandle handle;
    handle.dwUpper = m_Handle.dwUpper;
    handle.dwLower = m_Handle.dwLower;
    m_Handle.dwUpper = 0;
    m_Handle.dwLower = 0;
    return handle;
  }

  _Check_return_
  SECURITY_STATUS Acquire(_In_ unsigned long fCredentialUse, _In_opt_ SCHANNEL_CRED* pAuthData, _Out_opt_ PTimeStamp ptsExpiry = nullptr) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(!ValidHandle());

#pragma warning(suppress: 26465)
    return AcquireCredentialsHandle(nullptr, const_cast<LPTSTR>(UNISP_NAME), fCredentialUse, nullptr, pAuthData, nullptr, nullptr, &m_Handle, ptsExpiry);
  }

  SECURITY_STATUS Free() noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    const SECURITY_STATUS status = FreeCredentialsHandle(&m_Handle);
    m_Handle.dwUpper = 0;
    m_Handle.dwLower = 0;
    return status;
  }

  SECURITY_STATUS QueryAttibute(_In_ unsigned long ulAttribute, _Inout_ void* pBuffer) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    return QueryCredentialsAttributes(&m_Handle, ulAttribute, pBuffer);
  }

  _NODISCARD bool ValidHandle() const noexcept
  {
    return (m_Handle.dwUpper != 0) || (m_Handle.dwLower != 0);
  }

//Member variables
  CredHandle m_Handle;
};


//Encapsulates a SChannel CtxtHandle
class CContext
{
public:
//Constructors / Destructors
  CContext() noexcept
  {
    m_Handle.dwUpper = 0;
    m_Handle.dwLower = 0;
  }

  CContext(_In_ const CContext&) = delete;

  CContext(_In_ CContext&& context) noexcept
  {
    m_Handle.dwUpper = 0;
    m_Handle.dwLower = 0;
    Attach(context.Detach());
  }
  
  explicit CContext(_In_ const CtxtHandle& handle) noexcept
  {
    m_Handle.dwUpper = handle.dwUpper;
    m_Handle.dwLower = handle.dwLower;
  }

  ~CContext() noexcept
  {
    if (ValidHandle())
      Delete();
  }
  
//Methods
  CContext& operator=(_In_ const CContext&) = delete;

  CContext& operator=(_In_ CContext&& context) noexcept
  {
    if (ValidHandle())
      Delete();
    Attach(context.Detach());

    return *this;
  }

  _NODISCARD CtxtHandle Handle() const noexcept
  {
    return m_Handle;
  }

  void Attach(_In_ const CtxtHandle& handle) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(!ValidHandle());

    m_Handle.dwUpper = handle.dwUpper;
    m_Handle.dwLower = handle.dwLower;
  }

  CtxtHandle Detach() noexcept
  {
    CtxtHandle handle;
    handle.dwUpper = m_Handle.dwUpper;
    handle.dwLower = m_Handle.dwLower;
    m_Handle.dwUpper = 0;
    m_Handle.dwLower = 0;
    return handle;
  }

  _Check_return_
  SECURITY_STATUS Initialize(_In_ CCredentials& credentials,
                           #ifdef _UNICODE
                             _In_opt_ SEC_WCHAR* pszTargetName,
                           #else
                             _In_opt_ SEC_CHAR* pszTargetName,
                           #endif //#ifdef _UNICODE
                             _In_ unsigned long fContextReq, _In_opt_ PSecBufferDesc pInput, _Inout_opt_ PSecBufferDesc pOutput, _Out_ unsigned long* pfContextAttr, _Out_opt_ PTimeStamp ptsExpiry = nullptr) noexcept
  {
    return InitializeSecurityContext(&credentials.m_Handle, ValidHandle() ? &m_Handle : nullptr, pszTargetName, fContextReq, 0, 0, pInput, 0, &m_Handle, pOutput, pfContextAttr, ptsExpiry);
  }

  _Check_return_
  SECURITY_STATUS Accept(_In_ CCredentials& credentials, _In_opt_ PSecBufferDesc pInput, _In_ unsigned long fContextReq, _Inout_opt_ PSecBufferDesc pOutput,
                         _Out_ unsigned long* pfContextAttr, _Out_opt_ PTimeStamp ptsExpiry = nullptr) noexcept
  {
    return AcceptSecurityContext(&credentials.m_Handle, ValidHandle() ? &m_Handle : nullptr, pInput, fContextReq, 0, &m_Handle, pOutput, pfContextAttr, ptsExpiry);
  }

  SECURITY_STATUS Delete() noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    const SECURITY_STATUS status = DeleteSecurityContext(&m_Handle);
    m_Handle.dwUpper = 0;
    m_Handle.dwLower = 0;
    return status;
  }

  SECURITY_STATUS SetAttribute(_In_ unsigned long ulAttribute, _In_reads_bytes_(cbBuffer) void* pBuffer, _In_ unsigned long cbBuffer) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    return SetContextAttributes(&m_Handle, ulAttribute, pBuffer, cbBuffer);
  }

  SECURITY_STATUS QueryAttribute(_In_ unsigned long ulAttribute, _Out_ void* pBuffer) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    return QueryContextAttributes(&m_Handle, ulAttribute, pBuffer);
  }

  SECURITY_STATUS DecryptMessage(_In_ PSecBufferDesc pMessage) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    return ::DecryptMessage(&m_Handle, pMessage, 0, nullptr);
  }

  SECURITY_STATUS EncryptMessage(_In_ PSecBufferDesc pMessage, _In_ unsigned long fQOP = 0) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    return ::EncryptMessage(&m_Handle, fQOP, pMessage, 0);
  }

  SECURITY_STATUS ApplyControlToken(_In_ PSecBufferDesc pInput) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    return ::ApplyControlToken(&m_Handle, pInput);
  }

  SECURITY_STATUS Impersonate() noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    return ImpersonateSecurityContext(&m_Handle);
  }

  SECURITY_STATUS Revert() noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    return RevertSecurityContext(&m_Handle);
  }

  SECURITY_STATUS QueryToken(_Out_ void** phToken) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    return QuerySecurityContextToken(&m_Handle, phToken);
  }

  SECURITY_STATUS Export(_In_ ULONG fFlags, _Out_ PSecBuffer pPackedContext, _Out_ void** pToken) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(ValidHandle());

    return ExportSecurityContext(&m_Handle, fFlags, pPackedContext, pToken);
  }

  SECURITY_STATUS Import(_In_ PSecBuffer pPackedContext, _In_ VOID* Token) noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(!ValidHandle());

#pragma warning(suppress: 26465)
    return ImportSecurityContext(const_cast<LPTSTR>(UNISP_NAME), pPackedContext, Token, &m_Handle);
  }

  _NODISCARD bool ValidHandle() const noexcept
  {
    return (m_Handle.dwUpper != 0) || (m_Handle.dwLower != 0);
  }

//Member variables
  CtxtHandle m_Handle;
};


//A class which caches the certificate details for credentials 
class CCachedCredentials : public CCredentials
{
public:
//Constructors / Destructors
  CCachedCredentials() noexcept : m_sslCredentials{}
  {
    m_sslCredentials.dwVersion = SCHANNEL_CRED_VERSION;
  }

//Methods
  SECURITY_STATUS AcquireClient(_In_opt_ LPTSTR pszClientCertificateName = nullptr, _In_ LPCSTR lpszStoreProvider = CERT_STORE_PROV_SYSTEM, 
                                _In_ DWORD dwCertOpenStoreFlags = CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_READONLY_FLAG, _In_opt_ const void* pvCertOpenStorePara = L"MY") noexcept
  {
    //Find the client certificate if required
    if (pszClientCertificateName != nullptr)
    {
      //Open the specified certificate store
      CryptoWrappers::CStore certStore;
      if (!certStore.Open(lpszStoreProvider, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, dwCertOpenStoreFlags, pvCertOpenStorePara))
        return GetLastError();

      //Fill in an attribute structure for the certificate common name we are looking for
      CERT_RDN_ATTR certRDNAttr;
#pragma warning(suppress: 26465)
      certRDNAttr.pszObjId = const_cast<LPSTR>(szOID_COMMON_NAME);
      certRDNAttr.dwValueType = CERT_RDN_PRINTABLE_STRING;
#pragma warning(suppress: 26490)
      certRDNAttr.Value.pbData = reinterpret_cast<BYTE*>(pszClientCertificateName);
#pragma warning(suppress: 26472)
      certRDNAttr.Value.cbData = static_cast<DWORD>(_tcslen(pszClientCertificateName) * sizeof(TCHAR));
      CERT_RDN certRDN;
      certRDN.cRDNAttr = 1;
      certRDN.rgRDNAttr = &certRDNAttr;

      //Find the certificate context
      CryptoWrappers::CCertificate prevCertificate;
      m_Certificate = certStore.FindCertificate(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                                    #ifdef _UNICODE 
                                                      CERT_UNICODE_IS_RDN_ATTRS_FLAG, 
                                                    #else
                                                      0,
                                                    #endif //#ifdef _UNICODE
                                                      CERT_FIND_SUBJECT_ATTR, &certRDN, prevCertificate);
      if (m_Certificate.Handle() == nullptr)
        return GetLastError();
    }

    if (m_Certificate.Handle() != nullptr)
    {
      m_sslCredentials.cCreds = 1;
      m_sslCredentials.paCred = &m_Certificate.m_pCert;
    }
    return Acquire(SECPKG_CRED_OUTBOUND, &m_sslCredentials);
  }

  SECURITY_STATUS AcquireServer(_In_ LPTSTR pszServerCertificateName, _In_ LPCSTR lpszStoreProvider = CERT_STORE_PROV_SYSTEM, 
                                _In_ DWORD dwCertOpenStoreFlags = CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_READONLY_FLAG, _In_opt_ const void* pvCertOpenStorePara = L"MY") noexcept
  {
    //Open the specified certificate store
    CryptoWrappers::CStore certStore;
    if (!certStore.Open(lpszStoreProvider, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, dwCertOpenStoreFlags, pvCertOpenStorePara))
      return GetLastError();

    //Fill in an attribute structure for the certificate common name we are looking for
    CERT_RDN_ATTR certRDNAttr;
#pragma warning(suppress: 26465)
    certRDNAttr.pszObjId = const_cast<LPSTR>(szOID_COMMON_NAME);
    certRDNAttr.dwValueType = CERT_RDN_PRINTABLE_STRING;
#pragma warning(suppress: 26490)
    certRDNAttr.Value.pbData = reinterpret_cast<BYTE*>(pszServerCertificateName);
#pragma warning(suppress: 26472)
    certRDNAttr.Value.cbData = static_cast<DWORD>(_tcslen(pszServerCertificateName) * sizeof(TCHAR));
    CERT_RDN certRDN;
    certRDN.cRDNAttr = 1;
#pragma warning(suppress: 26485)
    certRDN.rgRDNAttr = &certRDNAttr;

    //Find the certificate context
    CryptoWrappers::CCertificate prevCertificate;
    m_Certificate = certStore.FindCertificate(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                                  #ifdef _UNICODE 
                                                    CERT_UNICODE_IS_RDN_ATTRS_FLAG, 
                                                  #else
                                                    0,
                                                  #endif //#ifdef _UNICODE
                                                    CERT_FIND_SUBJECT_ATTR, &certRDN, prevCertificate);
    if (m_Certificate.Handle() == nullptr)
      return GetLastError();

    m_sslCredentials.cCreds = 1;
    m_sslCredentials.paCred = &m_Certificate.m_pCert;
    return Acquire(SECPKG_CRED_INBOUND, &m_sslCredentials);
  }

//Member variables
  SCHANNEL_CRED m_sslCredentials;
  CryptoWrappers::CCertificate m_Certificate;
};



//The higher level class which provides a base class implementation of SSL using SSPI
class CSSL : public CContext
{
public:
//Defines
  enum
  {
      AUDIT_DATA = 0x01,
      AUDIT_EVENTS = 0x02,
      AUDIT_ERRORS = 0x04
  };

//Constructors / Destructors
  CSSL() noexcept : m_lReadBufferLen(0),
                    m_lWriteBufferLen(0),
                    m_lExtraReadData(0),
                    m_SecPkgSizes{},
                    m_dwAuditFlags(0),
                    m_bVerifyServerCertificate(false),
                    m_bCheckServerCertificateRevocation(false),
                    m_bVerifyClientCertificate(false),
                    m_dwCertVerifyCertificateChainPolicyFlags(0),
                    m_dwCertGetCertificateChainFlags(CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT),
                    m_pCachedCredentials(nullptr)
  {
  }

  virtual ~CSSL() = default;
  CSSL(_In_ const CSSL&) = delete;
  CSSL(_In_ CSSL&&) = delete;

//Virtual methods
  virtual SECURITY_STATUS SendData(_In_reads_bytes_opt_(lSize) const BYTE* pbyData, ULONG lSize) noexcept
  {
#pragma warning(suppress: 26477)
    ATLASSERT(false); //Derived classes need to implement this method

    UNREFERENCED_PARAMETER(pbyData);
    UNREFERENCED_PARAMETER(lSize);

    return E_NOTIMPL;
  }

  virtual SECURITY_STATUS ReceiveData(_Out_writes_bytes_to_(lSize, lReceived) BYTE* pbyData, _In_ ULONG lSize, _Out_ ULONG& lReceived) noexcept
  {
#pragma warning(suppress: 26477)
    ATLASSERT(false); //Derived classes need to implement this method

    UNREFERENCED_PARAMETER(pbyData);
    UNREFERENCED_PARAMETER(lSize);

    lReceived = 0;
    return E_NOTIMPL;
  }

//Non virtual methods
  CSSL& operator=(_In_ const CSSL&) = delete;
  CSSL& operator=(_In_ CSSL&&) = delete;

  void SetCachedCredentials(_In_ CCachedCredentials* pCredentials) noexcept
  {
    m_pCachedCredentials = pCredentials;
  }

  _NODISCARD CCachedCredentials* GetCachedCredentials() const noexcept
  {
    return m_pCachedCredentials;
  }

  void SetAuditFlags(_In_ DWORD dwAuditFlags) noexcept
  {
    m_dwAuditFlags = dwAuditFlags;
  }

  _NODISCARD DWORD GetAuditFlags() const noexcept
  {
    return m_dwAuditFlags;
  }

  void SetVerifyServerCertificate(_In_ bool bVerifyServerCertificate) noexcept
  {
    m_bVerifyServerCertificate = bVerifyServerCertificate;
  }

  _NODISCARD bool GetVerifyServerCertificate() const noexcept
  {
    return m_bVerifyServerCertificate;
  }

  void SetVerifyClientCertificate(_In_ bool bVerifyClientCertificate) noexcept
  {
    m_bVerifyClientCertificate = bVerifyClientCertificate;
  }

  _NODISCARD bool GetVerifyClientCertificate() const noexcept
  {
    return m_bVerifyClientCertificate;
  }

  void SetCertVerifyCertificateChainPolicyFlags(_In_ DWORD dwFlags) noexcept
  {
    m_dwCertVerifyCertificateChainPolicyFlags = dwFlags;
  }

  _NODISCARD DWORD GetCertVerifyCertificateChainPolicyFlags() const noexcept
  {
    return m_dwCertVerifyCertificateChainPolicyFlags;
  }

  void SetCertGetCertificateChainFlags(_In_ DWORD dwFlags) noexcept
  {
    m_dwCertGetCertificateChainFlags = dwFlags;
  }

  _NODISCARD DWORD GetCertGetCertificateChainFlags() const noexcept
  {
    return m_dwCertGetCertificateChainFlags;
  }

  void SetCheckServerCertificateRevocation(_In_ bool bCheckServerCertificateRevocation) noexcept
  {
    m_bCheckServerCertificateRevocation = bCheckServerCertificateRevocation;
  }

  _NODISCARD bool GetCheckServerCertificateRevocation() const noexcept
  {
    return m_bCheckServerCertificateRevocation;
  }

  _NODISCARD const SecPkgContext_StreamSizes& GetSecPkgStreamSizes() const noexcept
  {
    return m_SecPkgSizes;
  }

  virtual unsigned long GetClientContextRequest() noexcept
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials != nullptr);

    //Work out the context request flags to use
    unsigned long nContextRequest = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | 
                                    ISC_RET_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;
    if (m_pCachedCredentials->m_sslCredentials.cCreds)
      nContextRequest |= ISC_REQ_MUTUAL_AUTH;

    return nContextRequest;
  }

  virtual SECURITY_STATUS SSLConnect(_In_ LPCTSTR pszServerName)
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials != nullptr);

    //Allocate the receive buffer if necessary
    if (m_lReadBufferLen == 0)
    {
      const SECURITY_STATUS status = AllocateReceiveBuffer();
      if (status != SEC_E_OK)
        return status;
    }

    //Note it is important that we throw away any pending read data waiting to be read via
    //GetEncryptedMessage because we are starting a connection to a new server here
    m_lExtraReadData = 0;

    //Work out the context request flags to use
    const unsigned long nContextRequest = GetClientContextRequest();

    //Initialize the out buffers
    SecBuffer OutBuffers{ 0, SECBUFFER_EMPTY, nullptr };
    SecBufferDesc OutBuffer{ SECBUFFER_VERSION, 1, &OutBuffers };

    //Work with a non const local copy of pszServerName 
    ATL::CAtlString sLocalServerName(pszServerName);

    //Call InitializeSecurityContext for the first time
    unsigned long nContextRequestOut = 0;
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials->ValidHandle());
    SECURITY_STATUS status = Initialize(*m_pCachedCredentials, sLocalServerName.GetBuffer(), nContextRequest, nullptr, &OutBuffer, &nContextRequestOut);
    sLocalServerName.ReleaseBuffer();
    if (status != SEC_I_CONTINUE_NEEDED)
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::SSLConnect, InitializeSecurityContext failed, Error:0x%08X\n"), status);

      return status;
    }

    //Write out any data which SSPI provided to us if required
    if (OutBuffers.cbBuffer != 0)
    {
      //Validate our parameters
#pragma warning(suppress: 26477)
      ATLASSERT(OutBuffers.pvBuffer != nullptr);

      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_EVENTS)
        Audit(_T("CSSL::SSLConnect, InitializeSecurityContext has provided %u bytes of data to send to server\n"), OutBuffers.cbBuffer);
      if (m_dwAuditFlags & AUDIT_DATA)
        AuditData(_T("CSSL::SSLConnect, sending data to server"), static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);

      status = SendData(static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);

      //Free output buffer
      FreeContextBuffer(OutBuffers.pvBuffer);
      OutBuffers.pvBuffer = nullptr;

      if (status != SEC_E_OK)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_ERRORS)
          Audit(_T("CSSL::SSLConnect, An error occurred sending data to the server, Error:0x%08X\n"), status);

        return status;
      }
    }

    //Delegate most of the rest of the work to the internal helper method
    status = ClientHandshakeLoop(pszServerName, nContextRequest, true);
    if (status != SEC_E_OK)
      return status;

    //Allocate the send buffer before we return
    return AllocateSendBuffer();
  }

  virtual SECURITY_STATUS SSLRequestRenegotiationClient(_In_ LPCTSTR pszServerName)
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials != nullptr);

    //Work out the context request flags to use
    const unsigned long nContextRequest = GetClientContextRequest();

    //Initialize the out buffers
    SecBuffer OutBuffers{ 0, SECBUFFER_EMPTY, nullptr };
    SecBufferDesc OutBuffer{ SECBUFFER_VERSION, 1, &OutBuffers };

    //Work with a non const local copy of pszServerName
    ATL::CAtlString sLocalServerName(pszServerName);

    //Call InitializeSecurityContext for the first time
    unsigned long nContextRequestOut = 0;
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials->ValidHandle());
    SECURITY_STATUS status = Initialize(*m_pCachedCredentials, sLocalServerName.GetBuffer(), nContextRequest, nullptr, &OutBuffer, &nContextRequestOut);
    sLocalServerName.ReleaseBuffer();
    if (status != SEC_I_CONTINUE_NEEDED)
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::SSLRequestRenegotiationClient, InitializeSecurityContext failed, Error:0x%08X\n"), status);

      return status;
    }

    //Write out any data which SSPI provided to us if required
    if (OutBuffers.cbBuffer != 0)
    {
      //Validate our parameters
#pragma warning(suppress: 26477)
      ATLASSERT(OutBuffers.pvBuffer != nullptr);

      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_EVENTS)
        Audit(_T("CSSL::SSLRequestRenegotiationClient, InitializeSecurityContext has provided %u bytes of data to send to server\n"), OutBuffers.cbBuffer);
      if (m_dwAuditFlags & AUDIT_DATA)
        AuditData(_T("CSSL::SSLRequestRenegotiationClient, sending data to server"), static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);

      status = SendData(static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);

      //Free output buffer
      FreeContextBuffer(OutBuffers.pvBuffer);
      OutBuffers.pvBuffer = nullptr;

      if (status != SEC_E_OK)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_ERRORS)
          Audit(_T("CSSL::SSLRequestRenegotiationClient, An error occurred sending data to the server, Error:0x%08X\n"), status);

        return status;
      }
    }

    //Delegate most of the rest of the work to the internal helper method
    return ClientHandshakeLoop(pszServerName, nContextRequest, true);
  }

  virtual SECURITY_STATUS SSLHandleRenegotiationClient(_In_ LPCTSTR pszServerName)
  {
    return ClientHandshakeLoop(pszServerName, GetClientContextRequest(), false);
  }

  virtual SECURITY_STATUS SSLAccept(_In_ bool bClientAuth)
  {
    //Allocate the receive buffer if necessary
    if (m_lReadBufferLen == 0)
    {
      const SECURITY_STATUS status = AllocateReceiveBuffer();
      if (status != SEC_E_OK)
        return status;
    }

    //Note it is important that we throw away any pending read data waiting to be read via
    //GetEncryptedMessage because we are accepting a new client connection here
    m_lExtraReadData = 0;

    //Delegate most of the rest of the work to the internal helper method
    const SECURITY_STATUS status = ServerHandshakeLoop(GetServerContextRequest(bClientAuth), true);
    if (status != SEC_E_OK)
      return status;

    //Allocate the send buffer before we return
    return AllocateSendBuffer();
  }

  virtual SECURITY_STATUS SSLRequestRenegotiationServer(_In_ bool bClientAuth)
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials != nullptr);

    //Work out the context request flags to use
    const unsigned long nContextRequest = GetServerContextRequest(bClientAuth);

    //Initialize the out buffers
    SecBuffer OutBuffers{ 0, SECBUFFER_EMPTY, nullptr };
    SecBufferDesc OutBuffer{ SECBUFFER_VERSION, 1, &OutBuffers };

    //Call AcceptSecurityContext
    unsigned long nContextRequestOut = 0;
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials->ValidHandle());
    SECURITY_STATUS status = Accept(*m_pCachedCredentials, nullptr, nContextRequest, &OutBuffer, &nContextRequestOut);
    if (status != SEC_E_OK)
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::SSLRequestRenegotiationServer, InitializeSecurityContext failed, Error:0x%08X\n"), status);

      return status;
    }

    //Write out any data which SSPI provided to us if required
    if (OutBuffers.cbBuffer != 0)
    {
      //Validate our parameters
#pragma warning(suppress: 26477)
      ATLASSUME(OutBuffers.pvBuffer != nullptr);

      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_EVENTS)
        Audit(_T("CSSL::SSLRequestRenegotiationServer, InitializeSecurityContext has provided %u bytes of data to send to server\n"), OutBuffers.cbBuffer);
      if (m_dwAuditFlags & AUDIT_DATA)
        AuditData(_T("CSSL::SSLRequestRenegotiationServer, sending data to client"), static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);

      status = SendData(static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);

      //Free output buffer
      FreeContextBuffer(OutBuffers.pvBuffer);
      OutBuffers.pvBuffer = nullptr;

      if (status != SEC_E_OK)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_ERRORS)
          Audit(_T("CSSL::SSLRequestRenegotiationServer, An error occurred sending data to the client, Error:0x%08X\n"), status);

        return status;
      }
    }

    //Delegate most of the rest of the work to the internal helper method
    return ServerHandshakeLoop(nContextRequest, true);
  }

  virtual SECURITY_STATUS SSLHandleRenegotiationServer(_In_ bool bClientAuth)
  {
    return ServerHandshakeLoop(GetServerContextRequest(bClientAuth), true);
  }

  virtual unsigned long GetServerContextRequest(_In_ bool bClientAuth) noexcept
  {
    //Work out the context request flags to use
    unsigned long nContextRequest = ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY | 
                                    ASC_RET_EXTENDED_ERROR | ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_STREAM;
    if (bClientAuth)
      nContextRequest |= ASC_REQ_MUTUAL_AUTH;

    return nContextRequest;
  }

  virtual SECURITY_STATUS SendEncryptedMessage(_In_reads_bytes_(lSize) const BYTE* pbyData, _In_ ULONG lSize)
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_WriteBuffer.data() != nullptr);

    //Send the data
    if (m_dwAuditFlags & AUDIT_DATA)
      AuditData(_T("CSSL::SendEncryptedMessage, data to send"), pbyData, lSize);
  
    //Check to see if we can handle this sized message
    if ((lSize + m_SecPkgSizes.cbHeader + m_SecPkgSizes.cbTrailer) > m_lWriteBufferLen)
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::SendEncryptedMessage, message is too large\n"));

      return SEC_E_INVALID_PARAMETER;
    }

    //Setup the buffers
#pragma warning(suppress: 26481)
    memcpy_s(m_WriteBuffer.data() + m_SecPkgSizes.cbHeader, m_lWriteBufferLen - m_SecPkgSizes.cbHeader, pbyData, lSize);
#pragma warning(suppress: 26481)
    std::array<SecBuffer, 3> OutBuffers{ { { m_SecPkgSizes.cbHeader, SECBUFFER_STREAM_HEADER, m_WriteBuffer.data() }, { lSize, SECBUFFER_DATA, m_WriteBuffer.data() + m_SecPkgSizes.cbHeader }, { m_SecPkgSizes.cbTrailer, SECBUFFER_STREAM_TRAILER, m_WriteBuffer.data() + m_SecPkgSizes.cbHeader + lSize } } };
    SecBufferDesc OutBuffer{ SECBUFFER_VERSION, 3, OutBuffers.data() };

    //Encrypt the data
    SECURITY_STATUS status = EncryptMessage(&OutBuffer, 0);
    if (status != SEC_E_OK)
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::SendEncryptedMessage failed to encrypt data, Error:0x%08X\n"), status);

      return status;
    }

    //Do auditing if necessary
    if (m_dwAuditFlags & AUDIT_DATA)
#pragma warning(suppress: 26446)
      AuditData(_T("CSSL::SendEncryptedMessage, sending data"), m_WriteBuffer.data(), OutBuffers[0].cbBuffer + OutBuffers[1].cbBuffer + OutBuffers[2].cbBuffer);

    //Send the data
#pragma warning(suppress: 26446)
    status = SendData(m_WriteBuffer.data(), OutBuffers[0].cbBuffer + OutBuffers[1].cbBuffer + OutBuffers[2].cbBuffer);

    if ((status != SEC_E_OK) && (m_dwAuditFlags & AUDIT_ERRORS))
      Audit(_T("CSSL::SendEncryptedMessage, failed to send data, Error:0x%08X\n"), status);

    return status;
  }

  virtual SECURITY_STATUS SendEncrypted(_In_reads_bytes_(lSize) const BYTE* pbyData, _In_ ULONG lSize)
  {
    //Send the data (Note we send it a chunk at a time to ensure we do not exceed the max SSL/TLS message length)
    bool bMoreDataToSend = true;
    ULONG lDataRemaining = lSize;
    const BYTE* pCurrentBuffer = pbyData;
    while (bMoreDataToSend)
    {
      const ULONG lDataToSend = min(lDataRemaining, m_SecPkgSizes.cbMaximumMessage);
#pragma warning(suppress: 26486)
      const SECURITY_STATUS ss = SendEncryptedMessage(pCurrentBuffer, lDataToSend);
      if (ss != SEC_E_OK)
        return ss;

      //Prepare for the next loop
      lDataRemaining -= lDataToSend;
#pragma warning(suppress: 26481)
      pCurrentBuffer += lDataToSend;
      bMoreDataToSend = (lDataRemaining > 0);
    }

    return SEC_E_OK;
  }

  virtual SECURITY_STATUS GetEncryptedMessage(_Inout_ std::vector<BYTE>& message)
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_lReadBufferLen != 0);
#pragma warning(suppress: 26477)
    ATLASSERT(m_ReadBuffer.data() != nullptr);

    //Setup the buffers
    std::array<SecBuffer, 4> InBuffers{};
    SecBufferDesc InBuffer{ SECBUFFER_VERSION, 4, InBuffers.data() };

    //Loop around calling DecryptMessage and calling ReadData as necessary
    SECURITY_STATUS status = SEC_E_INCOMPLETE_MESSAGE;
    ULONG cbData = m_lExtraReadData;
    BYTE* pbyData = m_ReadBuffer.data();
    do
    {
      //Setup the buffers with the received data
#pragma warning(suppress: 26446)
      InBuffers[0].BufferType = SECBUFFER_DATA;
#pragma warning(suppress: 26446)
      InBuffers[0].pvBuffer = pbyData;
#pragma warning(suppress: 26446)
      InBuffers[0].cbBuffer = cbData;
#pragma warning(suppress: 26446)
      InBuffers[1].BufferType = SECBUFFER_EMPTY;
#pragma warning(suppress: 26446)
      InBuffers[2].BufferType = SECBUFFER_EMPTY;
#pragma warning(suppress: 26446)
      InBuffers[3].BufferType = SECBUFFER_EMPTY;

      //If we have some data, try to decrypt it
      if (cbData != 0)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::GetEncryptedMessage, calling DecryptMessage with %u bytes of data\n"), cbData);

        status = DecryptMessage(&InBuffer);
      }

      //Get more data if required
      if (status == SEC_E_INCOMPLETE_MESSAGE)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::GetEncryptedMessage, Going to network to read more data because status is SEC_E_INCOMPLETE_MESSAGE\n"));

        const ULONG lReadBufferSize = m_lReadBufferLen - cbData;
        ULONG lReceived = 0;
#pragma warning(suppress: 26481)
        const SECURITY_STATUS status2 = ReceiveData(pbyData + cbData, lReadBufferSize, lReceived);
        if (status2 != SEC_E_OK)
        {
          //Do auditing if necessary
          if (m_dwAuditFlags & AUDIT_ERRORS)
            Audit(_T("CSSL::GetEncryptedMessage failed, Error:0x%08X\n"), status);

          return status2;
        }
        if (lReceived)
        {
          //Do auditing if necessary
          if (m_dwAuditFlags & AUDIT_DATA)
#pragma warning(suppress: 26481)
            AuditData(_T("CSSL::GetEncryptedMessage, received data"), pbyData + cbData, lReceived);

          cbData += lReceived;
        }
        else
        {
          //Do auditing if necessary
          if (m_dwAuditFlags & AUDIT_ERRORS)
            Audit(_T("CSSL::GetEncryptedMessage, A graceful disconnect occurred reading data\n"));

          return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_GRACEFUL_DISCONNECT);
        }
      }
      else if ((status != SEC_E_OK) && (status != SEC_I_RENEGOTIATE))
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_ERRORS)
          Audit(_T("CSSL::GetEncryptedMessage failed to decrypt data, Error:0x%08X\n"), status);

        return status;
      }
    }
    while (status == SEC_E_INCOMPLETE_MESSAGE);

    if (status == SEC_E_OK)
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_EVENTS)
        Audit(_T("CSSL::GetEncryptedMessage was successful\n"));

      //Locate the data buffer
      int nIndexDataBuffer = -1;
      for (int i=1; (i<4) && (nIndexDataBuffer == -1); i++)
      {
#pragma warning(suppress: 26446 26482)
        if (InBuffers[i].BufferType == SECBUFFER_DATA)
          nIndexDataBuffer = i;
      }
      if (nIndexDataBuffer == -1)
      {
        status = SEC_E_DECRYPT_FAILURE;

        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_ERRORS)
          Audit(_T("CSSL::GetEncryptedMessage failed to locate data buffer for message\n"));

        return status;
      }

      //Setup the message output parameter
#pragma warning(suppress: 26446 26482)
      const ULONG nMessageSize = InBuffers[nIndexDataBuffer].cbBuffer;
      message.resize(nMessageSize);
#pragma warning(suppress: 26446)
      memcpy_s(message.data(), nMessageSize, InBuffers[1].pvBuffer, nMessageSize);
    }

    if ((status == SEC_E_OK) || (status == SEC_I_RENEGOTIATE))
    {
      //Locate the extra buffer
      int nIndexExtraBuffer = -1;
      for (int i=1; (i<4) && (nIndexExtraBuffer == -1); i++)
      {
#pragma warning(suppress: 26446 26482)
        if (InBuffers[i].BufferType == SECBUFFER_EXTRA)
          nIndexExtraBuffer = i;
      }

      //Move the extra data into the read buffer ready for the next call to GetEncryptedMessage
      if (nIndexExtraBuffer != -1)
      {
#pragma warning(suppress: 26446 26482)
        m_lExtraReadData = InBuffers[nIndexExtraBuffer].cbBuffer;
#pragma warning(suppress: 26481)
        memcpy_s(m_ReadBuffer.data(), m_lReadBufferLen, pbyData + (cbData - m_lExtraReadData), m_lExtraReadData);

        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
#pragma warning(suppress: 26446 26482)
          Audit(_T("CSSL::GetEncryptedMessage, DecryptMessage returned %u bytes of additional unprocessed data\n"), InBuffers[nIndexExtraBuffer].cbBuffer);
      }
      else
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::GetEncryptedMessage, DecryptMessage returned no additional unprocessed data\n"));

        m_lExtraReadData = 0;
      }
    }

    return status;
  }

  virtual SECURITY_STATUS SendCloseNotify(_In_ bool bOperatingAsClient)
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials != nullptr);

    //Create the buffer description for the close notify
    DWORD dwType = SCHANNEL_SHUTDOWN;
    SecBuffer OutBuffers{ sizeof(dwType), SECBUFFER_TOKEN, &dwType };
    SecBufferDesc OutBuffer{ SECBUFFER_VERSION, 1, &OutBuffers };

    //Tell SSPI about our intent to shutdown
    SECURITY_STATUS status = ApplyControlToken(&OutBuffer);
    if (status != SEC_E_OK)
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::SendCloseNotify, ApplyControlToken failed, Error:0x%08X\n"), status);

      return status;
    }

    //Build the output buffer
    OutBuffers.BufferType = SECBUFFER_TOKEN;
    OutBuffer.cBuffers = 1;
    OutBuffer.pBuffers = &OutBuffers;
    OutBuffer.ulVersion = SECBUFFER_VERSION;

    //Work out the context request flags to use
    const unsigned long nContextRequest = bOperatingAsClient ? GetClientContextRequest() : GetServerContextRequest(false);

    unsigned long nContextRequestOut = 0;
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials->ValidHandle());
    if (bOperatingAsClient)
      status = Initialize(*m_pCachedCredentials, nullptr, nContextRequest, nullptr, &OutBuffer, &nContextRequestOut);
    else
      status = Accept(*m_pCachedCredentials, nullptr, nContextRequest, &OutBuffer, &nContextRequestOut);
    if (status != SEC_E_OK)
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::SendCloseNotify failed, Error:0x%08X\n"), status);

      return status;
    }

    //Send the close notify message to the other side
    if (OutBuffers.cbBuffer != 0)
    {
      //Validate our parameters
#pragma warning(suppress: 26477)
      ATLASSERT(OutBuffers.pvBuffer != nullptr);

      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_DATA) 
        AuditData(_T("CSSL::SendCloseNotify, Sending data"), static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);

      status = SendData(static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);

      //Free output buffer
      FreeContextBuffer(OutBuffers.pvBuffer);

      if (status != SEC_E_OK)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_ERRORS)
          Audit(_T("CSSL::SendCloseNotify failed to send data, Error:0x%08X\n"), status);

        return status;
      }
    }

    return status;
  }

  virtual bool Audit(_In_z_ _Printf_format_string_ LPCTSTR pszFormat, ...)
  {
    //Work out the size of the buffer we need
    va_list argptr;
#pragma warning(suppress: 26492 26481)
    va_start(argptr, pszFormat);
#pragma warning(suppress: 26486)
    const int nBufferLen = _vsctprintf(pszFormat, argptr) + 1;

    //Allocate the buffer for the string and write into it
    std::vector<TCHAR> auditBuffer(nBufferLen);
    _vstprintf_s(auditBuffer.data(), nBufferLen, pszFormat, argptr);

    //Audit the data via OutputDebugString
    OutputDebugString(auditBuffer.data());

#pragma warning(suppress: 26477)
    va_end(argptr);

    return true;
  }

  virtual bool AuditData(_In_ LPCTSTR pszTitle, _In_reads_bytes_(lSize) const BYTE* pbyData, _In_ ULONG lSize)
  {
    LPTSTR pszString = nullptr;
    std::vector<TCHAR> auditString;
    if (lSize != 0)
    {
      //First call to get the length of the buffer to allocate
      DWORD cchString = 0;
      if (!CryptBinaryToString(pbyData, lSize, CRYPT_STRING_HEXASCIIADDR, nullptr, &cchString))
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_ERRORS)
          Audit(_T("CSSL::AuditData, %s, failed call to CryptBinaryToString, Error:%u\n"), pszTitle, GetLastError());

        return false;
      }

      //Allocate enough memory and recall
      auditString.resize(cchString);
      if (!CryptBinaryToString(pbyData, lSize, CRYPT_STRING_HEXASCIIADDR, auditString.data(), &cchString))
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_ERRORS)
          Audit(_T("CSSL::AuditData, %s, failed call to CryptBinaryToString, Error:%u\n"), pszTitle, GetLastError());

        return false;
      }
      pszString = auditString.data();
    }

    //Audit the data via OutputDebugString
    OutputDebugString(pszTitle);
    OutputDebugString(_T("\n"));
    if (pszString != nullptr)
      OutputDebugString(pszString);
    else
      OutputDebugString(_T("<NULL>\n"));

    return true;
  }

  _NODISCARD unsigned long PendingReadSize() const noexcept
  {
      return m_lExtraReadData;
  }

protected:
//Methods
  virtual SECURITY_STATUS AllocateSendBuffer()
  {
    m_WriteBuffer.resize(0);
    m_lWriteBufferLen = 0;

    //Qeury for the size details
    const SECURITY_STATUS status = QueryAttribute(SECPKG_ATTR_STREAM_SIZES, &m_SecPkgSizes);
    if (status != SEC_E_OK)
      return status;

    //Allocate the buffer based on the size values returned
    const ULONG lWriteBufferLen = m_SecPkgSizes.cbHeader + m_SecPkgSizes.cbMaximumMessage + m_SecPkgSizes.cbTrailer;
    m_WriteBuffer.resize(lWriteBufferLen);
    m_lWriteBufferLen = lWriteBufferLen;

    return SEC_E_OK;
  }

  virtual SECURITY_STATUS AllocateReceiveBuffer()
  {
    m_ReadBuffer.resize(0);
    m_lReadBufferLen = 0;

    //Query for the size details for this SSPI
    PSecPkgInfo pSecInfo = nullptr;
#pragma warning(suppress: 26465 26486)
    const SECURITY_STATUS status = QuerySecurityPackageInfo(const_cast<LPTSTR>(UNISP_NAME), &pSecInfo);
    if (status != SEC_E_OK)
      return status;
    m_ReadBuffer.resize(pSecInfo->cbMaxToken);
    m_lReadBufferLen = pSecInfo->cbMaxToken;
    FreeContextBuffer(pSecInfo);

    return SEC_E_OK;
  }

  virtual SECURITY_STATUS ServerHandshakeLoop(_In_ unsigned long nContextRequest, _In_ bool bDoInitialRead)
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_lReadBufferLen != 0);
#pragma warning(suppress: 26477)
    ATLASSERT(m_ReadBuffer.data() != nullptr);
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials != nullptr);

    //Loop around calling AcceptSecurityContext until we are finished
    unsigned long nInputDataRead = m_lExtraReadData;
    std::array<SecBuffer, 2> InBuffers{};
    SECURITY_STATUS status = SEC_E_INCOMPLETE_MESSAGE;
    if (nInputDataRead || !bDoInitialRead)
      status = SEC_E_OK;
    do
    {
      //Read data from the client if we currently have an incomplete message or if we have just send some data to the cient because of a "SEC_I_CONTINUE_NEEDED"
      if ((status == SEC_E_INCOMPLETE_MESSAGE) || (status == SEC_I_CONTINUE_NEEDED))
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ServerHandshakeLoop, Going to network to read more data\n"));

        ULONG lReceived = 0;
#pragma warning(suppress: 26481)
        const SECURITY_STATUS status2 = ReceiveData(m_ReadBuffer.data() + nInputDataRead, m_lReadBufferLen - nInputDataRead, lReceived);
        if (status2 != SEC_E_OK)
        {
          //Do auditing if necessary
          if (m_dwAuditFlags & AUDIT_ERRORS)
            Audit(_T("CSSL::ServerHandshakeLoop, An error occurred reading data from the client, Error:0x%08X\n"), status2);

          //Release the context
          if (ValidHandle())
              Delete();

          return status2;
        }
        if (lReceived == 0)
        {
          //Do auditing if necessary
          if (m_dwAuditFlags & AUDIT_ERRORS)
            Audit(_T("CSSL::ServerHandshakeLoop, A graceful disconnect occurred reading data from the client\n"));

          //Release the context
          if (ValidHandle())
              Delete();

          return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_GRACEFUL_DISCONNECT);
        }

        if (m_dwAuditFlags & AUDIT_DATA)
#pragma warning(suppress: 26481)
          AuditData(_T("CSSL::ServerHandshakeLoop, received data from client"), m_ReadBuffer.data() + nInputDataRead, lReceived);

        nInputDataRead += lReceived;

        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ServerHandshakeLoop, Setting input data to %u\n"), nInputDataRead);
      }

      //Setup our input buffers
      InBuffers = { { { nInputDataRead, SECBUFFER_TOKEN, m_ReadBuffer.data() }, { 0, SECBUFFER_EMPTY, nullptr } } };
      SecBufferDesc InBuffer{ SECBUFFER_VERSION, 2, InBuffers.data() };

      //Setup our output buffers
      SecBuffer OutBuffers{ 0, SECBUFFER_EMPTY, nullptr };
      SecBufferDesc OutBuffer{ SECBUFFER_VERSION, 1, &OutBuffers };

      //Call AcceptSecurityContext (again)
      if (m_dwAuditFlags & AUDIT_EVENTS)
#pragma warning(suppress: 26446)
        Audit(_T("CSSL::ServerHandshakeLoop, Calling AcceptSecurityContext with %u bytes of input data\n"), InBuffers[0].cbBuffer);
      unsigned long nContextRequestOut = 0;
#pragma warning(suppress: 26477)
      ATLASSERT(m_pCachedCredentials->ValidHandle());
      status = Accept(*m_pCachedCredentials, &InBuffer, nContextRequest, &OutBuffer, &nContextRequestOut);

      //Send any data to the server which SSPI provided to us if required
      if ((status == SEC_E_OK) || (status == SEC_I_CONTINUE_NEEDED) || (FAILED(status) && (nContextRequestOut & ISC_RET_EXTENDED_ERROR)))
      {
        if (OutBuffers.cbBuffer != 0)
        {
          //Validate our parameters
#pragma warning(suppress: 26477)
          ATLASSUME(OutBuffers.pvBuffer != nullptr);

          //Do auditing if necessary
          if (m_dwAuditFlags & AUDIT_EVENTS)
            Audit(_T("CSSL::ServerHandshakeLoop, AcceptSecurityContext has provided %u bytes of data to send to client\n"), OutBuffers.cbBuffer);
          if (m_dwAuditFlags & AUDIT_DATA)
            AuditData(_T("CSSL::ServerHandshakeLoop, sending data to client"), static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);

          const SECURITY_STATUS status2 = SendData(static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);
          if (status2 != SEC_E_OK)
          {
            //Do auditing if necessary
            if (m_dwAuditFlags & AUDIT_ERRORS)
              Audit(_T("CSSL::ServerHandshakeLoop, An error occurred sending data to the client, Error:0x%08X\n"), status2);

            //Free output buffer
            FreeContextBuffer(OutBuffers.pvBuffer);
            OutBuffers.pvBuffer = nullptr;

            //Release the context
            if (ValidHandle())
                Delete();

            return status2;
          }
        }
      }

      //Free output buffer if required
      if (OutBuffers.pvBuffer != nullptr)
      {
        FreeContextBuffer(OutBuffers.pvBuffer);
        OutBuffers.pvBuffer = nullptr;
      }

      //Copy any data from the unprocessed extra bufer if provided back to the start of the read buffer
#pragma warning(suppress: 26446)
      if (InBuffers[1].BufferType == SECBUFFER_EXTRA)
      {
#pragma warning(suppress: 26446 26481)
        memcpy_s(m_ReadBuffer.data(), m_lReadBufferLen, m_ReadBuffer.data() + (nInputDataRead - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer);
#pragma warning(suppress: 26446)
        nInputDataRead = InBuffers[1].cbBuffer;

        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ServerHandshakeLoop, AcceptSecurityContext returned %u bytes of additional unprocessed data\n"), nInputDataRead);
        if (m_dwAuditFlags & AUDIT_DATA)
          AuditData(_T("CSSL::ServerHandshakeLoop, Additional unprocessed data"), m_ReadBuffer.data(), nInputDataRead);
      }
      else if (status != SEC_E_INCOMPLETE_MESSAGE)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ServerHandshakeLoop, resetting nInputDataRead to 0 because input buffers indicate no additional unprocessed data, status:%08X\n"), status);

        nInputDataRead = 0;
      }
    }
    while ((status == SEC_I_CONTINUE_NEEDED) || (status == SEC_E_INCOMPLETE_MESSAGE));

    if (status != SEC_E_OK)
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::ServerHandshakeLoop failed, Error:0x%08X\n"), status);

      //Release the context if the function failed
      if (ValidHandle())
        Delete();

      return status;
    }

    //Do validation of the client certificate if required
    if (m_bVerifyClientCertificate)
    {
      CryptoWrappers::CCertificate remoteCertificate;
      status = QueryAttribute(SECPKG_ATTR_REMOTE_CERT_CONTEXT, &remoteCertificate.m_pCert);
      if (status != SEC_E_OK)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ServerHandshakeLoop, Failed to obtain remote cert context, status:%08X\n"), status);

        //Release the context if the function failed
        if (ValidHandle())
            Delete();

        return status;
      }

#pragma warning(suppress: 26486)
      status = VerifyClientCertificate(remoteCertificate);
      if (status != SEC_E_OK)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ServerHandshakeLoop, VerifyClientCertificate call failed, status:%08X\n"), status);

        //Release the context if the function failed
        if (ValidHandle())
            Delete();

        return status;
      }
    }

    //If the "extra" input buffer contains data, this is encrypted application protocol layer stuff.
    //It needs to be saved and returned to the application layer so that it can be decrypt later using
    //the DecryptMessage method
#pragma warning(suppress: 26446)
    if (InBuffers[1].BufferType == SECBUFFER_EXTRA)
    {
#pragma warning(suppress: 26446 26481)
      memcpy_s(m_ReadBuffer.data(), m_lReadBufferLen, m_ReadBuffer.data() + (nInputDataRead - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer);
#pragma warning(suppress: 26446)
      m_lExtraReadData = InBuffers[1].cbBuffer;

      if (m_dwAuditFlags & AUDIT_EVENTS)
        Audit(_T("CSSL::ServerHandshakeLoop, AcceptSecurityContext has provided %u bytes of additional unprocessed data\n"), m_lExtraReadData);
      if (m_dwAuditFlags & AUDIT_DATA)
        AuditData(_T("CSSL::ServerHandshakeLoop, Additional unprocessed data"), m_ReadBuffer.data(), m_lExtraReadData);
    }
    else
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_EVENTS)
        Audit(_T("CSSL::ServerHandshakeLoop, AcceptSecurityContext has provided no additional unprocessed data\n"));

      m_lExtraReadData = 0;
    }

    if (m_dwAuditFlags & AUDIT_EVENTS)
      Audit(_T("CSSL::ServerHandshakeLoop handshake was successful\n"));
    return status;
  }

  virtual SECURITY_STATUS ClientHandshakeLoop(_In_ LPCTSTR pszServerName, _In_ unsigned long nContextRequest, _In_ bool bDoInitialRead)
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_lReadBufferLen != 0);
#pragma warning(suppress: 26477)
    ATLASSERT(m_ReadBuffer.data() != nullptr);
#pragma warning(suppress: 26477)
    ATLASSERT(m_pCachedCredentials != nullptr);

    //Loop around calling InitializeSecurityContext until we are finished
    unsigned long nInputDataRead = m_lExtraReadData;
    std::array<SecBuffer, 2> InBuffers{};
    SECURITY_STATUS status = SEC_E_INCOMPLETE_MESSAGE;
    if (nInputDataRead || !bDoInitialRead)
      status = SEC_E_OK;
    do
    {
      //Read data from the server if we currently have an incomplete message or if we have just send some data to the server because of a "SEC_I_CONTINUE_NEEDED"
      if ((status == SEC_E_INCOMPLETE_MESSAGE) || (status == SEC_I_CONTINUE_NEEDED))
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ClientHandshakeLoop, Going to network to read more data\n"));

        ULONG lReceived = 0;
#pragma warning(suppress: 26481)
        const SECURITY_STATUS status2 = ReceiveData(m_ReadBuffer.data() + nInputDataRead, m_lReadBufferLen - nInputDataRead, lReceived);
        if (status2 != SEC_E_OK)
        {
          //Do auditing if necessary
          if (m_dwAuditFlags & AUDIT_ERRORS)
            Audit(_T("CSSL::ClientHandshakeLoop, An error occurred reading data from the server, Error:0x%08X\n"), status2);

          return status2;
        }
        if (lReceived == 0)
        {
          //Do auditing if necessary
          if (m_dwAuditFlags & AUDIT_ERRORS)
            Audit(_T("CSSL::ClientHandshakeLoop, A graceful disconnect occurred reading data from the server\n"));

          return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_GRACEFUL_DISCONNECT);
        }

        if (m_dwAuditFlags & AUDIT_DATA)
#pragma warning(suppress: 26481)
          AuditData(_T("CSSL::ClientHandshakeLoop, received data from server"), m_ReadBuffer.data() + nInputDataRead, lReceived);

        nInputDataRead += lReceived;
      }

      //Setup our input buffers
      InBuffers = { { { nInputDataRead, SECBUFFER_TOKEN, m_ReadBuffer.data() }, { 0, SECBUFFER_EMPTY, nullptr } } };
      SecBufferDesc InBuffer{ SECBUFFER_VERSION, 2, InBuffers.data() };

      //Setup our output buffers
      SecBuffer OutBuffers{ 0, SECBUFFER_EMPTY, nullptr };
      SecBufferDesc OutBuffer{ SECBUFFER_VERSION, 1, &OutBuffers };

      //Call InitializeSecurityContext (again)
      if (m_dwAuditFlags & AUDIT_EVENTS)
#pragma warning(suppress: 26446)
        Audit(_T("CSSL::ClientHandshakeLoop, Calling InitializeSecurityContext with %u bytes of input data\n"), InBuffers[0].cbBuffer);
      unsigned long nContextRequestOut = 0;
#pragma warning(suppress: 26477)
      ATLASSERT(m_pCachedCredentials->ValidHandle());
      status = Initialize(*m_pCachedCredentials, nullptr, nContextRequest, &InBuffer, &OutBuffer, &nContextRequestOut);

      //Send any data to the server which SSPI provided to us if required
      if ((status == SEC_E_OK) || (status == SEC_I_CONTINUE_NEEDED) || (FAILED(status) && (nContextRequestOut & ISC_RET_EXTENDED_ERROR)))
      {
        if (OutBuffers.cbBuffer != 0)
        {
          //Validate our parameters
#pragma warning(suppress: 26477)
          ATLASSERT(OutBuffers.pvBuffer != nullptr);

          //Do auditing if necessary
          if (m_dwAuditFlags & AUDIT_EVENTS)
            Audit(_T("CSSL::ClientHandshakeLoop, InitializeSecurityContext has provided %u bytes of data to send to server\n"), OutBuffers.cbBuffer);
          if (m_dwAuditFlags & AUDIT_DATA)
            AuditData(_T("CSSL::ClientHandshakeLoop, sending data to server"), static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);

          const SECURITY_STATUS status2 = SendData(static_cast<const BYTE*>(OutBuffers.pvBuffer), OutBuffers.cbBuffer);
          if (status2 != SEC_E_OK)
          {
            //Do auditing if necessary
            if (m_dwAuditFlags & AUDIT_ERRORS)
              Audit(_T("CSSL::ClientHandshakeLoop, An error occurred sending data to the server, Error:0x%08X\n"), status2);

            //Free output buffer
            FreeContextBuffer(OutBuffers.pvBuffer);
            OutBuffers.pvBuffer = nullptr;

            return status2;
          }
        }
      }

      //Free output buffer if required
      if (OutBuffers.pvBuffer != nullptr)
      {
        FreeContextBuffer(OutBuffers.pvBuffer);
        OutBuffers.pvBuffer = nullptr;
      }

      //Copy any data from the unprocessed extra bufer if provided back to the start of the read buffer
#pragma warning(suppress: 26446)
      if (InBuffers[1].BufferType == SECBUFFER_EXTRA)
      {
#pragma warning(suppress: 26446 26481)
        memcpy_s(m_ReadBuffer.data(), m_lReadBufferLen, m_ReadBuffer.data() + (nInputDataRead - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer);
#pragma warning(suppress: 26446)
        nInputDataRead = InBuffers[1].cbBuffer;

        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ClientHandshakeLoop, InitializeSecurityContext returned %u bytes of additional unprocessed data\n"), nInputDataRead);
        if (m_dwAuditFlags & AUDIT_DATA)
          AuditData(_T("CSSL::ClientHandshakeLoop, Additional unprocessed data"), m_ReadBuffer.data(), nInputDataRead);
      }
      else if (status != SEC_E_INCOMPLETE_MESSAGE)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ClientHandshakeLoop, resetting nInputDataRead to 0 because input buffers indicate no additional unprocessed data, status:%08X\n"), status);

        nInputDataRead = 0;
      }
    }
    while ((status == SEC_I_CONTINUE_NEEDED) || (status == SEC_E_INCOMPLETE_MESSAGE) || (status == SEC_I_INCOMPLETE_CREDENTIALS));

    if (status != SEC_E_OK)
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::ClientHandshakeLoop failed, Error:0x%08X\n"), status);

      //Release the context if the function failed
      if (ValidHandle())
        Delete();

      return status;
    }

    //Do custom validation of the server certificate if required via the virtual method "VerifyServerCertificate"
    if (m_bVerifyServerCertificate)
    {
      //Convert server name to unicode if required
    #ifndef _UNICODE
      ATL::CA2W unicodeServerName(pszServerName);
      LPCWSTR pwszUnicodeServerName = unicodeServerName;
    #else
      LPCWSTR pwszUnicodeServerName = pszServerName;
    #endif //#ifndef _UNICODE

      CryptoWrappers::CCertificate remoteCertificate;
      status = QueryAttribute(SECPKG_ATTR_REMOTE_CERT_CONTEXT, &remoteCertificate.m_pCert);
      if (status != SEC_E_OK)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ClientHandshakeLoop, Failed to obtain remote cert context, status:%08X\n"), status);

        //Release the context if the function failed
        if (ValidHandle())
          Delete();

        return status;
      }
#pragma warning(suppress: 26486)
      status = VerifyServerCertificate(remoteCertificate, pwszUnicodeServerName, m_dwCertGetCertificateChainFlags, m_dwCertVerifyCertificateChainPolicyFlags);
      if (status != SEC_E_OK)
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_EVENTS)
          Audit(_T("CSSL::ClientHandshakeLoop, VerifyServerCertificate call failed, status:%08X\n"), status);

        //Release the context if the function failed
        if (ValidHandle())
          Delete();

        return status;
      }
    }

    //If the "extra" input buffer contains data, this is encrypted application protocol layer stuff. 
    //It needs to be saved and returned to the application layer so that it can be decrypted later using
    //the GetEncryptedMessage method
#pragma warning(suppress: 26446)
    if (InBuffers[1].BufferType == SECBUFFER_EXTRA)
    {
#pragma warning(suppress: 26446 26481)
      memcpy_s(m_ReadBuffer.data(), m_lReadBufferLen, m_ReadBuffer.data() + (nInputDataRead - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer);
#pragma warning(suppress: 26446)
      m_lExtraReadData = InBuffers[1].cbBuffer;

      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_EVENTS)
        Audit(_T("CSSL::ClientHandshakeLoop, InitializeSecurityContext returned %u bytes of additional unprocessed data\n"), m_lExtraReadData);
      if (m_dwAuditFlags & AUDIT_DATA)
        AuditData(_T("CSSL::ClientHandshakeLoop, Additional unprocessed data"), m_ReadBuffer.data(), m_lExtraReadData);
    }
    else
    {
      m_lExtraReadData = 0;

      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_EVENTS)
        Audit(_T("CSSL::ClientHandshakeLoop, InitializeSecurityContext has provided no additional unprocessed data\n"));
    }

    //Do auditing if necessary
    if (m_dwAuditFlags & AUDIT_EVENTS)
      Audit(_T("CSSL::ClientHandshakeLoop handshake was successful\n"));
    return status;
  }

  virtual SECURITY_STATUS VerifyClientCertificate(_In_ CryptoWrappers::CCertificate& /*clientCertificate*/) noexcept
  {
    //Note the default implementation does not do any real work.
    //Derived classes are free to do their own custom validation. To enable VerifyClientCertificate
    //to be called at runtime you should use SetVerifyClientCertificate(true)
    return SEC_E_OK;
  }

  virtual SECURITY_STATUS VerifyServerCertificate(_In_ CryptoWrappers::CCertificate& serverCertificate, _In_opt_ LPCWSTR pszServerName, _In_ DWORD dwCertGetCertificateChainFlags, _In_ DWORD dwCertVerifyCertificateChainPolicyFlags)
  {
    //Note the default implementation of VerifyServerCertificate does a comprehensive check of the server certificate.
    //Derived classes are free to do their own custom validation. Note that if you do want to do custom validation 
    //then you should probably turn of auto validation of the server certificate by Schannel by using the 
    //SCH_CRED_MANUAL_CRED_VALIDATION flag and then to enable VerifyServerCertificate to be called at runtime, 
    //you should use SetVerifyServerCertificate(true)

    //Verify the server name
    if ((pszServerName == nullptr) || (wcslen(pszServerName) == 0))
    {
      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_EVENTS)
        Audit(_T("CSSL::VerifyServerCertificate, Server name is NULL or empty\n"));

      return SEC_E_WRONG_PRINCIPAL;
    }

    //Work with a non const local copy of pszServerName as required by SSL_EXTRA_CERT_CHAIN_POLICY_PARA::pwszServerName
    ATL::CAtlStringW sLocalServerName(pszServerName);

    //Build the certificate chain
    CERT_CHAIN_PARA chainPara{};
    chainPara.cbSize = sizeof(chainPara);
    chainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
    std::array<LPCSTR, 3> rgszUsages{ szOID_PKIX_KP_SERVER_AUTH, szOID_SERVER_GATED_CRYPTO, szOID_SGC_NETSCAPE };
    constexpr const DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR);
    chainPara.RequestedUsage.Usage.cUsageIdentifier  = cUsages;
#pragma warning(suppress: 26465)
    chainPara.RequestedUsage.Usage.rgpszUsageIdentifier = const_cast<LPSTR*>(rgszUsages.data());
    CryptoWrappers::CChain chain;
#pragma warning(suppress: 26486)
    if (!CertGetCertificateChain(nullptr, serverCertificate.m_pCert, nullptr, serverCertificate.m_pCert->hCertStore, &chainPara, dwCertGetCertificateChainFlags, nullptr, &chain.m_pChain))
    {
      const SECURITY_STATUS status = GetLastError();

      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)  
        Audit(_T("CSSL::VerifyServerCertificate, An error occurred building the certificate chain, Error:0x%08X\n"), status);

      return status;
    }

    //Validate the certificate chain
    SSL_EXTRA_CERT_CHAIN_POLICY_PARA extraPolicyPara{};
    extraPolicyPara.cbStruct = sizeof(extraPolicyPara);
    extraPolicyPara.dwAuthType = AUTHTYPE_SERVER;
    extraPolicyPara.fdwChecks = dwCertVerifyCertificateChainPolicyFlags;
    extraPolicyPara.pwszServerName = sLocalServerName.GetBuffer();
    CERT_CHAIN_POLICY_PARA policyPara{};
    policyPara.cbSize = sizeof(policyPara);
    policyPara.pvExtraPolicyPara = &extraPolicyPara;
    CERT_CHAIN_POLICY_STATUS policyStatus{};
    policyStatus.cbSize = sizeof(policyStatus);
    if (!chain.VerifyPolicy(CERT_CHAIN_POLICY_SSL, &policyPara, &policyStatus))
    {
      const SECURITY_STATUS status = GetLastError();

      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::VerifyServerCertificate, An error occurred verifying the certificate chain, Error:0x%08X\n"), status);

      sLocalServerName.ReleaseBuffer();
      return status;
    }

    //Clean up our resources used
    sLocalServerName.ReleaseBuffer();

    //Check to see if the chain validated ok
    SECURITY_STATUS status = SEC_E_OK;
    if (policyStatus.dwError)
    {
      status = policyStatus.dwError;

      //Do auditing if necessary
      if (m_dwAuditFlags & AUDIT_ERRORS)
        Audit(_T("CSSL::VerifyServerCertificate, certificate validation failed, Error:0x%08X\n"), status);
    }

    //Check all the certificates in the chain for revocation if required
    if (m_bCheckServerCertificateRevocation)
    {
      std::vector<PCERT_CONTEXT> certsBuffer(chain.m_pChain->cChain);
      for (DWORD i=0; i<chain.m_pChain->cChain; i++)
#pragma warning(suppress: 26489 26465 26481 26446 26492)
        certsBuffer[i] = const_cast<PCERT_CONTEXT>(chain.m_pChain->rgpChain[i]->rgpElement[0]->pCertContext);
      CERT_REVOCATION_STATUS revStat{};
      revStat.cbSize = sizeof(revStat);
#pragma warning(suppress: 26490 26486)
      if (!CertVerifyRevocation(X509_ASN_ENCODING, CERT_CONTEXT_REVOCATION_TYPE, chain.m_pChain->cChain, reinterpret_cast<PVOID*>(certsBuffer.data()), CERT_VERIFY_REV_CHAIN_FLAG, nullptr, &revStat))
      {
        //Do auditing if necessary
        if (m_dwAuditFlags & AUDIT_ERRORS)
          Audit(_T("CSSL::VerifyServerCertificate, Failed to check revocation of certificate, Error:0x%08X\n"), revStat.dwError);

        status = revStat.dwError;
      }
    }

    return status;
  }

//Member variables
  unsigned long m_lReadBufferLen;
  std::vector<BYTE> m_ReadBuffer;
  unsigned long m_lWriteBufferLen;
  std::vector<BYTE> m_WriteBuffer;
  unsigned long m_lExtraReadData;
  SecPkgContext_StreamSizes m_SecPkgSizes;
  DWORD m_dwAuditFlags;
  bool m_bVerifyServerCertificate;
  bool m_bCheckServerCertificateRevocation;
  bool m_bVerifyClientCertificate;
  DWORD m_dwCertVerifyCertificateChainPolicyFlags;
  DWORD m_dwCertGetCertificateChainFlags;
  CCachedCredentials* m_pCachedCredentials;
};


//The class which provides a socket based implementation of SSL/TLS using SSPI
class CSocket : public CSSL
{
public:
//Constructors / Destructors
  CSocket()  noexcept : m_hSocket(INVALID_SOCKET),
                        m_dwReadTimeout(5000),
                        m_dwWriteTimeout(5000)
  {
  }

//Virtual methods
  SECURITY_STATUS SendData(_In_reads_bytes_(lSize) const BYTE* pbyData, _In_ ULONG lSize) noexcept override
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_hSocket != INVALID_SOCKET);

    //Loop around sending all the data down the socket
    ULONG nSent = 0;
    while (nSent != lSize) 
    {
      //Check for writability on the socket before we try to send
      const int nWritable = IsWritable(m_hSocket, m_dwWriteTimeout);
      if (nWritable == SOCKET_ERROR)
        return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, WSAGetLastError());
      else if (nWritable == 0)
        return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_TIMEOUT);

      //Send the rest of the data
#pragma warning(suppress: 26490 26481)
      const int nSentThisTime = send(m_hSocket, reinterpret_cast<const char*>(pbyData + nSent), lSize - nSent, 0);
      if (nSentThisTime == SOCKET_ERROR) 
        return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, WSAGetLastError());

      //Prepare for the next loop
      nSent += nSentThisTime;
    }

    return SEC_E_OK;
  }

  SECURITY_STATUS ReceiveData(_Out_writes_bytes_to_(lSize, lReceived) BYTE* pbyData, _In_ ULONG lSize, _Out_ ULONG& lReceived) noexcept override
  {
    //Validate our parameters
#pragma warning(suppress: 26477)
    ATLASSERT(m_hSocket != INVALID_SOCKET);

    //Initialize the output parameter
    lReceived = 0;

    //Check the socket for readability
    const int nReadable = IsReadible(m_hSocket, m_dwReadTimeout);
    if (nReadable == SOCKET_ERROR)
      return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, WSAGetLastError());
    else if (nReadable == 0)
      return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, ERROR_TIMEOUT);

    //Do the actual receive
#pragma warning(suppress: 26490)
    lReceived = recv(m_hSocket, reinterpret_cast<char*>(pbyData), lSize, 0);
#pragma warning(suppress: 26472)
    if (lReceived == static_cast<ULONG>(SOCKET_ERROR))
      return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, WSAGetLastError());

    return SEC_E_OK;
  }

//Non virtual methods
#pragma warning(suppress: 26434)
  void Attach(_In_ SOCKET hSocket) noexcept
  {
    m_hSocket = hSocket;
  }

#pragma warning(suppress: 26434)
  SOCKET Detach() noexcept
  {
    const SOCKET hSocket = m_hSocket;
    m_hSocket = INVALID_SOCKET;
    return hSocket;
  }

  static int IsReadible(_In_ SOCKET hSocket, _In_ DWORD dwTimeout) noexcept
  {
    //Check the socket for readability
    timeval timeout;
    timeout.tv_sec = dwTimeout / 1000;
    timeout.tv_usec = (dwTimeout % 1000) * 1000;
    fd_set fds;
    FD_ZERO(&fds);
#pragma warning(suppress: 26482 26446)
    FD_SET(hSocket, &fds);
    return select(0, &fds, nullptr, nullptr, &timeout);
  }

  static int IsWritable(_In_ SOCKET hSocket, _In_ DWORD dwTimeout) noexcept
  {
    //Check the socket for writability
    timeval timeout;
    timeout.tv_sec = dwTimeout / 1000;
    timeout.tv_usec = (dwTimeout % 1000) * 1000;
    fd_set fds;
    FD_ZERO(&fds);
#pragma warning(suppress: 26482 26446)
    FD_SET(hSocket, &fds);
    return select(0, nullptr, &fds, nullptr, &timeout);
  }

  void SetReadTimeout(_In_ DWORD dwReadTimeout) noexcept
  {
    m_dwReadTimeout = dwReadTimeout;
  }

  _NODISCARD DWORD GetReadTimeout() const noexcept
  {
    return m_dwReadTimeout;
  }

  void SetWriteTimeout(_In_ DWORD dwWriteTimeout) noexcept
  {
    m_dwWriteTimeout = dwWriteTimeout;
  }

  _NODISCARD DWORD GetWriteTimeout() const noexcept
  {
    return m_dwWriteTimeout;
  }

  SOCKET GetSocket() noexcept
  {
    return m_hSocket;
  }

protected:
//Member variables
  SOCKET m_hSocket;
  DWORD m_dwReadTimeout;
  DWORD m_dwWriteTimeout;
};


}; //namespace SSLWrappers

#endif //#ifndef __SSLWRAPPERS_H__
