OpenSSL with Bash
Using the OpenSSL toolkit with Bash
ByCryptography 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.
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](https://www.linux-magazine.com/var/linux_magazin/storage/images/media/linux-magazine-eng-us/images/misc/learn-more/834592-1-eng-US/Learn-More_medium.png)
News
-
NVIDIA Released Driver for Upcoming NVIDIA 560 GPU for Linux
Not only has NVIDIA released the driver for its upcoming CPU series, it's the first release that defaults to using open-source GPU kernel modules.
-
OpenMandriva Lx 24.07 Released
If you’re into rolling release Linux distributions, OpenMandriva ROME has a new snapshot with a new kernel.
-
Kernel 6.10 Available for General Usage
Linus Torvalds has released the 6.10 kernel and it includes significant performance increases for Intel Core hybrid systems and more.
-
TUXEDO Computers Releases InfinityBook Pro 14 Gen9 Laptop
Sporting either AMD or Intel CPUs, the TUXEDO InfinityBook Pro 14 is an extremely compact, lightweight, sturdy powerhouse.
-
Google Extends Support for Linux Kernels Used for Android
Because the LTS Linux kernel releases are so important to Android, Google has decided to extend the support period beyond that offered by the kernel development team.
-
Linux Mint 22 Stable Delayed
If you're anxious about getting your hands on the stable release of Linux Mint 22, it looks as if you're going to have to wait a bit longer.
-
Nitrux 3.5.1 Available for Install
The latest version of the immutable, systemd-free distribution includes an updated kernel and NVIDIA driver.
-
Debian 12.6 Released with Plenty of Bug Fixes and Updates
The sixth update to Debian "Bookworm" is all about security mitigations and making adjustments for some "serious problems."
-
Canonical Offers 12-Year LTS for Open Source Docker Images
Canonical is expanding its LTS offering to reach beyond the DEB packages with a new distro-less Docker image.
-
Plasma Desktop 6.1 Released with Several Enhancements
If you're a fan of Plasma Desktop, you should be excited about this new point release.