The first thing that I do when I turn on my PC is to launch a terminal emulator and a tmux session. I live in the command line, surrounded by CLI tools and neovim . Over the years, I’ve crafted a bunch of shell scripts to help in my daily workflows and decided to write a short post to share some of them.
All my custom scripts begin with a comma (,
), a handy trick I adopted for quick access. By typing ,<TAB>
in the terminal, auto-completion instantly lists all of them, ensuring they don’t get mixed up with other executables present in $PATH
. Note that these scripts aren’t POSIX compliant; I use them exclusively on my machines which have recent versions of bash.
Random password generator
If you frequently need to churn out random passwords, tokens, or arbitrary strings, this is a must have. While graphical tools and online generators exist, I’ve always preferred the speed and convenience of the terminal. Typing ,randompassword
and copying its output is pretty fast.
$ ,randompassword
MFNc9vIC0qc5Abnan0
You can also choose the length of the string by passing it as the first argument:
$ ,randompassword 10
F541LkLsjj
The script makes use of two sources of entropy, /dev/urandom
and openssl rand
. By default I don’t use a big character set, only A-Z-a-z-0-9-_.
, however it can be changed easily in the script by modifying the arguments of tr
. Here’s the script:
#!/usr/bin/env bash
randompassword() {
local seed1 seed2 size
size=${1:-18}
seed1=$(tr -dc 'A-Z-a-z-0-9-_.' </dev/urandom | head -c "$((size * 2))")
seed2=$(openssl rand -base64 "$((size * 2))" | tr -dc 'A-Z-a-z-0-9-_.')
printf '%s\n\n' "$(fold -w1 <<<"${seed1}${seed2}${RANDOM}" | shuf | tr -d '\n' | head -c "$size")"
}
randompassword "$@"
However, When I need something more featureful, I use KeePassXC .
TLS certificates
Chain validation
Next script is a bit more complicated, I use it in order to quickly check the certificate chain of local certificates. It employs some awk
wizardry to parse a certificate in PEM format.
Here’s an example, I’ll firstly download a certificate chain and then check it with the script, note that openssl s_client -showcerts
already does a validation, to the best of my knowledge, there isn’t an inbuilt command for local certificate chain validation:
$ echo | openssl s_client -showcerts -connect aorith.github.io:443 > /tmp/cert.crt
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = "GitHub, Inc.", CN = *.github.io
verify return:1
DONE
$ ,certs_check-cert-chain /tmp/cert.crt
0: subject=C = US, ST = California, L = San Francisco, O = "GitHub, Inc.", CN = *.github.io
issuer=C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
1: subject=C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
issuer=C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
openssl verify: /tmp/cert.crt: OK
Here’s the full script:
#!/usr/bin/env bash
chain_pem="${1}"
if [[ ! -f "${chain_pem}" ]]; then
echo "Usage: $0 BASE64_CERTIFICATE_CHAIN_FILE" >&2
exit 1
fi
if ! openssl x509 -in "${chain_pem}" -noout 2>/dev/null; then
echo "${chain_pem} is not a certificate" >&2
exit 1
fi
awk -F'\n' '
BEGIN {
showcert = "openssl x509 -noout -subject -issuer"
append=0
}
/-----BEGIN CERTIFICATE-----/ {
printf "%2d: ", count
append=1
}
{
if (append == "1") {
cert=cert "\n" $0
}
}
/-----END CERTIFICATE-----/ {
append=0
printf "%s\n", cert | showcert
close(showcert)
cert=""
count++
}
' "${chain_pem}"
printf "\nopenssl verify: %s\n" "$(openssl verify -untrusted "${chain_pem}" "${chain_pem}")"
Private key verification
The script below automates the process of checking if a TLS certificate and a private key correspond to each other. It supports RSA and ECDSA key formats.
How to use: ,certs_check-cert-matches-key <certificate> <private_key>
:
$ ,certs_check-cert-matches-key cert.pem good.key
Format: ECDSA
Match! Certificate and key pair match.
$ ,certs_check-cert-matches-key cert.pem bad.key
Format: ECDSA
Mismatch! Certificate and key pair do not match.
Certificate public part: MD5(stdin)= 1075090c04ef326b12c20537aa2f9c69
Private Key public part: MD5(stdin)= cc3355454aa05eb32f15fa4c35e8ee1f
Here’s the script:
#!/usr/bin/env bash
check_cert_matches_key() {
local cert="$1"
local key="$2"
local cert_pub key_pub
if [[ ! -f "$cert" ]] || [[ ! -f "$key" ]]; then
echo "Usage: ${FUNCNAME[0]} <cert_path> <key_path>"
return 1
fi
# Calculate according to the key format
if openssl rsa -noout -text -in "$key" >/dev/null 2>&1; then
echo "Format: RSA"
cert_pub=$(openssl x509 -noout -modulus -in "$cert" | openssl md5)
key_pub=$(openssl rsa -noout -modulus -in "$key" | openssl md5)
elif openssl ec -noout -text -in "$key" >/dev/null 2>&1; then
echo "Format: ECDSA"
cert_pub=$(openssl x509 -noout -pubkey -in "$cert" | openssl ec -pubin -pubout 2>/dev/null | openssl md5)
key_pub=$(openssl ec -pubout -in "$key" 2>/dev/null | openssl md5)
else
echo "Unrecognized or unsupported key format."
return 1
fi
# Check if the public parts match
if [[ "$cert_pub" == "$key_pub" ]]; then
echo "Match! Certificate and key pair match."
return 0
else
echo "Mismatch! Certificate and key pair do not match."
echo "Certificate public part: $cert_pub"
echo "Private Key public part: $key_pub"
return 1
fi
}
check_cert_matches_key "$@"
Basic info and expiry date
When I need to verify the installed certificate of a domain, I use the following two commands:
- To retrieve the certificate’s complete information:
echo -n | openssl s_client -servername $domain -connect $domain:443 2>/dev/null
- To extract only the certificate’s validity dates:
echo -n | openssl s_client -servername $domain -connect $domain:443 2>/dev/null | openssl x509 -noout -dates
For convenience I use the following script which runs both commands:
#!/usr/bin/env bash
check_cert_for_domain() {
local Remote
[[ -n "$1" ]] || {
echo "Usage: ${FUNCNAME[0]} <domain> [ip-address-to-connect-to]"
return 1
}
[[ -n "$2" ]] && Remote="$2" || Remote="$1"
echo -e "$(tput setaf 3)$ echo -n | openssl s_client -servername $1 -connect ${Remote}:443 2>/dev/null$(tput sgr0)"
echo -n | openssl s_client -servername "${1}" -connect "${Remote}:443" 2>/dev/null
echo -e "$(tput setaf 3)$ echo -n | openssl s_client -servername $1 -connect ${Remote}:443 2>/dev/null | openssl x509 -noout -dates$(tput sgr0)"
echo -n | openssl s_client -servername "${1}" -connect "${Remote}:443" 2>/dev/null | openssl x509 -noout -dates
}
check_cert_for_domain "$@"
Here’s an example against aorith.github.io
:
$ ,certs_check-cert-for-domain aorith.github.io
The output will also print the individual openssl commands used:
$ echo -n | openssl s_client -servername aorith.github.io -connect aorith.github.io:443 2>/dev/null
CONNECTED(00000003)
---
Certificate chain
0 s:C = US, ST = California, L = San Francisco, O = "GitHub, Inc.", CN = *.github.io
i:C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Feb 21 00:00:00 2023 GMT; NotAfter: Mar 20 23:59:59 2024 GMT
1 s:C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1
i:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
v:NotBefore: Apr 14 00:00:00 2021 GMT; NotAfter: Apr 13 23:59:59 2031 GMT
...
Verify return code: 0 (ok)
...
$ echo -n | openssl s_client -servername aorith.github.io -connect aorith.github.io:443 2>/dev/null | openssl x509 -noout -dates
notBefore=Feb 21 00:00:00 2023 GMT
notAfter=Mar 20 23:59:59 2024 GMT
Parse unix timestamps
This little script parses UNIX timestamps and outputs the result as a JSON string. To convert a UNIX timestamp, simply call the script followed by the timestamp:
$ ,unixdate 1699935000
Output:
[
{
"timestamp": 1699935000
},
{
"tz": "Europe/Madrid",
"date": "mar 14 nov 2023 05:10:00 CET"
},
{
"tz": "UTC",
"date": "mar 14 nov 2023 04:10:00 UTC"
}
]
You can change the local timezone with the variable MYTZ
:
$ MYTZ="America/Los_Angeles" ,unixdate 1699935000
Output:
[
{
"timestamp": 1699935000
},
{
"tz": "America/Los_Angeles",
"date": "lun 13 nov 2023 20:10:00 PST"
},
{
"tz": "UTC",
"date": "mar 14 nov 2023 04:10:00 UTC"
}
]
Here’s the script:
#!/usr/bin/env bash
unixdate() {
local EXE timestamp json count
MYTZ="${MYTZ:-Europe/Madrid}"
EXE='command date'
command -v gdate >/dev/null 2>&1 && EXE='command gdate'
timestamp="${1:-}"
[[ -z $timestamp ]] && {
echo -n "Unix timestamp: "
read -r timestamp
}
json="[ { \"timestamp\": ${timestamp} } "
count=0
for _tz in "$MYTZ" "UTC" "${2:-}"; do
[[ -n "$_tz" ]] || continue
json="${json}, { \"tz\": \"${_tz}\", \"date\": \"$(TZ=${_tz} $EXE --date=@"${timestamp}")\" }"
count=$((count + 1))
done
jq . <<<"${json} ]"
}
unixdate "$@"