Managing Certificates from Tcl

Exclusive offer: get 50% off this eBook here
Tcl 8.5 Network Programming

Tcl 8.5 Network Programming — Save 50%

Build network-aware applications using Tcl, a powerful dynamic programming language

£18.99    £9.50
by Piotr Beltowski Wojciech Kocjan | July 2010 | Open Source

In this article by Wojciech Kocjan and Piotr Beltowski, authors of the book Tcl 8.5 Network Programming, we will see how to create a secure communication system, and make sure all certificates can be generated and transferred securely to remote systems before they can be fully authenticated within the infrastructure.

(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.

Tcl 8.5 Network Programming Build network-aware applications using Tcl, a powerful dynamic programming language
Published: July 2010
eBook Price: £18.99
Book Price: £30.99
See more
Select your format and quantity:

(For more resources on Tcl, see here.)

Client side

The next step is to create a client side of our application. It will contain two parts—a client for requesting a certificate and a test of whether issuing HTTPS requests using a valid certificate works afterwards. In order to make it easier to run both the server and client from the same directory and / or on the same computer, client stores all of its keys in the keys-client subdirectory. If both the server and client are run on the same computer, the client will copy the ca.crt file from the keys subdirectory. If not, it is needed to copy ca.crt from the keys directory on the machine where the server is running to the keys-client subdirectory on the machine where the client will be run.

We'll start creating client code by creating a command to retrieve a certificate. It accepts the hostname to connect to and the common name, and the e-mail to send.

First we will open a connection, set it to blocking and binary mode, and send a request to the server.

proc requestKey {hostname name email} {
set chan [tls::socket $hostname 9901]
fconfigure $chan -blocking 1 -translation binary
puts $chan "certificate $name $email"
flush $chan

Now, we read the result from the server and assign results to variables. If the server refused to create a certificate, we show an error.

gets $chan result
lassign [split $result] result keysize certsize
if {!$result} {
close $chan
error "Request to generate certificate denied"
} else {

If the server has created keys and certificates, we copy and save them in the keys-client directory. The common name is used as a base for the key and certificate filename. After that we close the connection to the server and return.

set fh [open [file join keys-client $name.key] w]
fconfigure $fh -translation binary
fcopy $chan $fh -size $keysize
close $fh

set fh [open [file join keys-client $name.crt] w]
fconfigure $fh -translation binary
fcopy $chan $fh -size $certsize
close $fh

close $chan
}
}

We can now move to initializing our client. We start by loading the tls and http packages, then set up the https protocol for http package and assign arguments passed to script as variables.

package require tls
package require http
http::register https 443 tls::socket

lassign $argv host name email

After this we check arguments that were passed. If either host or name is empty, we print out usage information on standard error and exit.

If only email is empty, we set it to $name@localhost, where $name is the common name passed as argument.

if {($host == "") || ($name == "")} {
puts stderr "Usage: [info script] host name ?email?"
exit 1
}

if {$email == ""} {
set email "$name@localhost"
}

Now we create filenames of the key and certificate, and check if a certificate file exists:

set keyfile [file join "keys-client" "$name.key"]
set certfile [file join "keys-client" "$name.crt"]

if {![file exists $certfile]} {

If it does not exist, we set up tls to use the CA certificate and require a valid certificate from the remote peer. Next, we invoke the previously created requestKey function, passing the host to connect to, the common name and e-mail.

We also print the status—either that we've successfully requested the keys or that the keys were already present on the filesystem.

tls::init \
-cafile "keys-client/ca.crt" \
-require true

requestKey $host $name $email
puts "SSL keys retrieved"
} else {
puts "SSL keys already present"
}

We now initialize tls again, using key, certificate and CA information, requiring the remote peer to have a valid certificate.

tls::init \
-cafile "keys-client/ca.crt" \
-keyfile $keyfile \
-certfile $certfile \
-require true

Now we send an HTTPS request to the remote host. This connection will leverage the newly retrieved key and certificate:

set token [http::geturl "https://${host}:9902/test"]

Finally we check the result from the HTTPS request. If it is an error or eof status, we print out the information. If the request succeeds, we print out information about response.

switch -- [http::status $token] {
error {
puts " ERROR: [http::error $token]"
}
eof {
puts " EOF reading response"
}
ok {
puts " OK; HTTP code: [http::ncode $token]"
puts " Response size: [http::size $token] bytes"
puts " Response: [http::data $token]"
}
}
http::cleanup $token

This concludes the code for a test client.

Testing our solution

Now that we have both client and server applications, we can run them in order to check if everything works. In order to run our example, we need to be sure that the openssl command can be run from the command line. A good test would be to run the version subcommand:

zoro@ubuntu:~$ openssl version

If we have not received an error, we can continue. Otherwise we need to make sure openssl libraries and openssl command are installed in our system, and that the directory containing the openssl command is present in our shell's path.

This is usually the case on Unix systems. On Windows machines, it might be necessary to add a directory such as C:\Program Files\OpenSSL\bin to your path. For example, you can do this by running:

path " C:\Program Files\OpenSSL\bin;%PATH%"

Of course, you will need to verify paths to your OpenSSL distribution first. After setting the path, you can verify if it is correct by running the openssl version command.

Assuming everything went fine we can move on to running the server. We can run it by using tclsh. For example:

zoro@ubuntu:~/tcl$ tclsh8.5 server.tcl

Then we can run the client. We need to specify the hostname and common name for the client:

zoro@ubuntu:~/tcl$ tclsh8.5 client.tcl 127.0.0.1 client1

Sample output from the client should be as follows:

OK; HTTP code: 200
Response size: 34 bytes
Response: Clock: 1262879775; Client: client1

For the purpose of this example, we'll assume we're running both on the same machine. If this is not the case, we need to copy keys/ca.crt from the machine where the server is run as keys-client/ca.crt on the machine where the client will be run. We also need to specify actual server IP address or a hostname instead of 127.0.0.1.

Also, in real world scenarios, a request for a new certificate might be queued in the system, and require system administrator to confirm that a new certificate should be created. In these cases, the additional step of checking those permissions should be done on the server for each request. The client would need to periodically check if the certificate has been generated and then retrieve it.

Another feature omitted from this example is handling expiration of certificates. While for this example all certificates are generated to expire in 10 years, usually CA is generated for a very long period of time; all other certificates should expire much faster, such as in one year. In these cases clients would need to keep track of when their certificate expires, and request a new one either before that happens or as soon as this has happened.

Summary

In this article, we saw how to create a secure communication system and manage certificates in TCL.


Further resources on this subject:


Tcl 8.5 Network Programming Build network-aware applications using Tcl, a powerful dynamic programming language
Published: July 2010
eBook Price: £18.99
Book Price: £30.99
See more
Select your format and quantity:

About the Author :


Piotr Beltowski

Piotr Beltowski is an IT and software engineer, co-author of several US patents and publications. Master of Science in telecommunications, his technical experience includes various aspects of telecommunication, like designing IP networks or implementing support of network protocols. Piotr's experience as Level 3 customer support lead makes him focused on finding simple solutions to complex technical issues.

His professional career includes working in international corporations such as IBM and Ericsson.

Wojciech Kocjan

Wojciech Kocjan is a system administrator and programmer with 10 years of experience. His work experience includes several years of using Nagios for enterprise IT infrastructure monitoring. He also has experience in large variety of devices and servers, routers, Linux, Solaris, AIX servers and i5/OS mainframes. His programming experience includes multiple languages (such as Java, Ruby, Python, and Perl) and focuses on web applications as well as client-server solutions.

Books From Packt

jQuery 1.4 Reference Guide
jQuery 1.4 Reference Guide

JSF 2.0 Cookbook
JSF 2.0 Cookbook

Spring Security 3
Spring Security 3

NetBeans Platform 6.9 Developer's Guide
NetBeans Platform 6.9 Developer's Guide

Plone 3 Multimedia
Plone 3 Multimedia

PrestaShop 1.3 Beginner's Guide
PrestaShop 1.3 Beginner's Guide

Hacking Vim 7.2
Hacking Vim 7.2

Moodle 1.9 for Teaching Special   Education Children (5-10): Beginner's Guide
Moodle 1.9 for Teaching Special Education Children (5-10): Beginner's Guide

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software