OpenSSL with Bash

Using the OpenSSL toolkit with Bash

By

Cryptography is an important part of IT security, and OpenSSL is a well-known cryptography toolkit for Linux. Experts depend on OpenSSL because it is free, it has huge capabilities, and it’s easy to use in Bash scripts.

OpenSSL makes use of standard input and standard output, and it supports a wide range of parameters, such as command-line switches, environment variables, named pipes, file descriptors, and files. You can take advantage of these features to quickly write Bash (Bourne-Again Shell) scripts that automate tasks, such as testing SSL/TLS (Secure Socket Layer/Transport Layer Security) connections, bulk conversions between different formats of cryptographic keys and certificates, batch signing/encrypting of files, auditing password protected files, and implementing or testing a PKI (Public Key Infrastructure).

The OpenSSL toolkit provides many modules that each perform a specific task. Each module is not a separate executable, but is, instead, selected with the first parameter of the openssl executable. On the other hand, each module has a separate manual page. For example, a module named x509 manages X.509 digital certificates and a module named pkcs12 manages PKCS12 packages.

To use x509, you should execute the following command:

openssl x509 -param1 param1value

but to see the manual page for it, you should type: man x509.

Testing SSL/TLS Connections

OpenSSL provides three modules that allow you to test SSL connections: s_client, s_server, and s_time. The first two, as the names suggest, are for simulating a client and a server in an SSL connection. The third one is for connection timing tests. I’ll start with a closer look at the s_client module.

S_client is particularly useful for checking which protocols and which ciphers the server agrees to use. This information is useful in security and functionality audits. For example, you could use this protocol information to find servers that don’t accept a legitimate protocol or cipher, thus preventing a legitimate client from connecting. You could also locate servers that accept weak protocols or ciphers and could thus allow a malicious attack. With a little help from Bash, you can fully automate this process.

Assume the client and the server hostnames are client and server, and that the server listens for SSL/TLS connections on port 443.

To check which protocols server accepts, you could use the following parameters: -ssl2, -ssl3, -tls1, -no_ssl2, -no_ssl3, or -no_tls1.

Because SSL2 is known to have security weaknesses, you can attempt to connect to the server using the following command:

openssl s_client -connect server:443 -no_ssl3 -no_tls1

If the server accepts any protocol other than SSL3 or TLS1, the preceding command opens a connection and waits for data. (Of course, this approach is not ideal if you plan to embed the command in a Bash script.) To close the connection immediately after establishing it, write to s_client‘s standard input:

echo "x" | openssl s_client -connect server:443 -no_ssl3 -no_tls1

Similarly, you can check allowed ciphers with the -cipher parameter. For user convenience, OpenSSL allows you to specify specific cipher suites (e.g., DES-CBC3-SHA) or groups of ciphers (e.g., LOW, MEDIUM, HIGH, NULL, ALL). Find group names and ciphers with man ciphers.

To check whether the server accepts connections using ciphers from group NULL or LOW, use the following:

echo "x" | openssl s_client -connect $server:443 -cipher NULL,LOW

In Bash scripts, it is a good idea to run OpenSSL modules with a specified timeout. Otherwise, when a hostname can’t be resolved, the script will hang for a long time. A special Linux utility will let you run any command with a timeout. Surprisingly, the utility is called timeout. For example, to check if an SSL2 connection can be established, but not wait for it longer than 10 seconds, use:

echo "x" | timeout 10 openssl s_client -connect server:443 -ssl2

Finally, to make the command more automatic, you can use the $? variable to check the return code of the last command executed by BASH. If the connection is established, OpenSSL returns 0. Listing 1 shows a simple example script with everything I have done so far.

Listing 1: Checking Permitted Protocols

01 #!/bin/bash
02 while read server ; do
03     timeout 3 openssl s_client -connect $server:443 -no_ssl3 -no_tls1
04     if [ $? -eq 0 ] ; then
05         echo $server >> bad_protocol.txt
06     fi
07     timeout 3 openssl s_client -connect $server:443 -cipher NULL,LOW
08     if [ $? -eq 0 ] ; then
09         echo $server >> bad_cipher.txt
10     fi
11 done

The script reads hostnames from standard input and checks if a connection other than SSL3 or TLS1 can be established on port 443. It waits no more than three seconds. Names of hosts that allow such connections are written to the file bad_protocol.txt. Similarly, the hosts that allow connections with NULL or LOW ciphers are listed in bad_cipher.txt.

Handling PEM/DER and PKCS12 Formats

A few formats and containers are used for public cryptography keypairs and digital certificates. Without getting into details, the most common formats for my network are PEM, DER, PKCS12, or JKS. From these, only the JKS format is not supported by the OpenSSL software. PEM and DER are encoding formats – PEM is a Base64-encoded format. DER is binary. PKCS12 is a container that can hold private and public keys, as well as signed certificates and certificates chains.

To convert between the PEM and DER file formats, you can use the -inform and -outform parameters. For example, to convert all X.509 certificates from PEM to DER, you can use the following loop:

for file in *.pem;
do openssl x509 -inform PEM -in $file -outform DER -out $file.der;
done

Another common task is extracting keys/certificates from a PKCS12 package, which is usually protected with a password. I can handle such an operation with the Bash and OpenSSL option -passin. This option allows me to specify passwords to access data in password-protected files in five ways. It is useful not only for PKCS12, but for every action that requires a password, for example, encrypted private keys or data.

First, I can specify a password as a pass:password_text, in which case password_text is the actual password. This is not a secure method, because the password is stored in Bash history and can be spotted with a ps command during execution. Second, I can specify the password with env:var. This method is more secure, because the password is held in the environment variable var. Another approach is to store the password as a file:pathname, which instructs OpenSSL to read the password from the first line of the file pathname. Or, I could use fd:number, which makes OpenSSL read the password from the file descriptor number. Finally, I can simply use stdin to read passwords from standard input.

Next, I’ll extract all certificates from password-protected PKCS12 files in a working directory and store them without password protection. This can be done with the following:

for file in *.p12; do
    openssl pkcs12 -in $file -passin file:$file.pass -nokeys -nodes -out $file.nokeys
done

I will assume I have a password for each PKCS12 file written in a file with the .pass extension.

Bulk Encrypting and Decrypting

Common cryptography tasks include encrypting and decrypting files. Symmetric cryptography uses one key for encrypting and decrypting. Asymmetric cryptography uses a public key for encrypting and a private key for decrypting (typically implemented with PKI and X.509 certificates).

Symmetric cryptography is faster than asymmetric cryptography, and it is a better choice when there is no need to provide public access to the key.

To encrypt the plain.txt file with symmetric cryptography and write the output cipher.enc, I can use the following command:

openssl [ciphername] -a -salt -in plain.txt -out cipher.enc

The system will prompt for an encryption password, which also has to be typed when decrypting later. It is not the best option for bulk operations, but I have already described several methods for specifying a password to OpenSSL.

Thus, to encrypt all .txt files in the current directory and write them to the ../enc directory with the aes-256-cbc cipher, I can use the following loop (assuming the password is written in the pass file):

for file in *.txt; do
    openssl aes-256-cbc -a -salt -in "$file" -out "../enc/$file" -passin file:pass
done

I can decrypt all .txt files in the current directory and write them to the ../dec directory with:

for file in *.txt; do
    openssl aes-256-cbc -d -a -salt -in "$file" -out "../dec/$file" -passin file:pass
done

again assuming that I have a password in the pass file.

OpenSSL and Standard Input/Output

Keeping with Unix philosophy, every argument that is passed with the -in parameter can be passed also using standard input. If I don’t specify the output with the -out parameter, it is written to standard output. Hence, I can use OpenSSL for processing outputs of other commands and generating inputs for other programs with a pipe. To check whether a certificate with the serial number 44A2FC741D8C1755 has been revoked, I can use the following command:

curl -s http://localhost/crl.pem | openssl crl -text -noout | grep "Serial Number: 44A2FC741D8C1755"

This command will retrieve a CRL (Certificate Revocation List) and decode it with OpenSSL. Next, it will grep for a serial number. Similarly, as with the previous Bash scripts, I can add a timeout and check the output of the grep command with the $? variable.

Auditing Encryption Passwords

A private key should almost always be secured with a password. Often, PKCS12 files are secured with passwords, too. With OpenSSL and Bash, I can do a quick check of the passwords used to protect those files. Assume I have a text file with the most common passwords, one in a line, called passwords.txt. I can check each password-protected file in the current directory with:

while read pass; do
  for file in *.p12; do
    openssl pkcs12 -in $file -noout -passin pass:$pass 2>/dev/null
    if [ $? -eq 0 ] ; then
        echo "Guessed password for $file: $pass"
    fi
done
done < passwords.txt

using each password from the passwords.txt file.

Testing PKI

The last major capability of OpenSSL is implementing a Public Key Infrastructure (PKI). A PKI often has a crucial role in security, and OpenSSL can be used to implement and test a PKI.

First, I can use OpenSSL to generate key pairs and corresponding CSRs (Certificate Signing Request). Second, I can sign CSRs, thus creating a valid certificates. Third, I can revoke and generate CRLs. Fourth, I can sign/encrypt and verify/decrypt. Finally, I can change parameters on the fly, manipulating values such as algorithms, key lengths, or DN content. I can then use this data as input for other applications. Listing 2 shows a few example functions I can use for testing various elements of a PKI.

Listing 2: Testing a PKI

01 function create_config {
02     {
03     echo "HOME            = ."
04     echo "RANDFILE        = $ENV::HOME/.rnd"
05 
06     ...
07     cut for better readability
08     ...
09 
10     echo "oid_section     = new_oids"
11     echo 'subjectKeyIdentifier=hash'
12     echo 'authorityKeyIdentifier=keyid,issuer'
13     echo 'proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo'
14     } > "$config"
15 }
16 
17 function create_root_ca {
18     local keysize=$1
19     local country=$2
20     local org=$3
21     local name=$4
22     local days=$5
23     local certfile=$6
24     local keyfile=$7
25     openssl req -newkey rsa:$keysize -x509 -days $days -keyout $keyfile -nodes -out $certfile -config $config -subj /C=$country/O=$org/CN=$name
26     return $?
27 }
28 
29 function create_crl {
30     local cakey=$1
31     local cacert=$2
32     local crlfile=$3
33     openssl ca -gencrl -config $config -keyfile $cakey -cert $cacert -out $crlfile
34 }
35 
36 function create_client_req {
37     local keysize=$1
38     local country=$2
39     local org=$3
40     local name=$4
41     local keyfile=$5
42     local reqfile=$6
43     openssl req -new -newkey rsa:$keysize -nodes -keyout $keyfile -out $reqfile -config $config -subj /C=$country/O=$org/CN=$name
44 }
45 
46 function sign_client_req {
47     local clientreq=$1
48     local days=$2
49     local cacert=$3
50     local cakey=$4
51     local clientcert=$5
52     openssl x509 -req -days $days -CA $cacert -CAkey $cakey -CAcreateserial -in $clientreq -out $clientcert
53 }
54 
55 function revoke_client_cert {
56     local clientcert=$1
57     local cakey=$2
58     local cacert=$3
59     openssl ca -revoke $clientcert -keyfile $cakey -cert $cacert -config $config
60 }
61 
62 function get_cacountry {
63     cacountry="DC"
64 }
65 
66 function get_caorg {
67     caorg="Dummy org"
68 }
69 
70 function get_caname {
71     caname="Dummy CA"
72 }

The create_config function has been cut for better readability. You can use the contents of your default OpenSSL configuration file for additional configuration settings. The configuration file is usually called openssl.cnf and placed in /etc.

By default, OpenSSL reads its configuration file from a specified location (usually /etc/openssl.cnf), but for my purposes, it is easier to create a config file on the fly. The script function create_config takes care of this by writing the configuration to the ./config file. Later, the file created by this function is pointed to OpenSSL with the -config parameter.

Next I have functions create_root_ca, create_crl, create_client_req, sign_client_req, and revoke_client_cert, the names of which are self-explanatory. All of these functions take parameters that specify things such as a DN (Distinguished Name) string, valid period, keysize, etc.

The main part of the script (not shown in the listing) could use the functions to generate a specified number of CA’s (Certification Authority) certificates and a specified number of client’s certificates for each CA. Also, I could revoke some client’s certificates right after generating. Thus the output of the script would be a bunch of CA certificates, revoked client certificates, and CRLs.

Summary

OpenSSL is a very flexible tool. Because you can specify all the necessary parameters using command-line switches, files, pipes, and environment variables, it is perfectly suited for Bash scripts.

This article described a few uses for OpenSSL, but bear in mind that this is only the tip of an iceberg. I encourage you to glance through the manual and experiment with your own ideas. Just don’t confuse somebody else’s private key with your own.

The Author

Marcin Teodorczyk has been passionate about computers and Linux for more than 14 years. He works with grid environments as an Information Security Officer, and in his spare time, he juggles.

Related content

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News