Chapter 13. SSL and Security

Previous chapters of this book have presented how to write networked applications in Tcl. However, they did not cover any of the security aspects in detail. While security on its own is a subject large enough for a separate book, we will focus only on some aspects of it.

Security is a very important aspect of any application, especially if it communicates over a network. When communication is made, we need to be sure that we know who the other peer is; this is called authentication. This can be achieved using usernames and passwords, authenticating using public / private key based encryption, and many other aspects. We also need to check that a specified user can perform specific actions. For example, only the administrator can maintain a system. This is called authorization.

The terms authentication and authorization are often confused, as from a user perspective, these usually mean the same. When checking our e-mail, providing a valid username and password causes both authentication and authorization. However, it is important to be able to differentiate between these terms:

  • Authentication is the ability to verify the peer we are talking to
  • Authorization is the ability to verify whether this peer can perform specified operation

Whenever we receive any information, we also need to be sure that it has not been modified in any way during the transmission, which is called the integrity of our data. When sending data over public networks, if our application is not protected against it, information might be modified on its path by malicious users. Securing such communication can be achieved by sending data over an encrypted, secure connection channel where it is not possible to modify the transmitted data.

In many cases, we also need to be certain that the data we send over the network cannot be intercepted on its route; for example, that sensitive information cannot be read by anyone except for recipient(s) of the message. Intercepting and listening to a communication by an unauthorized user and / or application is called eavesdropping.

It is very easy to ensure authentication and authorization using Secure Socket Layer (SSL). This is a standard for encrypted network connections that works on top of regular sockets and provides an encryption layer on top of it. SSL uses public keys for authenticating peers and making sure only authorized users and / or computers can communicate with each other. In this chapter, we'll learn how to use encrypted connections from Tcl and how to make sure we know the other side our application is communicating with.

This chapter also shows the basics of authorizing users, dealing with issues of roles and access to various functions. We'll show how to map this to public-key based authentication.

We'll also learn how to use Tcl's safe interpreter feature. This can come in handy in order to hide some of Tcl's features or limit access to an operating system's resources—for example, to disallow accessing file system. You will learn how to limit commands or the time an interpreter can spend running. The Tcl safe feature can also be used for creating a sandbox that will allow / disallow specific features based on access level.

Learning Secure Socket Layer

SSL offers a security layer for communications over networks. SSL itself is a very generic standard for encrypting any form of communication. It is usually used for securing network connections to prevent messages from being modified and / or eavesdropped. We'll use it for securing TCP sockets, and show how applications can become SSL-enabled and switch to a secure model of communication.

SSL is the most popular standard for encrypted network communication and is used for almost all protocols, such as WWW, e-mail, FTP, and Jabber. It has multiple implementations, including open source ones such as OpenSSL, available fromhttp:// www.openssl.org/.

SSL offers end-to-end encryption of communication. It uses private and public key pairs for each of the peers communicating over the network. Public keys may be signed by a certificate authority (CA). Usually it is an application and / or machine in the network that deals with signing keys, and is trusted by other elements of the infrastructure. Signed public keys are called certificates. They are signed by certificate authority and can be used for authenticating remote peers in a communication.

Certificates and CAs allow the remote peer to be securely identified—remote peer needs to provide a valid certificate, which is signed by a trusted element of the network (CA). All systems within a network have a certificate for CA itself. Upon any connection a certificate is checked as to whether it is signed by a valid CA. The certificate also provides information about the identity of remote peers, and because it is signed by CA, this data can be trusted.

There are different types of certificate authorities:

  • The first type of CAs are publicly available certificate authorities called root CAs. These are organizations that verify and sign certificates for other companies for use in the Internet. An example is secure HTTPS websites, whose certificates are signed by such root CAs.
  • Many companies and / or IT applications also use their own certificate authorities that are trusted by an entire infrastructure. Each part of the system can then use that system's CA certificate(s) to check if a certificate of a remote peer is valid. This concept is discussed in more detail later in this chapter.

SSL provides two ways in which communication and authentication is carried out:

  • The first one is that only the server provides a valid and signed certificate, in which case the server can be authenticated, but the client remains anonymous
  • The second possibility is for both the server and client to provide a valid certificate

Server-only authentication is used for web browsing and many client-server applications. This means that only validity of the server is checked—the client checks if the server certificate is valid, but there is no key-based check performed for the client. In this case, additional authentication is made at application level, for example, by providing an additional username / password over the encrypted channel to authorize the user using the application.

Authenticating both the client and server is often used for applications that only communicate with trusted peers. This is used in client-server applications that use certificates for authentication. This is the model that the remaining part of this chapter will focus on, as a majority of our examples are applications that need to communicate in a secure manner.

Using SSL from Tcl

In order to create an SSL-enabled client or server from Tcl, we need to use tls package. It provides SSL support over TCP connections as well as any other type of connection, such as Unix pipes. For the purposes of this book, we'll focus on TCP connections only.

Package tls is an open source project built on top of the OpenSSL package, and it's available from https://sourceforge.net/projects/tls/. The page provides both binary and source code distribution of the package. The package is also provided with ActiveTcl distributions, so using any of the examples from ActiveTcl will work without additional dependencies.

SSL-enabled TCP sockets in Tcl work in the same way as any other kind of connections. The only difference is that the tls::socket command needs to be used instead of socket. The command can be used to set up both client and server connections. Syntax of the command is the same as that of the socket command. The command also accepts additional options related to SSL keys and options, which are described later in this chapter.

For example, in order to connect to host 192.168.2.1 port 443 using SSL, we can do the following:

package require tls
set channel [tls::socket 192.168.2.1 443]

We can then use commands such as puts, gets, or read for accessing data.

SSL sessions begin with a handshake. At that point, encryption of algorithms along with certificate exchange takes place. From a Tcl perspective, this happens when we first try to write to a channel for a client or after a first read for a server. If an SSL handshake fails, then a read or write will fail. Those commands will also fail if the connection is terminated, which is the same as that for regular TCP sockets.

Setting up an SSL-enabled server is also the same as that with regular TCP sockets. All we need to run is:

package require tls
tls::socket server mycommand 8992

The preceding code will cause Tcl to listen for incoming SSL connections and invoke the mycommand command, just the same as for regular TCP connections. It will be passed a new channel name as the first argument, followed by a remote IP address and remote port.

The tls package accepts various parameters as name-value pairs that specify items such as key and certificate files, supported protocols, and authentication policies. There are two possibilities to specify SSL-related parameters:

  • We can specify them globally using tls::init command.
  • Another possibility is to specify them directly in tls::socket command. In this case, options will only apply to the specified socket. In case of server connections, those options will apply to all incoming connections on the specified port. Options provided to tls::socket always overwrite options specified using tls::init.

The most important options are -keyfile and -certfile, which are used to specify key and certificate files accordingly. These files are created either using the tls package itself or using additional tools such as the openssl command-line utility. Private key file usually has .key extension and certificate usually has .pem extension.

For example, in order to specify a globally used SSL key and certificate files, we can run the following:

tls::init -keyfile "/path/to/server.key" 
-certfile "/path/to/server.pem"

We can also specify and / or overwrite these for specific connections by running:

tls::socket server mycommand -keyfile "/path/to/other.key" 
-certfile "/path/to/other.pem" 8992

This will cause connections for this particular port to use specified keys regardless of the ones specified in init command.

When passing options using both tls::init and tls::socket, options provided to tls::socket overwrite ones passed to tls::init.

The options -cadir and -cafile allow for specifying a file or directory that contains information about certificate authorities (CAs). The first option specifies a directory that holds one or more CA certificates. The second option can be used to specify a single CA certificate to use. CA certificate files usually have .crt extension.

We can also specify whether to ask the remote peer for a certificate, and whether to only accept valid SSL certificates during handshake or not. The option -request is used to specify whether to request a certificate from remote peer during SSL handshake and it defaults to true.

The option -require, if enabled (that is, when specifying -require true), forces accepting only valid SSL certificates. It defaults to false. A valid certificate is a certificate signed by one or more trusted CAs. All connections not having a certificate signed by a trusted CA will be closed. Enabling this option also requires enabling the -request option and specifying either -cadir or -cafile.

The package tls also supports using password protected SSL keys. This is commonly used to improve security. The option -password can be used to specify a command to be run whenever OpenSSL will need to get a password. The command is run without additional parameters and should return a string that is then passed back to OpenSSL.

For example, to pass a pre-defined password we can do the following:

proc gettlspassword {} {
return "hardcodedpassword"
}
tls::init -keyfile $keyfile -certfile $certfile 
password [gettlspassword]

Options set using the tls::init command are combined with any other options passed when the socket is created. Note that the tls::init command should be called first. This is especially important for server sockets; for example, the following code will not work:

package require tls
tls::socket server mycommand 8992
tls::init -keyfile "/path/to/server.key" 
-certfile "/path/to/server.pem"

When creating the server socket, tls did not have any key and certificate specified. Even though this is specified later, tls did pick up the values at the time tls::socket was invoked. The next example is correct:

package require tls
tls::init -keyfile "/path/to/server.key" 
-certfile "/path/to/server.pem"
tls::socket server mycommand 8992

The preceding code differs by setting the tls default parameter before initializing the socket. At the time the server socket is created, key and certificate filenames are already set.

Generating self-signed certificates

As stated earlier, SSL needs to have a valid certificate either at just server, or at both the server and client. We will need to tell the tls package where a valid key and certificate is located.

There are multiple possibilities for creating and signing a certificate, which are described later in this chapter. For test purposes, it is enough to generate a self-signed certificate; this is a certificate that is signed by its own key.

The package tls offers a command for generating a self-signed key and certificate quickly. It is done using the tls::misc command and its req subcommand. It requires four parameters. The first one is the size of the key, in bits. It is recommended to use at least 1024 bits or 2048 bits, as a smaller amount of bits can be considered insecure. The second argument specifies a path to the private key file and the third specifies a path to the certificate file. The last argument is a list of name-value pairs that specify information included in the certificate.

Certificate information can contain one or more elements. The days attribute specifies the number of days a certificate should be valid; it defaults to 365. The serial attribute specifies the key's serial number and defaults to 0. Additional details about the key's validity period, expiration, and serial numbers are described in more detail later in this chapter.

Certificate information can also include one or more of the following attributes, which map to SSL certificate fields:

Attribute name

Description

C

Country; should be specified as two-letter country code; for example, US for United States of America, UK for United Kingdom, and PL for Poland

ST

State or province name, specified as full name; potential values for this field vary across countries

L

Locality name; for example, city

O

Organization name; for example, company name

OU

Organization Unit Name; for example, section / department name

CN

Common Name, varies depending on type of certificate

Email

E-mail address of system or application administrator

The field common name is often used to identify resource that owns the certificate. For example, for the majority of Internet applications, the common name is set to the hostname of the host we are connecting to, such as www.packtpub.com.

For many applications, the common name can be used to uniquely name a resource or client; for systems that use Globally Unique Identifier (GUID) for identifying resources, the common name can be the GUID of the system. This way, SSL-based authorization can be used to map a connection to a particular resource, application, or agent.

For example, in order to create a sample SSL key and certificate we can do the following:

set keyfile server.key
set certfile server.pem
if {![file exists $keyfile]} {
tls::misc req 1024 $keyfile $certfile 
[list CN "localhost" days 7300]
}

Now, we configure tls to use the newly generated certificate by calling:

tls::init -keyfile $keyfile -certfile $certfile

We can use the tls::status command to get SSL-specific information about a socket. The result is a list of key-value pairs that describe the remote peer. If an SSL handshake has not been done yet, the command returns an empty list. Otherwise, the following keys are returned:

Attribute name

Description

issuer

Information about certificate issuer

subject

Information about certificate subject

notBefore

Begin date for validity of this certificate

notAfter

Expiry date for this certificate

serial

Serial number of this certificate

cipher

Encryption used for communication

sbits

Number of bits used for session key

The fields issuer and subject are in the form of attribute=value, separated by comma(s). They contain the same fields as ones used for SSL certificate generation, except that emailAddress is used instead of Email. An example value for subject or issuer is:

emailAddress=root@localhost,CN=CertificateAuthority,O=Tcl Network Programming Book,L=Krakow,ST=Malopolskie,C=PL

Note

A complete example of using SSL-enabled sockets is located in the 01sockets directory in the source code examples for this chapter.

Setting up and using public key infrastructure

As our applications grow and include larger numbers of applications or devices that communicate within the system, we will need a consistent way to identify and trust other nodes in the system.

A very common solution is to use SSL certificates. The idea is that we create a trusted entity that is responsible for authenticating everyone else in the system.

We start off with a commonly trusted entity that is only used for signing other entities' certificates. This way any certificate that is signed by this entity is considered secure, and we trust whatever identity is stored in the certificate itself. This is what certificate authority is.

Identity is usually equal to the common name contained in the certificate itself. Naming for certificates is up to either the administrator or creator of the application. In the following examples, we're using identifiers of clients as the common name. For web applications, it has to be the hostname of the website. As a trusted entity is signing certificates, we assume that information in the certificate that is also signed can be trusted.

Throughout our example, we'll be performing the following steps:

  • Creating a key and self-signed certificate for certificate authority, which will be the trusted entity for entire infrastructure
  • Creating a key and certificate request for a sample server
  • Generating a certificate for the newly created server key—this step uses the certificate request from the previous step
  • Creating a key and certificate request for a sample client
  • Generating a certificate for the newly created client key—this step uses the certificate request from previous step

In order to create additional server and client keys, only the steps for generating the key and the request, as well as generating the certificates themselves, have to be performed. The key and self-signed certificate for certificate authority should be generated only once.

Each of the entities in the system will also need their own key and certificate. Each certificate should be signed by the certificate authority. We will need to generate certificate authority, keys and certificates signed by a previously created CA. We'll use openssl command from OpenSSL package for these actions. Its documentation can be found at http://www.openssl.org/docs/apps/openssl.html.

Easy RSA is a very good open source package for managing keys using certificate authority. It can be used to set up a CA infrastructure and can be found at http://openvpn.net/easyrsa.html. Even though we will not use the package itself, we'll be using an OpenSSL configuration file based on the one from Easy RSA package.

Setting up certificates manually

The first thing we need to do is to set up directories for our keys. We'll store all keys in a keys subdirectory. We'll also need an empty index.txt file and serial file with contents of 01, which is used by OpenSSL.

To do this, let's invoke the following commands:

root@ubuntu:/private/ssl/keys# mkdir keys
root@ubuntu:/private/ssl/keys# touch keys/index.txt
root@ubuntu:/private/ssl/keys# echo 01 >keys/serial

Note

The configuration file openssl.cnf should also be put in the main directory. It is located in the 02opensslconfig directory in the source code examples for this chapter.

Examples show these commands run as the administrative user, but all commands can be run as any user. The same applies to Microsoft Windows and MacOSX. It also applies to all examples for running openssl command, both automatically and manually.

The following shows the layout of files and directories:

Setting up certificates manually

The files ca.crt, ca.key, client1.crt, client1.key, server1.crt, server1.key are files that are created by commands shown in the following section.

Creating certificate authority

After creating the directory structure, we need to create a key and certificate for certificate authority. We'll use openssl and its req subcommand, which is used for creating and managing certificates.

For example, we can invoke the following command:

root@ubuntu:/private/ssl/keys# openssl req -days 3650 
-nodes -new -x509 -config openssl.cnf 
-keyout keys/ca.key -out keys/ca.crt

Generating a 1024 bit RSA private key
..................++++++
...........++++++
writing new private key to 'keys/ca.key'
-----
(...)
-----
Country Name (2 letter code) [PL]: <Enter>

State or Province Name (full name) [Malopolskie]: <Enter>

Locality Name (eg, city) [Krakow]: <Enter>

Organization Name (eg, company) [Tcl Network Programming Book]: <Enter>

Organizational Unit Name (eg, section) [SSL]: <Enter>

Common Name (eg, your name or your server's hostname) []:CertificateAuthority

Email Address []:

The same can be made in silent, non-interactive mode. For this purpose, it's necessary to create properly built openssl.cnf in which all listed parameters will be specified. Automating this process is described later in the chapter.

Parts of command output were omitted and are marked with (...) in command output. Commands and output we have entered are marked in bold. Whenever the openssl command asked for input and just leaving the value empty was required, an<Enter> text has been added.

The preceding command is invoked with various flags:

  • Specifying -days 3650 tells OpenSSL that the CA should be valid for 3650 days, which is roughly 10 years. The option -nodes disables DES encryption and also disables password protection of the CA certificate.
  • The option -new combined with -x509 causes OpenSSL to generate a new key and certificate. Without the latter option, it will generate a key along with certificate request.
  • The option -config specifies which configuration file to use. The options -keyout and -out specify paths of CA private key file and CA certificate file respectively.

All functionality of the openssl req command is documented at: http://www.openssl.org/docs/apps/req.html

As a result, the openssl req command invoked will create keys/ca.key containing private key, used by CA for signing additional keys. This file should be kept secret and users should not have access to it. The file keys/ca.crt is the certificate for this CA. It can be passed to users in order to verify the signature of additional keys.

We can also skip using the -nodes option and create a CA key that is password protected. This increases security of the CA key, but for the purposes of this chapter and ease of automation, we will not use password protected CA or keys.

Generating the CA key and certificate needs to be done only once regardless of the number of keys we want to generate.

Generating and signing keys

Now, we can create one or more keys for clients or other servers in the network. We will also sign these keys using the CA done in previous step.

Command for creating the key itself is similar to previous one:

root@ubuntu:/private/ssl/keys# openssl req -days 3650 
-nodes -new -config openssl.cnf 
-keyout keys/server1.key -out keys/server1.csr

Generating a 1024 bit RSA private key
..........................++++++
............................................++++++
writing new private key to 'keys/server1.key'
-----
(...)
-----
Country Name (2 letter code) [PL]: <Enter>

State or Province Name (full name) [Malopolskie]: <Enter>

Locality Name (eg, city) [Krakow]: <Enter>

Organization Name (eg, company) [Tcl Network Programming Book]: <Enter>

Organizational Unit Name (eg, section) [SSL]: <Enter>

Common Name (eg, your name or your server's hostname) []:server1
Email Address []:<Enter>

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []: <Enter>

An optional company name []: <Enter>

This will create a key and its certificate. Similar to previous example, parts of text were omitted and text we entered is marked in bold.

Options specified for the openssl req command are very similar to ones provided for generating the CA key and certificate. The only difference is that in this case we do not specify the -x509 option. This creates a certificate request instead of generating a certificate. This is needed, as the request is then signed by CA to create a properly signed certificate.

Now our new private key is created as keys/server1.key and an unsigned request in keys/server1.csr. The private key should be kept secret, similar to the ca.key file. The file server1.csr can be made public.

The next step is to sign the certificate as Certificate Authority. We will use the ca subcommand from openssl for this purpose.

The following is an example of signing a certificate:

root@ubunturouter:/private/ssl/keys# openssl ca 
-days 3650 -config openssl.cnf 
-extensions server 
-in keys/server1.csr -out keys/server1.crt
Using configuration from openssl.cnf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
countryName :PRINTABLE:'PL'
stateOrProvinceName :PRINTABLE:'Malopolskie'
localityName :PRINTABLE:'Krakow'
organizationName :PRINTABLE:'Tcl Network Programming Book'
organizationalUnitName:PRINTABLE:'SSL'
commonName :PRINTABLE:'server1'
Certificate is to be certified until Dec 28 14:17:48 2019 GMT (3650 days)
Sign the certificate? [y/n]:y

1 out of 1 certificate requests certified, commit? [y/n]y

Write out database with 1 new entries
Data Base Updated

Similar to previous examples, we have chosen a few of the options available using openssl command:

  • The option -days 3650 tells OpenSSL that the certificate should be valid for 3650 days.
  • The option -config specifies configuration file to use.
  • -in and -out specify paths of certificate request and output certificate respectively.
  • The option -extensions server specifies that the certificate should indicate that it can be used as a server. This is used whenever a client connects to verify if the remote peer is actually signed as a server.

Even though options do not specify a path to CA key and certificate, OpenSSL knows this as it is hardcoded in the openssl.cnf file.

All functionality of the openssl ca command is documented at: http://www.openssl.org/docs/apps/ca.html

Now a signed certificate has been created as keys/server1.crt. The file keys/server1.csr can now be deleted. After this, we can use it from within Tcl as follows:

tls::init -cafile keys/ca.crt 
-keyfile keys/server1.key -certfile keys/server1.pem

The text server1 in filenames should be replaced with the actual name of the key to use. All servers and clients should also include the -cafile option to allow for checking the certificate signature.

We can also add the -require true option to require a valid certificate from remote peers. This will require all peers to have a signed certificate in order to communicate with other systems.

The previous example created a server key and certificate using the -extensions server option. The procedure for creating certificates for a client is exactly the same, but signing a certificate should be done without this flag. For example:

root@ubuntu:/private/ssl/keys# openssl req -days 3650 
-nodes -new -config openssl.cnf 
-keyout keys/client1.key -out keys/client1.csr
Generating a 1024 bit RSA private key
..........................++++++
............................................++++++
writing new private key to 'keys/server1.key'
-----
(...)
-----
Common Name (eg, your name or your server's hostname) []:client1

Email Address []: <Enter>

(...)

Creating the key is exactly the same as before. The command for signing this key is the same except for removing -extensions server option:

root@ubunturouter:/private/ssl/keys# openssl ca 
-days 3650 -config openssl.cnf 
-in keys/client1.csr -out keys/client1.crt

(...)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y

Write out database with 1 new entries
Data Base Updated

Note

The configuration file used in preceding examples is located in the 02opensslconfig directory in the source code examples for this chapter.

In this example, we have created all keys and certificates from the same machine and in same directory. It is also possible to perform these steps on multiple machines. In this case, generation of CA keys and the certificate and the signing of all keys needs to be done on one machine. Generation of all other keys can be done on other machines. Then, the unsigned keys (files with .csr extension) should be passed to the machine where the CA key and certificate is kept. After signing and creating the certificate file (files with .crt extension), it should be passed back to the machine where the key was generated. There is no need to transfer private keys (files with .key extension) at any point so transfers of (.csr and .crt files) can be made using unencrypted channels.

An implementation of a safe mechanism for generating and signing certificates is shown later in this chapter.

Automating certificates from Tcl

The package tls does not offer functionality for creating anything other than self-signed certificates. However, we can automate invocations of openssl command from within Tcl in order to manage certificates automatically from within Tcl.

We'll use the configuration file from the previous example with slight modifications. We'll be using a feature of passing environment variables to specify data for OpenSSL. This way we can automatically generate certificates with specified information, without any interaction on standard input or standard output.

The command openssl accepts the -batch option that causes it to use default values from configuration, instead of reading them from standard input.

OpenSSL also allows values to be specified for some fields using environment variables by specifying $ENV::<variableName> in the openssl.cnf file, where<variableName> is the name variable and maps to $::env(<variableName>) in Tcl. We will use this mechanism to pass parameters for certificate information from Tcl.

This will allow us to automate certificate creation and and customize the information contained in each certificate. This is needed for new nodes in the system or when a certificate expires and a new one needs to be generated. Automating this will allow us to add in new clients to our network without any manual effort from the administrator, such as adding new computers to a managed system. It will also help if we decide that automated certificate creation and signing from Tcl takes a few steps to complete. First let's initialize the namespace and variables for keeping the configuration:

namespace eval sslkeys {}
set sslkeys::keydirectory "keys"
set sslkeys::confdirectory "conf"
set sslkeys::keysize 1024
set sslkeys::expires 3650
set sslkeys::openssl "openssl"

The variables keydirectory and confdirectory store paths to keys and the configuration directory, respectively. The keysize variable specifies the number of bits to use for each key, expires specifies the expiration period in days, and the openssl variable specifies the path to openssl binary.

Next we'll set up defaults for each certificate. It is a list of name-value pairs:

set sslkeys::defaults {
COUNTRY "PL"
PROVINCE "Malopolskie"
CITY "Krakow"
ORG "Tcl Network Programming Book"
OU "SSL"
CN "TBD"
EMAIL "root@localhost"
}

Attributes are similar to the ones used for generating certificates in Tcl.

For each key, we'll merge preceding defaults with additional information provided when generating a key. We can create a helper procedure to merge values and copy information to the environment variable:

proc sslkeys::_initArray {argslist} {
variable keydirectory
variable keysize
variable defaults

We'll map the ar array to the same variable in the previous stack frame. This can be implemented to use the ar array directly in commands that generate keys. We'll also combine defaults with arguments provided to the procedure using array set:

upvar 1 ar ar
array set ar $defaults
array set ar $argslist

Now we'll copy required values to environment variables. We'll also set up a key directory as well as key size.

foreach k {COUNTRY PROVINCE CITY ORG OU CN EMAIL} {
set ::env(KEY_$k) $ar($k)
}
set ::env(KEY_DIR) $keydirectory
set ::env(KEY_SIZE) $keysize
}

These environment variables are later used by the openssl binary to access information about the key and directories to use. Tcl variable ::env represents the operating system's environment variables.

We'll also create a helper function to clean up environment variables, which can be used after invoking OpenSSL. It will also remove *.old files, which are created as part of the signing certificates process and do not need to be kept.

proc sslkeys::_finiArray {} {
variable keydirectory
foreach k {
COUNTRY PROVINCE CITY ORG OU CN EMAIL DIR SIZE
} {
unset ::env(KEY_$k)
}
foreach g [glob -nocomplain -directory $keydirectory 
*.old *.OLD] {
catch {file delete -force $g}
}
}

We can now move to functions that will invoke openssl binary to perform actions.

The procedure we'll need to write is initializing certificate authority files if they are not there. We'll start by mapping needed namespace variables, and initializing the ar variable using the helper procedure recently mentioned. The command accepts any number of arguments, which are passed to _initArray command.

proc sslkeys::initialize {args} {
variable keydirectory
variable confdirectory
variable keysize
variable expires
variable openssl
_initArray $args

The first thing we check is whether the key directory exists at all. If it does not, we create it:

if {![file exists $keydirectory]} {
file mkdir $keydirectory
}

After this, we'll check if the file ca.key exists in the keys directory. If it exists, it means that CA has already been set up and there is no need to create those files:

if {![file exists [file join $keydirectory ca.key]]} {

If the file does not exist, we'll need to create the serial and index.txt files as before. These are used by OpenSSL to keep information about signed certificates.

set fh [open [file join $keydirectory serial] w]
puts $fh 01
close $fh
set fh [open [file join $keydirectory index.txt] w]
close $fh

Now we'll create a Tcl command that will run the openssl binary to create a CA key and certificate. We'll first build it as a Tcl list and evaluate it as the next step. This approach allows for logging and debugging which command will be run.

set command [list exec $openssl req -batch 
-days $expires -nodes -new -x509 
-keyout [file join $keydirectory ca.key] 
-out [file join $keydirectory ca.crt] 
-config [file join $confdirectory openssl.cnf] 
2>@1]
set result [eval $command]

We're using the exec command to run the openssl binary. We're also redirecting standard error to standard output using 2>@1 statement. This is because openssl writes additional information to standard error, and without this statement Tcl will throw an error that information was written to standard output.

Finally, we need to unset environment variables.

}
_finiArray
}

The final procedure is creating and signing a new certificate. It accepts the type of certificate—either client or server, the filename for keys, and arguments for certificate information. Certificate type is used to determine whether -extensions server should be passed to the openssl binary when signing the certificate. Filename is the base name for the key and certificate file to create.

We start by mapping the required namespace variables and initializing the ar array:

proc sslkeys::createAndSign {type filename args} {
variable keydirectory
variable confdirectory
variable expires
variable openssl
_initArray $args

Now we build a Tcl list for running the openssl binary to create the key and run it:

set command [list exec $openssl req -batch 
-days $expires -nodes -new 
-keyout [file join $keydirectory $filename.key] 
-out [file join $keydirectory $filename.csr] 
-config [file join $confdirectory openssl.cnf] 
2>@1]
set result [eval $command]

We then create a Tcl command for signing the new certificate.

set command [list exec $openssl ca -batch 
-days $expires 
-in [file join $keydirectory $filename.csr] 
-out [file join $keydirectory $filename.crt] 
-config [file join $confdirectory openssl.cnf] 
]

Next, if the certificate type is server, we also append the -extensions server option.

if {$type == "server"} {
lappend command -extensions server
}

We then append the 2>@1 statement and run the command:

lappend command 2>@1
set result [eval $command]

Finally, we remove the .csr file which was used to sign the certificate and clean up the environment variables:

file delete 
[file join $keydirectory $filename.csr]
_finiArray
}

This code can now be used to create a complete set of certificates for our system—starting with the CA certificate and providing both client and server keys. This can be used to create a communication system, which can authenticate each of the peers and ensure the security and integrity of communication.

In order to create a CA, server, and certificate key all we need to do is invoke the following commands:

sslkeys::initialize CN "CertificateAuthority"

The previous command will create our CA's key and certificate. Now we can create a certificate for a client which can only be used for outgoing connections:

sslkeys::createAndSign client client1 CN "client1"

We can also create a server certificate:

sslkeys::createAndSign server server1 CN "server1"

Now our client will have the client1.key and client.crt files ready. Our server will have server1.key and server1.crt available. These can now be used directly from the tls package.

It is always better to specify a meaningful value for the common name. Usually it'll also work in such way that the common name will be the system's identifier, which will also be the filename of the key. For examples in this chapter, we decided to use GUIDs and the common name was the same as the GUID generated by the client.

Using the code to generate SSL certificates is shown in more detail in the next section, which also shows how to automate the process of generating and assigning certificates to other parts of the system.

Managing certificates from Tcl

Now that we can 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. It could export services similar to those described in Chapter 11, TclHttpd in Client-Server Applications, or any other type of service.

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.

Note

The server code for this example is located in the server.tcl file in the 04keymgmt directory in the source code examples for this chapter.

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.

Note

The Code of client part of this example is located in the client.tcl file in the 04keymgmt directory in the source code examples for this chapter.

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
OpenSSL 0.9.8g 19 Oct 2007

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 FilesOpenSSLin to your path. For example, you can do this by running:

path " C:Program FilesOpenSSLin;%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.

SSL and performance issues

While SSL seems like a very good solution to all security aspects, we have to remember that it also introduces some drawbacks that we might need to take into account when creating software.

Using encrypted connections produces overhead. While it might seem minimal, it is worth remembering a few things about how using SSL for communication impacts it.

SSL generates a large overhead on both the client and server when a connection is established. This is usually a problem for the server side if a lot of connections are made; in these cases, it is a good idea to limit the number of connections.

One way to do this is to combine multiple requests into one, such as querying server for new tasks, if an update is available, and which modules should be loaded at once. Another option is to use the HTTP keep alive feature by using the -keepalive flag from the http package on the clients.

Encrypted communication itself also generates overhead; transferring things such as large files over an encrypted connection might be a bit slower than unencrypted files. Any modern computer will be able to keep up a transfer of several megabytes per second over SSL connections.

When a server is providing data over encrypted connection to multiple clients, the transfer rate might be much slower due to SSL overhead. This issue mainly applies to large environments, where big volumes of information need to be shared over encrypted networks. In many cases, it is also possible to create an instance of a web server just for providing files; either Tclhttpd or Apache configured for SSL handling.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.30.178