Version 2 of TLS server using TWAPI

Updated 2020-12-21 13:59:34 by oehhar

The following short example from miguel demonstrates the use of TWAPI for communicating over SSL/TLS on Windows.

#
# Create [ssocket], an ssl-enabled clone of the [socket] command
#
# The credentials should be in cert.pfx in the same folder as this script, the
# file cert.pwd should contain the password.
#
# At the bottom of the file we create an echo-server using ssocket.
#

apply [list {} {
    set base [file normalize [file dir [info script]]]
    set fbase [file join $base cert]
    set ::auto_path [linsert $::auto_path 0 [file join $base twapi-bin]]

    if {![catch {puts "twapi [package require twapi_crypto]"}] \
            && [file exists $fbase.pfx]} {
        # adapted from tls_init_echo_server_creds
        # http://sourceforge.net/p/twapi/code/ci/default/tree/twapi/tests/tlsecho.tcl
        
        set init_creds [list fbase {
            set f [open $fbase.pfx rb]
            set pfx [read $f]
            close $f
            set f [open $fbase.pwd r]
            set pwd [gets $f]
            close $f

            # Set up the store containing the certificates
            set certStore [cert_temporary_store -pfx $pfx \
                               -password [conceal $pwd]]

            # Set up the client and server credentials
            # Note: the substring should identify your certified url
            set serverCert [cert_store_find_certificate $certStore \
                                subject_substring hunter]
            set creds [sspi_schannel_credentials \
                           -certificates [list $serverCert] \
                           -protocols [list ssl3 tls1.1 tls1.2]]
            set creds [sspi_acquire_credentials \
                           -credentials $creds \
                           -package unisp -role server]
            cert_release $serverCert
            cert_store_release $certStore

            return $creds
        } ::twapi]
        
        interp alias {} ssocket {} ::twapi::tls_socket \
            -credentials [::apply $init_creds $fbase]
        
    } else {
        return -code error "NO TLS"
    }
}]

##################
## The echo server
##################

# An echo server - just to test the tls connection

proc answer {chan h p} {
    if {[set request [gets $chan]] eq {}} return
    puts $chan "Received request: '$request'"
    close $chan
}

set port 1234
ssocket -server answer $port
vwait forever

HaO 2020-08-19: Works like a charm for me. Some observations when adapting TCLTLS in Embedded TCL Web Server for TWAPI:

To convert a pair of PEM certificates, OPENSSL may be used to get the PFX file

openssl pkcs12 -inkey private.pem -in public.pem -export -out privatepublic.pfx

Find certificate

For me, the following command included in the upper example

cert_store_find_certificate $certStore subject_substring hunter

returned the empty string to say that the certificate was not found.

I don't know what a substring in this context is. I only had a common name and replacing "hunter" by my common name did not succeed. Searching any certificate did the job for me:

cert_store_find_certificate $certStore any

bgerror call on TLS negociation

If anything goes wrong within TLS negociation, in some situations a bgerror is called. This is due to the way, the stacked channel subsystem handles those errors.

A typicall error example is a socket close while negociating. Thats my typical test case: just open a channel and close it.

The error trace is as follows:

% set errorInfo
Unexpected EOF during TLS negotiation (NEGOTIATING)
    while executing
"throw {TWAPI TLS NEGOTIATE EOF} "Unexpected EOF during TLS negotiation (NEGOTIATING)""
    (procedure "_negotiate2" line 16)
    invoked from within
"_negotiate2 $chan"
    (procedure "rethrow" line 2)
    invoked from within
"rethrow"
    invoked from within
"trap {
        _negotiate2 $chan
    } onerror {} {
        variable _channels
        if {[info exists _channels($chan)]} {
            if {[dict get..."
    (procedure "_negotiate" line 3)
    invoked from within
"_negotiate $chan"
    (procedure "::twapi::tls_background_error" line 9)
    invoked from within
"::twapi::tls_background_error $result $ropts"
    (procedure "_negotiate_from_handler" line 22)
    invoked from within
"_negotiate_from_handler $chan"
    (procedure "::twapi::tls::_so_read_handler" line 7)
    invoked from within
"::twapi::tls::_so_read_handler rc1"

% set errorCode
TWAPI TLS NEGOTIATE EOF

This is annoying for me, as my application opens an error box, but the error is more on the other side of the channel. I would prefer an error, I may clearly attribute to the socket and handle it in a different way than general background errors.

In consequence, I am filtering those errors by the error code:

interp bgerror . myBGError
proc myBGError {errorMsg errorDict} {
        set errorCode [dict get $errDict -errorcode]
        if {[string match "TWAPI TLS *" $errorCode]} {
                # do background report
        } else {
                tk_messageBox -message "Error $errorMsg"
        }

TWAPI 4.5.2 has a new command prefix, that those errors may be handled in a particular way. I use that to be sure that the error code starts with "TWAPI TLS":

if {0 != [llength [info commands twapi::tls_background_error]]} {
        proc ::twapi::tls_background_error {result ropts} {
                if {![string match "TWAPI TLS *" [dict get $ropts -errorcode]]} {
                        dict set ropts -errorcode\
                                        [concat {TWAPI TLS} [dict get $ropts -errorcode]]
                }
                return -options $ropts $result
        }
}