en-US/about_TLSAuthentication.help.txt

TOPIC
    about_tlsauthentication
 
SHORT DESCRIPTION
    On Windows, SSPI is used to setup a TLS connection and provides the API that
    programs use to perform the handshake. This is a similar role to OpenSSL,
    except the APIs follow a different format. This doc aims to provide some
    examples of how to use PSSPI to test out TLS connections.
 
CREDENTIAL TYPES
    There are 2 Schannel credential types that can be used with SSPI:
    + `SCHANNEL_CRED` - Retrieved with `Get-SchannelCredential`
    + `SCH_CREDENTIALS` - Retrieved with `Get-SCHCredential`
    The `SCHANNEL_CRED` is the legacy credential type and is deprecated in
    favour of `SCH_CREDENTIALS`. It can only use TLS 1.2 or lower and provides a
    limited way of controlling the cipher suites that are used in the
    connection. It operates on an allow list where the system defaults are used
    but when a protocols is specified it will only allow the ones specified to
    be used.
    The `SCH_CREDENTIALS` is the new credential type added with Windows 10 Build
    1809. It must be used if the client or server wishes to negotiate with TLS
    1.3. The ability to restrict protocols and cipher suites is a lot more
    robust than `SCHANNEL_CRED` and operates on a deny mechanism. This deny list
    is specified through TLS Parameters created by `New-TlsParameter` which each
    parameter can specify TLS protocols to restrict as well as crypto algorithms
    through the `-DisabledCrypto` parameter.
    Both credential types support the `-Flags` field which can be set to various
    flags that control the handshake behaviour. The `SCH_USE_STRONG_CRYPTO` can,
    and should, be specified so that Schannel restricts the default protocol and
    cipher suite list to stronger algorithms than what the default offers.
    The `Get-SCHCredential` cmdlet should be used for Schannel authentication
    attempts on any host that runs with Windows 10 Build 1809 or newer. The
    `Get-SchannelCredential` is only available for older hosts and support for
    it is limited.
 
CLIENT AUTHENTICATION
    TLS supports client authentication where the server requests the client to
    send an X.509 certificate to the server. The server can then inspect this
    certificate and work through any authorisation rules it needs to do. For the
    server to request the client to send a certificate it must be stepped
    through with the `ASC_REQ_MUTUAL_AUTH` ContextReq flag.
    Once the handshake is complete, it can retrieve the certificate send by the
    client by calling `Get-SecContextRemoteCert -Context $serverContext`. It is
    up to the server to then inspect the certificate and authorised the client
    identity.
 
AUTHENTICATION EXAMPLE
    The authentication phase is known as the TLS handshake and requires multiple
    calls to the SSPI API to complete. The exchange is quite complex but
    hopefully this example helps to illustrate what is needed.
 
    # The client will get its credentials and build the security context.
    # It is with Get-SCHCredential that restrictions can be placed on what TLS
    # protocols are used and other authentication options.
    $cCred = Get-SCHCredential
    $cCtx = New-SecContext -Credential $cCred
     
    # The server must provide a X509Certificate object when getting its
    # credentials. The example here uses a hardcoded thumbprint but the
    # cert provider can be used to scan the cert store and filter out the certs
    # needed dynamically.
    $thumbprint = '2B192AD47337A1DEEAFB087E51F6BB294F713671'
    $cert = Get-Item Cert:\LocalMachine\My\$thumbprint
    $sCred = Get-SCHCredential -CredentialUse SECPKG_CRED_INBOUND -Certificate $cert
    $sCtx = New-SecContext -Credential $sCred
     
    # There are multiple steps needed to complete the authentication phase. This
    # example has been tested with TLS 1.3 where the client and server both produce
    # two tokens that each other must process.
    $sRes = $null
    while ($true) {
        $stepParams = @{
            Context = $cCtx
            Target = "target-host" # Used for SNI and cert CN verification
            ContextReq = 'ISC_REQ_SEQUENCE_DETECT, ISC_REQ_REPLAY_DETECT, ISC_REQ_CONFIDENTIALITY, ISC_REQ_ALLOCATE_MEMORY, ISC_REQ_STREAM'
        }
     
        if ($sRes) {
            # On subsequent calls the input buffer contains the SECBUFFER_TOKEN
            # from the server plus a SECBUFFER_EMPTY.
            $stepParams.InputBuffer = @(
                $sRes.Buffers[0]
                'SECBUFFER_EMPTY'
            )
     
            # The output buffer contains the token, an alert buffer and finally
            # an empty buffer.
            $stepParams.OutputBuffer = @(
                'SECBUFFER_TOKEN',
                'SECBUFFER_ALERT',
                'SECBUFFER_EMPTY'
            )
        }
        else {
            # On the first call the input buffer must not be set. The output buffer
            # must be a SECBUFFER_EMPTY which SSPI will fill.
            $stepParams.OutputBuffer = 'SECBUFFER_EMPTY'
        }
     
        $cRes = Step-InitSecContext @stepParams
     
        # First call will be ContinueNeeded. The second call will be Ok but the
        # client may still need to process more data from the server. Only exit
        # the authentication loop when there's no more data to send to the server
        if ($cRes.Buffers[0].Length -eq 0) {
            break
        }
     
        # The server's input buffer is the SECBUFFER_TOKEN from the client and a
        # SECBUFFER_EMPTY token. The output buffer is simply the SECBUFFER_TOKEN.
        $stepParams = @{
            Context = $sCtx
            ContextReq = 'ASC_REQ_ALLOCATE_MEMORY'
            InputBuffer = @(
                $cRes.Buffers[0]
                'SECBUFFER_EMPTY'
            )
            OutputBuffer = 'SECBUFFER_TOKEN'
        }
        $sRes = Step-AcceptSecContext @stepParams
     
        # Exit the loop if there's no more data to send to the server
        if ($sRes.Buffers[0].Length -eq 0) {
            break
        }
    }
     
    if ($cRes.Result -ne [PSSPI.SecContextStatus]::Ok) {
        throw "Client context is not complete and there's no more server data: $($cRes.Result)"
    }
    if ($sRes.Result -ne [PSSPI.SecContextStatus]::Ok) {
        throw "Server context is not complete and there's no more client data: $($sRes.Result)"
    }