8 min read

(For more resources on Tcl, see here.)

Once we know how to create and sign certificates, the next step is to create a simple solution that would allow us to build an infrastructure using these keys. We’ll create a server that handles creating certificates for clients and exports a simple service over HTTPS.

The following is a diagram of how communication from the client will look like:

Managing Certificates from Tcl

Our example will be performing the following steps:

  1. The client requests a new key and certificate from the server; the client will check if the other side is valid by comparing the server’s certificate against certificate authority’s certificate, which the client needs to already have
  2. The server provides the client with new key and certificate
  3. The client can request information over HTTPS using newly created key and certificate
  4. The server will be able to authenticate the other side by checking if the certificate is signed by certificate authority

All further communication can use HTTPS as a proper key has been provided to the client.

Our example will have a server that can create a complete certificate authority, create, and sign its own key. It will also offer a HTTPS server that will only accept requests from clients using a valid certificate. We will use the recently mentioned code for managing CA and server / client keys.

Additionally, our server will provide an SSL-enabled server for clients to request certificates. This server will not require a valid certificate. This would allow any client to request a certificate that would then be used for communicating over HTTPS. This is needed because the HTTPS server will not allow any incoming connections without a valid certificate. We’ll create a dedicated service just for creating the key and a signed certificate that accepts connections without a valid certificate.

While the server will not be able to authenticate clients at this point, client will be able to use CA certificate to verify that a server can be trusted.

In a typical application, the client would start by requesting a new certificate if it does not have it. The HTTPS server would be used for communication, once the certificate has been issued.

In order to simplify the example, the protocol for requesting a certificate is very simple. A client connects over the SSL-encrypted socket. At this point the client does not have a valid certificate yet, so this will not be checked. It sends a single line specifying the command, common name, and e-mail address for the certificate. The server generates it and sends a response. It sends a single line containing a result, which is true or false, followed by the size of the key and certificate file in bytes. Next the key and certificate are sent as binary data. Since the client knows their sizes, it reads this back to key and certificate files. After retrieving a valid certificate, the client can now connect over HTTPS using a valid certificate and issue other commands.

In many cases, the infrastructure could also be extended to provide multiple servers. In this case, only one server would offer certificate creation—for both clients and servers. From then on communication could be made with all servers.

Server side

Let’s start by creating server side of our sample application. It will be built up from two things—a server for issuing certificates and the HTTPS server for invoking commands for clients with valid certificates. The server side of the application will store all keys and certificates in the keys subdirectory. It will keep CA key and certificate, its own key and certificate, and certificates of all other systems in the network. Although keeping all certificates is not necessary, it is used as a mechanism for detecting whether a specified client has already had its key generated or not.

First we’ll set up our server name, create a Certificate Authority, and create this server’s certificate, if it does not exist yet:

set server "server1"

sslkeys::initialize CN "CertificateAuthority"

set sslkey [file join $sslkeys::keydirectory $server.key]
set sslcert [file join $sslkeys::keydirectory $server.crt]
if {![file exists $sslkey]} {
sslkeys::createAndSign server $server CN $server
}

Next, we’ll set up a tls package to use these keys and that requires a valid certificate:

tls::init -keyfile $sslkey -certfile $sslcert
-cafile [file join $sslkeys::keydirectory ca.crt]
-require true

Now let’s set up the HTTPS server along with a very small application serving all requests:

package require tclhttpdinit
Httpd_SecureServer 9902

proc secureWebServerHandle {sock suffix} {
set certinfo [dict get [tls::status $sock] subject]
set client ""
foreach item [split $certinfo ,/] {
if {[regexp "^CN=(.*)$" $item - client]} {
break
}
}
set text "Clock: [clock seconds]; Client: $client"
Httpd_ReturnData $sock text/plain $text}

Url_PrefixInstall / secureWebServerHandle

Our server will listen for HTTPS requests on port 9902 and return information about the current time and client identifier, extracted from the SSL certificate. Since we require the certificate to be signed by a valid CA, we can assume that we can trust its value.

We can now proceed to creating code for requesting certificates. We’ll start by setting up an SSL-enabled server socket that, as an exception from the tls::init invocation shown in the preceding code, does not require a valid SSL certificate. It will listen on port 9901 and run the certRequestAccept command for each new connection:

tls::socket -require false -server certRequestAccept
9901

Whenever a connection comes in, we configure the channel as non-blocking, set up binary translation, and set up an event each time it is readable:

proc certRequestAccept {chan host port} {
fconfigure $chan -blocking 0 -buffering none -translation binary
fileevent $chan readable [list certRequestHandle $chan]
}

Every time a channel is readable, our command will try to read a line from it. If it fails, we close the channel and do nothing:

proc certRequestHandle {chan} {
if {[catch {gets $chan line} rc]} {
catch {close $chan}
} else {

If reading a line did not produce an error, we proceed with checking whether the end of the file has been reached for this channel or not. If it has, we also close it:

set eof 1
catch {set eof [eof $chan]}
if {$eof} {
catch {close $chan}
} else {

Otherwise we check if a line has been read successfully. The variable rc stores whatever the gets command returned; if a complete line has been read, it will contain the number of characters read. Otherwise it will be set to 1:

if {$rc < 0} {
return
}

If reading the line succeeds, we split the text on each white space to a list and assign each element of the list to variables. The first one is the command, only certificate being supported, followed by the common name and e-mail values to be used in certificate.

set line [split $line]
lassign $line command commonName email
if {$command != "certificate"} {
close $chan
return
}

We’ll also use the common name as the filename for the keys. Usually common names would be identifiers used throughout the system, such as GUIDs.

The next step is to create a full path to the destination key and certificate files, and check if the certificate file exists:

set keyfile [file join
$sslkeys::keydirectory $commonName.key]
set certfile [file join
$sslkeys::keydirectory $commonName.crt]

if {[file exists $certfile]} {

If it exists, a client with this identifier has already requested this certificate. In this case, we send information that we refused to create a certificate and close the channel.

if {[catch {
puts $chan "false 0 0"
flush $chan
close $chan
}]} {
catch {close $chan}
}
} else {

If a certificate has not been created yet, we create it and get the size of both files.

sslkeys::createAndSign client $commonName
CN $commonName EMAIL $email
set keysize [file size $keyfile]
set certsize [file size $certfile]

Then, we send a response to the client specifying that a certificate has been generated and the size of both files.

if {[catch {
puts $chan "true $keysize $certsize"

The next step is to send the contents of both files and flush the channel to make sure it gets sent:

set fh [open $keyfile r]
fconfigure $fh -translation binary
fcopy $fh $chan
close $fh
unset fh

set fh [open $certfile r]
fconfigure $fh -translation binary
fcopy $fh $chan
close $fh
unset fh

flush $chan

If writing the response produced an error, we assume that unless all data was sent, an error might have occurred. We close the socket and remove both keys and the certificate file.

}]} {
catch {close $fh}
catch {close $chan}
catch {file delete -force $keyfile}
catch {file delete -force $certfile}
} else {

We also try to close the file handle first—if an operation such as fcopy failed, the handle might have been left open and we need to close the file handle in order to delete it.

If the operation succeeds, we close the connection and only remove the private key. The certificate is left on the server for reference purposes, and for checking if it has already been passed to a client.

catch {close $chan}
file delete -force $keyfile
}
}
}
}
}

Finally we need to enter Tcl’s event loop:

vwait forever

We now have a complete server that allows the creation of SSL keys and certificates, and offers functionality over HTTPS protocol only to authenticated peers.

LEAVE A REPLY

Please enter your comment!
Please enter your name here