How to determine SSL cert expiration date from a PEM and send e-mail notification 7 days before expiring? - bash

for pem in $location; do
printf '%s: %s\n' \
certexpire=$(date -d "$(: | openssl x509 -enddate -noout -in "$pem"|cut -d= -f 2)" --iso-8601) \
"$pem"
done | sort
Tried this, but not sure how to take forward to set email notifications.

Related

Why is my key not found when sending a JWT assertion to Azure AD?

I'm trying to write a bash script to get access tokens from Microsoft for my app registered in AD. I can't seem to get past this error though:
"error":"invalid_client","error_description":"AADSTS700027: The certificate with identifier used to sign the client assertion is not registered on application. [Reason - The key was not found., Thumbprint of key used by client: '356134 ...
The full gist can be found here: https://gist.github.com/smaring/3a3a6779a809beecc39624aada6e2b88
Here are some of the juicy bits ...
$ openssl pkcs12 -in <your-app>.pfx -out <your-app>.pem
$ openssl x509 -outform der -in <your-app>.pem -out ${PUBLIC_CERT_FILE}
$ openssl rsa -in <your-app>.pem -out ${PRIVATE_KEY_FILE}
x5t="$(sha1sum ${PUBLIC_CERT_FILE} | awk '{print $1;}' | openssl base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$// )"
read -r -d '' HEADER <<EOF
{
"alg": "RS256",
"typ": "JWT",
"x5t": "${x5t}"
}
EOF
HEADER_NO_WHITESPACE=$(echo "${HEADER}" | sed ':a; N; s/[[:space:]]//g; ta')
BASE64_ENCODED_HEADER=$(echo ${HEADER_NO_WHITESPACE} | openssl base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$// )
read -r -d '' PAYLOAD <<EOF
{
"aud": "https: //login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token",
"exp": ${exp},
"iss": "${CLIENT_ID}",
"jti": "${jti}",
"nbf": ${nbf},
"sub": "${CLIENT_ID}"
}
EOF
PAYLOAD_NO_WHITESPACE=$(echo "${PAYLOAD}" | sed ':a; N; s/[[:space:]]//g; ta')
BASE64_ENCODED_PAYLOAD=$(echo ${PAYLOAD_NO_WHITESPACE} | openssl base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$// )
SIGNATURE=$( echo -n "${BASE64_ENCODED_HEADER}.${BASE64_ENCODED_PAYLOAD}" | \
openssl dgst -sha256 -binary -sign <(cat ${PRIVATE_KEY_FILE}) | \
openssl base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$// )
CLIENT_ASSERTION="${BASE64_ENCODED_HEADER}.${BASE64_ENCODED_PAYLOAD}.${SIGNATURE}"
curl -s -X POST \
--header \"Content-Type: application/x-www-form-urlencoded\" \
-d \"\
scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&\
grant_type=client_credentials&\
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&\
client_id=${CLIENT_ID}&\
client_assertion=${CLIENT_ASSERTION}\" \
https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token"
After getting a Java version of this working I figured out that I was not generating the thumbprint properly. The fix was this:
thumbprint=$(openssl x509 -in ${publicCertFile} -fingerprint -noout | awk '{split($0,a,"="); print a[2]}' )
thumbprintHex=$(echo -e "\\x${thumbprint}" | sed 's/:/\\x/g' )
thumbprintHexString=$(echo -e "${thumbprint}" | sed 's/://g' )
x5t=$(echo -ne "${thumbprintHex}" | openssl base64 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$// )
I have updated the gist to the fully working bash script.
Construct a JWT assertion in bash using client-credential with cert to get an access token from Microsoft Azure Directory --> https://gist.github.com/smaring/3a3a6779a809beecc39624aada6e2b88

How to generate an aws4 signed request in bash?

I'm trying to connect to the Scaleway S3 api in bash (it's a subset of aws s3 api). The signature model is aws4_request, and is documented. In the code below I'm uploading a file. I've reviewed every line and although I'm no bash guru, I'm pretty confident I have all the right parts to compute a correct signature, but I still get 403 error as a response. The request itself seems fine, as far as I can see in a wireshark capture. Can anyone find any issue here?
#!/bin/bash
set -e
echo "creds $SCW_ACCESS_KEY/$SCW_SECRET_KEY"
BUCKET="vni-backups"
REGION="fr-par"
HOST="$BUCKET.s3.$REGION.scw.cloud"
filepath="../import.yml"
file_sha256=$(sha256sum -b $filepath | head -c64)
fulldate=$(date +"%Y%m%dT%H%M%SZ" --utc)
shortdate=$(date +"%Y%m%d" --utc)
# Assemble canonical url
canonicalRequest="PUT
/import.yml
content-type:text/plain
host:$HOST
x-amz-content-sha256:$file_sha256
x-amz-date:$fulldate
content-type;host;x-amz-content-sha256;x-amz-date
$file_sha256"
canonReqSha=$(echo -n "$canonicalRequest" | openssl dgst -sha256 | awk '{print $2}')
echo $canonicalRequest
echo "requestHash: $canonReqSha"
echo "
---------------------------------------
"
stringToSign="AWS4-HMAC-SHA256
$fulldate
$shortdate/fr-par/s3/aws4_request
$canonReqSha"
echo $stringToSign
dateKey=$(echo -n "$shortdate" | openssl dgst -sha256 -binary -hmac "AWS4$SCW_SECRET_KEY")
regionKey=$(echo -n "fr-par" | openssl dgst -sha256 -binary -hmac "$dateKey")
serviceKey=$(echo -n "s3" | openssl dgst -sha256 -binary -hmac "$regionKey")
signingKey=$(echo -n "aws4_request" | openssl dgst -sha256 -binary -hmac "$serviceKey")
signature=$(echo -n "$stringToSign" | openssl dgst -sha256 -hmac "$signingKey" | awk '{print $2}')
echo "signature: $signature"
echo "
---------------------------------------
"
# Make request
curl -X PUT \
-H "Content-Type: text/plain" \
-H "x-amz-content-sha256: $file_sha256" \
-H "x-amz-date: $fulldate" \
-H "Authorization: AWS4-HMAC-SHA256" \
-H "Credential: $SCW_ACCESS_KEY/${shortdate}/$REGION/s3/aws4_request" \
-H "SignedHeaders: content-type;host;x-amz-content-sha256;x-amz-date" \
-H "Signature: $signature" \
--data-binary #../import.yml \
"http://$BUCKET.s3.$REGION.scw.cloud/import.yml"
The request is done in clear http just because it's simpler to capture it with network analysis tool (e.g. wireshark) than https.

Why do i have an empty file after sending stdout to it

Using bash i am executing following commands to filter information from a certificate
openssl s_client -connect google.com:443 < /dev/null > cert.pem
openssl x509 -in cert.pem -noout -subject > commonName
tr "," "\n" < commonName | sed -nr '/CN/p' | tr -d ' /t' > commonName
The last command leaves the file "commonName" empty, i wonder why this is.
If i instead append the file ">>" the desired filtered output is shown but unfiltered content remains.
Leaves file empty
tr "," "\n" < commonName | sed -nr '/CN/p' | tr -d ' /t' > commonName
Works but with undesired content
tr "," "\n" < commonName | sed -nr '/CN/p' | tr -d ' /t' >> commonName
Edit,
Might add that sending to a file with a new name works as intended. Changing "commonName" to "test" for example.
Thanks in advance!
/R
You can't edit the file with other commands and sed in only one pipe (the way you do it). You need a temporary file:
openssl s_client -connect google.com:443 < /dev/null > cert.pem
openssl x509 -in cert.pem -noout -subject > commonName
tr "," "\n" < commonName | sed -nr '/CN/p' | tr -d ' /t' > /tmp/temp
mv /tmp/temp commonName
And a better way to achieve the whole script, without temporary file:
openssl s_client -connect google.com:443 < /dev/null > cert.pem
openssl x509 -in cert.pem -noout -subject |
tr "," "\n" |
grep -o 'CN .*' > commonName

OpenSSL decrypt AES 256bit (base64) encrypted password - wrong final block length

I am trying to decrypt aes-256-cdc encoded password using OpenSSL
#!/usr/bin/env bash
ak=BgL0cPoZQ4wZWOWl5mXBhlMsNbbZL2zvsWZXjuGy4Iw=
iv=cGEvcGWzE8t7CS3wbeoUFQ==
pass=RCQm23YHOCg3nxOl7CcQ7w==
#change format from base64 into hex
AES_KEY=$(echo "${ak}" | openssl base64 -d | xxd -p |tr -d '\n')
AES_IV=$(echo "${iv}" | openssl base64 -d | xxd -p)
ENCODED_PASSWORD=$(echo "${pass}" | openssl base64 -d | xxd -p)
echo "AES_KEY ${AES_KEY}"
echo "AES_IV ${AES_IV}"
echo "ENCODED_PASSWORD ${ENCODED_PASSWORD}"
#set password file
echo "${ENCODED_PASSWORD}" > in.txt
#decode password
openssl enc -nosalt -aes-256-cbc -d -iv ${AES_IV} -K ${AES_KEY} -in in.txt
this results in error message
AES_KEY 0602f470fa19438c1958e5a5e665c186532c35b6d92f6cefb166578ee1b2e08c
AES_IV 70612f7065b313cb7b092df06dea1415
ENCODED_PASSWORD 53b7adff6e85baedfa9dab80109ad67d
▒▒▒▒▒▒`$;▒▒▒▒%▒O▒Q▒▒▒S▒▒<7 7
bad decrypt
32624:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:518:
0602f470fa19438c1958e5a5e665c186532c35b6d92f6cefb166578ee1b2
e08c
I think that the problem is aes-key (ak) which has a newline char in it, which I am trying to remove with
|tr -d '\n'
a password should decode as
password
No, what's wrong is the encoding of in.txt. It should not be text at all, it should be binary.
In principle you would not get this error either if you'd use echo -n suppressing the final end-of-line within the ciphertext. The decryption would however still fail as it expects binary instead of an encoded binary value.
You may want to change the name of in.txt to in.bin if you want to keep a file. You should also be able to simply pipe the ciphertext into openssl using the standard input (stdin). In that case you may want to encode it first to store it in a shell variable and then decode before piping it to openssl to decrypt it.
For binary, use cat instead of echo.
If your $pass is long (more than 32 chars maybe), be sure to use openssl -A option, the reason is documented in the openssl manual.
With -A option, for encryption, the base64 encoded string will not be splitted in segments; For decryption, whole line is read to be decoded using base64.
Code example:
plaintxt='hello world"
pass=$(echo ${plaintxt} | openssl enc -aes-128-cbc -a -K ${AES_KEY} -iv ${AES_IV} -A )
echo "decoded password is: "
echo ${pass} | openssl enc -aes-128-cbc -d -a -K ${AES_KEY} -iv ${AES_IV} -A
this works as expected:
#!/usr/bin/env bash
#base64 encoded aes key, iv and password
ak=BgL0cPoZQ4wZWOWl5mXBhlMsNbbZL2zvsWZXjuGy4Iw=
iv=cGEvcGWzE8t7CS3wbeoUFQ==
pass=OfOXO+ruKFTCsBwGHynXwA==
#change format from base64 into hex, for openssl to consume, xxd -p -c32 is taking care of wrapping of the new lines
AES_KEY=$(echo ${ak} | openssl base64 -d | xxd -p -c32)
AES_IV=$(echo ${iv} | openssl base64 -d | xxd -p -c32)
echo "AES_KEY ${ak}"
echo "AES_IV ${iv}"
echo "encoded password ${pass}"
echo "decoded password is: "
echo ${pass} | openssl enc -aes-256-cbc -d -a -K ${AES_KEY} -iv ${AES_IV}
explanation:
openssl enc -aes-256-cbc -d
decode using aes-256-cbc algo
-a
in our case, it means that openssl will accept base64 encoded password
-K
aes key
-iv
aes iv

How to verify ECDSA and RSA certificates

I am going to run acme-tiny on a central webserver in order to get the certificates for my two Nginx reverse proxies issued. The newly created certificates are published over https and available to the reverse proxies via download.
I want the Nginx servers to check the newly created certificates before replacing the old certificates with the new ones. For this purpose, I wrote the following bash script, which is run on each Nginx server. I would like to know whether I missed something or whether you have ideas for improvement. Or is there a better way to realise this?
#!/bin/bash
set -e
# Commands for deriving the public keys:
# openssl ec -in ecdsa.key -pubout > ecdsa_public_key.pem
# openssl rsa -in rsa.key -pubout > rsa_public_key.pem
curl -O https://example.org/ecdsa.pem
curl -O https://example.org/intermediate.pem
curl -O https://example.org/rsa.pem
# Are the certificates not expired?
# Have they (ecdsa.pem, rsa.pem) been recently issued (validity >= 80 days)?
openssl x509 -checkend 6912000 -noout -in intermediate.pem
openssl x509 -checkend 6912000 -noout -in ecdsa.pem
openssl x509 -checkend 6912000 -noout -in rsa.pem
# Do the private keys and certificates belong together?
openssl x509 -in ecdsa.pem -pubkey | \
sed -n '/-----BEGIN PUBLIC KEY-----/,/-----END PUBLIC KEY-----/p' | \
cmp - ecdsa_public_key.pem
openssl x509 -in rsa.pem -pubkey | \
sed -n '/-----BEGIN PUBLIC KEY-----/,/-----END PUBLIC KEY-----/p' | \
cmp - rsa_public_key.pem
# Is the certificate chain valid?
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt intermediate.pem
openssl verify -CAfile intermediate.pem ecdsa.pem
openssl verify -CAfile intermediate.pem rsa.pem
# Are the certificates issued for the correct domain names?
(openssl x509 -noout -subject -in ecdsa.pem | awk -F'CN=' '{print $2}' | \
awk -F'/' '{print $1}'; \
openssl x509 -noout -text -in ecdsa.pem | grep "^[[:space:]]*DNS:" | \
xargs | tr ' ' '\n' | grep ^DNS | sed 's/^DNS://' | sed 's/,$//')| \
sort | uniq | cmp - domains.txt
(openssl x509 -noout -subject -in rsa.pem | awk -F'CN=' '{print $2}' | \
awk -F'/' '{print $1}'; \
openssl x509 -noout -text -in rsa.pem | grep "^[[:space:]]*DNS:" | \
xargs | tr ' ' '\n' | grep ^DNS | sed 's/^DNS://' | sed 's/,$//')| \
sort | uniq | cmp - domains.txt
UPDATE 1:
As I have stated I want to check every aspect of the certificate not just the validity of the certificate chain. It currently checks:
the chains validity,
whether private key and certificate match,
whether the cert has been recently issued making replacement of the older cert necessary
whether the cert has been issued for the desired domain names
Therefore, it isn't a duplicate to the linked questions. And, I would like to know whether I have forgotten some check or whether there is space for improvement. Below is an update to the bash script. Beware I have not tested it thoroughly, yet.
#!/bin/bash
set -e
# Execute the script:
# ( cd /path_to_workdir && \
# su - james -c "./check_cert.sh https://www.example.org intermediate.pem ecdsa.pem ecdsa_pubkey.pem /etc/ssl/certs/ca-certificates.crt domains.txt pass.txt" && \
# su - james -c "./check_cert.sh https://www.example.org intermediate.pem rsa.pem rsa_pubkey.pem /etc/ssl/certs/ca-certificates.crt domains.txt pass.txt" && \
# cat ecdsa.pem intermediate.pem > /etc/nginx/ssl/ecdsa_bundle.pem ) && \
# cat rsa.pem intermediate.pem > /etc/nginx/ssl/rsa_bundle.pem ) && \
# /etc/init.d/nginx reload
# Download URL, e.g. https://example.org
URL=$1
# Intermediate certificate of certificate chain
INTERMEDIATE=$2
# Issued certificate, e.g. ecdsa.pem
CERT=$3
# Public key derived from private key via:
# openssl ec -in ecdsa.key -pubout > ecdsa_pubkey.pem
# openssl rsa -in rsa.key -pubout > rsa_pubkey.pem
PUBKEY=$4
# ca-certificates file, e.g. /etc/ssl/certs/ca-certificates.crt
CACERTS=$5
# Domains that the cert should cover
DOMAINS=$6
# password-file.txt possible content:
# machine example.org login james password H3Llo
PASS=$7
# Download files if newer than local.
if [[ $(curl -s -O --netrc-file "$PASS" -w "%{http_code}" -z "$CERT" "$URL/$CERT") -eq 304 ]]; then
echo "No new certificate issued. Nothing to do!"
exit 1
fi
curl -s -O --netrc-file "$PASS" -z "$INTERMEDIATE" "$URL/$INTERMEDIATE"
# Is the certificate chain valid?
openssl verify -CAfile "$CACERTS" -untrusted "$INTERMEDIATE" "$CERT"
# Do the private keys and certificates belong together?
openssl x509 -in "$CERT" -pubkey | \
sed -n '/-----BEGIN PUBLIC KEY-----/,/-----END PUBLIC KEY-----/p' | \
cmp - "${PUBKEY}"
# Has the Let's Encrypt certificate been recently issued (validity >= 80 days)?
openssl x509 -checkend 6912000 -noout -in "$CERT"
# Are the certificates issued for the correct domain names?
(openssl x509 -noout -subject -in "$CERT" | awk -F'CN=' '{print $2}' | \
awk -F'/' '{print $1}'; \
openssl x509 -noout -text -in "$CERT" | grep "^[[:space:]]*DNS:" | \
xargs | tr ' ' '\n' | grep ^DNS | sed 's/^DNS://' | sed 's/,$//')| \
sort | uniq | cmp - "$DOMAINS"
The code might be run via cronjob:
0 1 * * * /usr/local/bin/update_cert.sh >/dev/null 2>&1
The content of update_cert.sh might be:
#!/bin/bash
( cd /path_to_workdir && \
su - james -c "./check_cert.sh https://www.example.org intermediate.pem ecdsa.pem ecdsa_pubkey.pem /etc/ssl/certs/ca-certificates.crt domains.txt pass.txt" && \
su - james -c "./check_cert.sh https://www.example.org intermediate.pem rsa.pem rsa_pubkey.pem /etc/ssl/certs/ca-certificates.crt domains.txt pass.txt" && \
cat ecdsa.pem intermediate.pem > /etc/nginx/ssl/ecdsa_bundle.pem ) && \
cat rsa.pem intermediate.pem > /etc/nginx/ssl/rsa_bundle.pem ) && \
/etc/init.d/nginx reload

Resources