Hi,
Has anyone ever tried to pull the expiry date of any Certificate? I can see it is in my Keychain and also can see the last date there. But I need to find a way to pull report from all Macs and send out notification based on expiry date.
Unable to pull this info. Any ideas?
Hi there,
I use the following script to search for AD certificates in the keychain management and display their expiration date in the extension attributes.
In our environment, we always need to renew the AD certificate so that our managed Macs can continue to access our university network via 802.1X.
Therefore, it is so that there is not only one AD certificate in the keychain management. The number keeps growing whenever a new certificate is created on a device.
With my script now I always want to know only the expiration date of the latest certificate.
Could someone please help me here.
Thank you very much.
#!/bin/bash
COMPUTER_NAME=$(scutil --get ComputerName)
EXPIRATION_DATE=$(/usr/bin/security find-certificate -a -c "$COMPUTER_NAME" -p -Z "/Library/Keychains/System.keychain" | /usr/bin/openssl x509 -noout -enddate| cut -f2 -d=)
EXPIRATION_DATE_FORMATTED=$(/bin/date -j -f "%b %d %T %Y %Z" "$EXPIRATION_DATE" "+%d.%m.%Y %H:%M:%S")
echo "<result>$EXPIRATION_DATE_FORMATTED</result>"
Hi there,
I use the following script to search for AD certificates in the keychain management and display their expiration date in the extension attributes.
In our environment, we always need to renew the AD certificate so that our managed Macs can continue to access our university network via 802.1X.
Therefore, it is so that there is not only one AD certificate in the keychain management. The number keeps growing whenever a new certificate is created on a device.
With my script now I always want to know only the expiration date of the latest certificate.
Could someone please help me here.
Thank you very much.
#!/bin/bash
COMPUTER_NAME=$(scutil --get ComputerName)
EXPIRATION_DATE=$(/usr/bin/security find-certificate -a -c "$COMPUTER_NAME" -p -Z "/Library/Keychains/System.keychain" | /usr/bin/openssl x509 -noout -enddate| cut -f2 -d=)
EXPIRATION_DATE_FORMATTED=$(/bin/date -j -f "%b %d %T %Y %Z" "$EXPIRATION_DATE" "+%d.%m.%Y %H:%M:%S")
echo "<result>$EXPIRATION_DATE_FORMATTED</result>"
In the past I have used logic like this example to get a total count of machine certs (or any cert really based on the criteria) to help determine if I need to scrub older certs or not
#!/bin/zsh
COMPUTER_NAME=$( scutil --get ComputerName )
DOMAIN="my.domain."
CERTIFICATE_COUNT=$( security find-certificate -a -c "${COMPUTER_NAME}.${DOMAIN}" "/Library/Keychains/System.keychain" | awk -F'"' '/alis/{print $4}' | wc -l | awk '{$1=$1;print}' )
echo "<result>${CERTIFICATE_COUNT}</result>"
In the past I have used logic like this example to get a total count of machine certs (or any cert really based on the criteria) to help determine if I need to scrub older certs or not
#!/bin/zsh
COMPUTER_NAME=$( scutil --get ComputerName )
DOMAIN="my.domain."
CERTIFICATE_COUNT=$( security find-certificate -a -c "${COMPUTER_NAME}.${DOMAIN}" "/Library/Keychains/System.keychain" | awk -F'"' '/alis/{print $4}' | wc -l | awk '{$1=$1;print}' )
echo "<result>${CERTIFICATE_COUNT}</result>"
Thanks @dstranathan,
but I don't need the number of AD certificates, I need the expiration date of the most recent one.
Via
<result>$EXPIRATION_DATE_FORMATTED</result>
I would enter the information as extension attribute. So I can always see in the overview of all managed Macs when the currently valid certificate expires (when renewing a new certificate is issued, but the old one remains in the keyring management) and when it must be renewed at the latest.
Below's our EA that lists all certificates in the naming convention specified in 3 keychains: system, login & openvpn
Feel free to edit/remove parts that's not needed.
Since our desktops and portables has different naming convention, there's a logic that checks what type of hardware model and assign accordingly.
#!/bin/sh
# get the AD name and domain, make the FQDN of the machine to match the cert we're looking for
#AD_NAME=$(scutil --get ComputerName)
username=$( stat -f%Su /dev/console )
login_keychain="/Users/$username/Library/Keychains/login.keychain"
system_keychain="/Library/Keychains/System.keychain"
openvpn_keychain="/Users/$username/Library/Keychains/openvpn.keychain"
# Gets the Model Identifier, splits name and major version
MODELIDENTIFIER=$(/usr/sbin/sysctl -n hw.model)
MODELNAME=$(echo "$MODELIDENTIFIER" | sed 's/[^a-zA-Z]//g')
# wildcard search for certificates ending with *.company.org
# FQDN assignment
# Magically form hostname based on asset type: Desktops -> lon , Portables -> nomad
lon_fqdn=".lon.company.org"
nomad_fqdn=".nomad.company.org"
case "$model_name" in
iMac*|iMacPro*|Macmini*|MacPro*|Mac13,1*)
cert_name="$lon_fqdn"
;;
*)
cert_name="$nomad_fqdn"
;;
esac
# Not used, but may come in handy if we need to match on a different AD value later.
# AD_DOMAIN=$(dsconfigad -show | awk '/Active Directory Domain/{print $NF}')
# FQDN="$AD_NAME.$AD_DOMAIN"
#
#We look through the entire system/login/OpenVPN keychain for any Computer certificates that matches
#Then we load the entire certificate and SHA Hash for ALL matches into an array, which is important if you are trying to locate duplicate certificates.
IFS=","
#System Keychain
CertList=$(/usr/bin/security find-certificate -a -c $cert_name -p -Z $system_keychain | sed s/"-----END CERTIFICATE-----"/"-----END CERTIFICATE-----,"/g)
if [ -n "$CertList" ]; then
Array=($CertList)
for ACert in "${Array[@]}"
do
#We pass each matching cert to openSSL, and pull a handful of useful details from each matched certificate.
certsubject=$(/usr/bin/openssl x509 -noout -subject <<< $ACert)
certCN=$(/usr/bin/openssl x509 -noout -subject <<< $ACert | sed -n '/^subject/s/^.*CN=//p')
certissuer=$(/usr/bin/openssl x509 -noout -issuer <<< $ACert)
certserial=$(/usr/bin/openssl x509 -noout -serial <<< $ACert)
certexpdate=$(/usr/bin/openssl x509 -noout -enddate <<< $ACert | cut -f2 -d=)
certexpdateformatted=$(/bin/date -j -f "%b %d %T %Y %Z" "$certexpdate" "+%Y-%m-%d %H:%M:%S")
systemResults+="System Keychain: $certCN | $certserial | Expiry Date: $certexpdateformatted\\n"
done
#Added this to differentiate between non-responsive systems and systems that just don't have a matching cert at all.
#Esp useful in case you are running the contents from ARD (it works)
else
systemResults="No Matching Certs Found in System Keychain\\n"
fi
#Login Keychain
CertList=$(/usr/bin/security find-certificate -a -c $cert_name -p -Z $login_keychain | sed s/"-----END CERTIFICATE-----"/"-----END CERTIFICATE-----,"/g)
if [ -n "$CertList" ]; then
Array=($CertList)
for ACert in "${Array[@]}"
do
#We pass each matching cert to openSSL, and pull a handful of useful details from each matched certificate.
certsubject=$(/usr/bin/openssl x509 -noout -subject <<< $ACert)
certCN=$(/usr/bin/openssl x509 -noout -subject <<< $ACert | sed -n '/^subject/s/^.*CN=//p')
certissuer=$(/usr/bin/openssl x509 -noout -issuer <<< $ACert)
certserial=$(/usr/bin/openssl x509 -noout -serial <<< $ACert)
certexpdate=$(/usr/bin/openssl x509 -noout -enddate <<< $ACert | cut -f2 -d=)
certexpdateformatted=$(/bin/date -j -f "%b %d %T %Y %Z" "$certexpdate" "+%Y-%m-%d %H:%M:%S")
loginResults+="Login Keychain: $certCN | $certserial | Expiry Date: $certexpdateformatted\\n"
done
#Added this to differentiate between non-responsive systems and systems that just don't have a matching cert at all.
#Esp useful in case you are running the contents from ARD (it works)
else
loginResults="No Matching Certs Found in Login Keychain\\n"
fi
#OpenVPN Keychain
CertList=$(/usr/bin/security find-certificate -a -c $cert_name -p -Z $openvpn_keychain | sed s/"-----END CERTIFICATE-----"/"-----END CERTIFICATE-----,"/g)
if [ -n "$CertList" ]; then
Array=($CertList)
for ACert in "${Array[@]}"
do
#We pass each matching cert to openSSL, and pull a handful of useful details from each matched certificate.
certsubject=$(/usr/bin/openssl x509 -noout -subject <<< $ACert)
certCN=$(/usr/bin/openssl x509 -noout -subject <<< $ACert | sed -n '/^subject/s/^.*CN=//p')
certissuer=$(/usr/bin/openssl x509 -noout -issuer <<< $ACert)
certserial=$(/usr/bin/openssl x509 -noout -serial <<< $ACert)
certexpdate=$(/usr/bin/openssl x509 -noout -enddate <<< $ACert | cut -f2 -d=)
certexpdateformatted=$(/bin/date -j -f "%b %d %T %Y %Z" "$certexpdate" "+%Y-%m-%d %H:%M:%S")
openvpnResults+="OpenVPN Keychain: $certCN | $certserial | Expiry Date: $certexpdateformatted\\n"
done
#Added this to differentiate between non-responsive systems and systems that just don't have a matching cert at all.
#Esp useful in case you are running the contents from ARD (it works)
else
openvpnResults="No Matching Certs Found in OpenVPN Keychain\\n"
fi
#Consolidate results
RESULT="$systemResults$loginResults$openvpnResults"
/usr/bin/printf "<result>$RESULT</result>"
Thanks @Asri-Zainal,
that is certainly very helpful. But I am unfortunately not someone who can write scripts or knows anything about it.
The part about reading the AD certificates is about the most complicated thing we have in our Jamf environment.
I would be very, very grateful if someone could still add the part I need in our script (finding the AD certificate with the longest validity). I don't think I'll ever get anywhere here otherwise.
Thanks a lot for your help.
#!/bin/bash
COMPUTER_NAME=$(scutil --get ComputerName)
EXPIRATION_DATE=$(/usr/bin/security find-certificate -a -c "$COMPUTER_NAME" -p -Z "/Library/Keychains/System.keychain" | /usr/bin/openssl x509 -noout -enddate| cut -f2 -d=)
EXPIRATION_DATE_FORMATTED=$(/bin/date -j -f "%b %d %T %Y %Z" "$EXPIRATION_DATE" "+%d.%m.%Y %H:%M:%S")
echo "<result>$EXPIRATION_DATE_FORMATTED</result>"
Thanks @Asri-Zainal,
that is certainly very helpful. But I am unfortunately not someone who can write scripts or knows anything about it.
The part about reading the AD certificates is about the most complicated thing we have in our Jamf environment.
I would be very, very grateful if someone could still add the part I need in our script (finding the AD certificate with the longest validity). I don't think I'll ever get anywhere here otherwise.
Thanks a lot for your help.
#!/bin/bash
COMPUTER_NAME=$(scutil --get ComputerName)
EXPIRATION_DATE=$(/usr/bin/security find-certificate -a -c "$COMPUTER_NAME" -p -Z "/Library/Keychains/System.keychain" | /usr/bin/openssl x509 -noout -enddate| cut -f2 -d=)
EXPIRATION_DATE_FORMATTED=$(/bin/date -j -f "%b %d %T %Y %Z" "$EXPIRATION_DATE" "+%d.%m.%Y %H:%M:%S")
echo "<result>$EXPIRATION_DATE_FORMATTED</result>"
This should do the trick, as long as you provide the criteria for your machine certificate name. I used this in the past on our 802.1x EAP-TLS machine certificate for 802.1x in the System Keychain, but it can be used for any certs.
If only 1 cert is located and it is valid then it tells you (and reports the exp date for QA). If there are multiple certs with the same name then the oldest cert(s) are deleted.
I have not used it in a while, but I just tested it on Ventura and it worked.
(sorry for the weird formatting and possible line wrap. Cut and paste it into a text doc and tweak it there)
#!/bin/sh
# Script name: verify_and_delete_expired_computer_certs.sh
# Version: 1.0
# Author: DDS
# Creation date: August 10 2021
# Modification date: March 31 2022
COMPUTER_NAME=$( scutil --get ComputerName )
COMPUTER_CERTIFICATE_NAME="MY_CERT"
SYSTEM_KEYCHAIN="/Library/Keychains/System.keychain"
CURRENT_DATE_FORMATTED=$( date +%Y-%m-%d ) # Example: 2021-05-06 (no seconds!)
CURRENT_DATE_FORMATTED_TRUNCATED=$( echo ${CURRENT_DATE_FORMATTED} | tr -d "-" ) # Example: 20210506 (removed dashes)
function VERIFY_EXPIRED_CERTIFICATES() {
# Look through the entire System Keychain for any computer certificates that match the AD Name of the computer.
# Parse the entire certificate and SHA Hash for ALL matches into an array, which is important if you are trying to locate duplicate certificates.
# The IFS below "internal field separator" is perform a find-and-replace for separating
# the search results with a , (1 comma) for each cert found that matched criteria.
# The sed find and replace operation after "IFS" adds a , ( 1 comma) to the security tool's
# find to sort and display any potential duplicates (if there are any).
# Word-splitting in zsh is totally different than bash and other shells! IFS may not work as expected.
#IFS=","
local IFS=","
CERT_LIST=$( security find-certificate -a -c "${COMPUTER_CERTIFICATE_NAME}" -p -Z "${SYSTEM_KEYCHAIN}" | sed s/"-----END CERTIFICATE-----"/"-----END CERTIFICATE-----,"/g)
if [[ -n "${CERT_LIST}" ]]; then
CERT_ARRAY=(${CERT_LIST})
for CERT_ELEMENT in "${CERT_ARRAY[@]}"; do
# Pass each matching certificate to openSSL, and pull expiration details from each matched certificate.
# Each instance of the cert name will have a (number) appended.
CERT_ARRAY_INDEX_NUMBER=$((${CERT_ARRAY_INDEX_NUMBER}+1))
CERT_EXPIRATION_DATE_FULL=$( openssl x509 -noout -enddate <<< "${CERT_ELEMENT}" | cut -f2 -d= )
CERT_EXPIRATION_DATE_FORMATTED=$( date -j -f "%b %d %T %Y %Z" "${CERT_EXPIRATION_DATE_FULL}" "+%Y-%m-%d" )
CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED=$( echo ${CERT_EXPIRATION_DATE_FORMATTED} | tr -d "-" )
#CERT_SERIAL=$( openssl x509 -noout -serial <<< $CERT_ELEMENT )
CERT_SHA1_HASH=$( grep -e 'SHA-1 hash:' <<< "${CERT_ELEMENT}" | cut -d" " -f3- )
# More QA output for testing.
#echo "SHA-1 hash: ${CERT_SHA1_HASH}"
#echo "Certificate expiration date (full): ${CERT_EXPIRATION_DATE_FULL}"
#echo "Certificate expiration date (formatted): ${CERT_EXPIRATION_DATE_FORMATTED}"
#echo "Certificate expiration date (formatted and truncated): ${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}"
#echo
#echo "Array elements: \\n"
#printf '%s\\n' "${CERT_ARRAY[@]}"
if [[ "${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}" -eq "${CURRENT_DATE_FORMATTED_TRUNCATED}" ]]; then
echo
echo "Certificate containing string '${COMPUTER_CERTIFICATE_NAME}'(${CERT_ARRAY_INDEX_NUMBER}) (${CERT_SHA1_HASH}) EXPIRES TODAY (${CURRENT_DATE_FORMATTED}). No action is required."
# Do not delete certificate.
elif [[ "${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}" -gt "${CURRENT_DATE_FORMATTED_TRUNCATED}" ]]; then
echo
echo "Certificate containing string '${COMPUTER_CERTIFICATE_NAME}' (${CERT_ARRAY_INDEX_NUMBER}) (${CERT_SHA1_HASH}) is VALID (Expiration: ${CERT_EXPIRATION_DATE_FORMATTED}). No action is required."
# Do not delete certificate.
elif [[ "${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}" -lt "${CURRENT_DATE_FORMATTED_TRUNCATED}" ]]; then
echo
echo "Certificate containing string '${COMPUTER_CERTIFICATE_NAME}'(${CERT_ARRAY_INDEX_NUMBER}) (${CERT_SHA1_HASH}) has EXPIRED (${CERT_EXPIRATION_DATE_FORMATTED}). It will be deleted."
# Delete certificate.
DELETE_EXPIRED_CERIFICATES
else
echo
echo "Alert: An error has occured. Certificate containing string '${COMPUTER_CERTIFICATE_NAME}' expiration is UNKNOWN."
# Do not delete certificate.
SCRIPT_POST_FLIGHT_1
fi
done
else
echo
echo "Alert: Certificate containing string '${COMPUTER_CERTIFICATE_NAME}' could not be found in ${SYSTEM_KEYCHAIN}."
SCRIPT_POST_FLIGHT_1
fi
}
function DELETE_EXPIRED_CERIFICATES() {
# Delete only the expired certificates
echo "Deleting expired certificate containing string '${COMPUTER_CERTIFICATE_NAME}' (${CERT_SHA1_HASH}) from ${SYSTEM_KEYCHAIN}..."
# The next line is dangerous!
sudo security delete-certificate -Z ${CERT_SHA1_HASH} ${SYSTEM_KEYCHAIN}
# Verify deletion status
if [[ $? == 0 ]]; then
echo
echo "The expired certificate containing string ${COMPUTER_CERTIFICATE_NAME} was successfully removed from ${SYSTEM_KEYCHAIN}."
else
echo
echo "Error: Failed to remove expired certificate containing string ${COMPUTER_CERTIFICATE_NAME}."
exit 1
fi
}
VERIFY_EXPIRED_CERTIFICATES
This should do the trick, as long as you provide the criteria for your machine certificate name. I used this in the past on our 802.1x EAP-TLS machine certificate for 802.1x in the System Keychain, but it can be used for any certs.
If only 1 cert is located and it is valid then it tells you (and reports the exp date for QA). If there are multiple certs with the same name then the oldest cert(s) are deleted.
I have not used it in a while, but I just tested it on Ventura and it worked.
(sorry for the weird formatting and possible line wrap. Cut and paste it into a text doc and tweak it there)
#!/bin/sh
# Script name: verify_and_delete_expired_computer_certs.sh
# Version: 1.0
# Author: DDS
# Creation date: August 10 2021
# Modification date: March 31 2022
COMPUTER_NAME=$( scutil --get ComputerName )
COMPUTER_CERTIFICATE_NAME="MY_CERT"
SYSTEM_KEYCHAIN="/Library/Keychains/System.keychain"
CURRENT_DATE_FORMATTED=$( date +%Y-%m-%d ) # Example: 2021-05-06 (no seconds!)
CURRENT_DATE_FORMATTED_TRUNCATED=$( echo ${CURRENT_DATE_FORMATTED} | tr -d "-" ) # Example: 20210506 (removed dashes)
function VERIFY_EXPIRED_CERTIFICATES() {
# Look through the entire System Keychain for any computer certificates that match the AD Name of the computer.
# Parse the entire certificate and SHA Hash for ALL matches into an array, which is important if you are trying to locate duplicate certificates.
# The IFS below "internal field separator" is perform a find-and-replace for separating
# the search results with a , (1 comma) for each cert found that matched criteria.
# The sed find and replace operation after "IFS" adds a , ( 1 comma) to the security tool's
# find to sort and display any potential duplicates (if there are any).
# Word-splitting in zsh is totally different than bash and other shells! IFS may not work as expected.
#IFS=","
local IFS=","
CERT_LIST=$( security find-certificate -a -c "${COMPUTER_CERTIFICATE_NAME}" -p -Z "${SYSTEM_KEYCHAIN}" | sed s/"-----END CERTIFICATE-----"/"-----END CERTIFICATE-----,"/g)
if [[ -n "${CERT_LIST}" ]]; then
CERT_ARRAY=(${CERT_LIST})
for CERT_ELEMENT in "${CERT_ARRAY[@]}"; do
# Pass each matching certificate to openSSL, and pull expiration details from each matched certificate.
# Each instance of the cert name will have a (number) appended.
CERT_ARRAY_INDEX_NUMBER=$((${CERT_ARRAY_INDEX_NUMBER}+1))
CERT_EXPIRATION_DATE_FULL=$( openssl x509 -noout -enddate <<< "${CERT_ELEMENT}" | cut -f2 -d= )
CERT_EXPIRATION_DATE_FORMATTED=$( date -j -f "%b %d %T %Y %Z" "${CERT_EXPIRATION_DATE_FULL}" "+%Y-%m-%d" )
CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED=$( echo ${CERT_EXPIRATION_DATE_FORMATTED} | tr -d "-" )
#CERT_SERIAL=$( openssl x509 -noout -serial <<< $CERT_ELEMENT )
CERT_SHA1_HASH=$( grep -e 'SHA-1 hash:' <<< "${CERT_ELEMENT}" | cut -d" " -f3- )
# More QA output for testing.
#echo "SHA-1 hash: ${CERT_SHA1_HASH}"
#echo "Certificate expiration date (full): ${CERT_EXPIRATION_DATE_FULL}"
#echo "Certificate expiration date (formatted): ${CERT_EXPIRATION_DATE_FORMATTED}"
#echo "Certificate expiration date (formatted and truncated): ${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}"
#echo
#echo "Array elements: \\n"
#printf '%s\\n' "${CERT_ARRAY[@]}"
if [[ "${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}" -eq "${CURRENT_DATE_FORMATTED_TRUNCATED}" ]]; then
echo
echo "Certificate containing string '${COMPUTER_CERTIFICATE_NAME}'(${CERT_ARRAY_INDEX_NUMBER}) (${CERT_SHA1_HASH}) EXPIRES TODAY (${CURRENT_DATE_FORMATTED}). No action is required."
# Do not delete certificate.
elif [[ "${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}" -gt "${CURRENT_DATE_FORMATTED_TRUNCATED}" ]]; then
echo
echo "Certificate containing string '${COMPUTER_CERTIFICATE_NAME}' (${CERT_ARRAY_INDEX_NUMBER}) (${CERT_SHA1_HASH}) is VALID (Expiration: ${CERT_EXPIRATION_DATE_FORMATTED}). No action is required."
# Do not delete certificate.
elif [[ "${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}" -lt "${CURRENT_DATE_FORMATTED_TRUNCATED}" ]]; then
echo
echo "Certificate containing string '${COMPUTER_CERTIFICATE_NAME}'(${CERT_ARRAY_INDEX_NUMBER}) (${CERT_SHA1_HASH}) has EXPIRED (${CERT_EXPIRATION_DATE_FORMATTED}). It will be deleted."
# Delete certificate.
DELETE_EXPIRED_CERIFICATES
else
echo
echo "Alert: An error has occured. Certificate containing string '${COMPUTER_CERTIFICATE_NAME}' expiration is UNKNOWN."
# Do not delete certificate.
SCRIPT_POST_FLIGHT_1
fi
done
else
echo
echo "Alert: Certificate containing string '${COMPUTER_CERTIFICATE_NAME}' could not be found in ${SYSTEM_KEYCHAIN}."
SCRIPT_POST_FLIGHT_1
fi
}
function DELETE_EXPIRED_CERIFICATES() {
# Delete only the expired certificates
echo "Deleting expired certificate containing string '${COMPUTER_CERTIFICATE_NAME}' (${CERT_SHA1_HASH}) from ${SYSTEM_KEYCHAIN}..."
# The next line is dangerous!
sudo security delete-certificate -Z ${CERT_SHA1_HASH} ${SYSTEM_KEYCHAIN}
# Verify deletion status
if [[ $? == 0 ]]; then
echo
echo "The expired certificate containing string ${COMPUTER_CERTIFICATE_NAME} was successfully removed from ${SYSTEM_KEYCHAIN}."
else
echo
echo "Error: Failed to remove expired certificate containing string ${COMPUTER_CERTIFICATE_NAME}."
exit 1
fi
}
VERIFY_EXPIRED_CERTIFICATES
Thank you very much @dstranathan.
I ran the script on one of our test devices (just had to redefine the variables at the beginning) and it worked immediately.
On the test device with 2 valid certificates, the script told me at the end that no further steps were needed after the two valid certificates were displayed.
How would I have to extend the script, if I always want to delete the oldest certificate, even if it is still valid? Because that happens sometimes with us as well.
And if also still valid, but no longer necessary certificates are deleted, then everything still remains clear.
Many greetings
Claudius
This should do the trick, as long as you provide the criteria for your machine certificate name. I used this in the past on our 802.1x EAP-TLS machine certificate for 802.1x in the System Keychain, but it can be used for any certs.
If only 1 cert is located and it is valid then it tells you (and reports the exp date for QA). If there are multiple certs with the same name then the oldest cert(s) are deleted.
I have not used it in a while, but I just tested it on Ventura and it worked.
(sorry for the weird formatting and possible line wrap. Cut and paste it into a text doc and tweak it there)
#!/bin/sh
# Script name: verify_and_delete_expired_computer_certs.sh
# Version: 1.0
# Author: DDS
# Creation date: August 10 2021
# Modification date: March 31 2022
COMPUTER_NAME=$( scutil --get ComputerName )
COMPUTER_CERTIFICATE_NAME="MY_CERT"
SYSTEM_KEYCHAIN="/Library/Keychains/System.keychain"
CURRENT_DATE_FORMATTED=$( date +%Y-%m-%d ) # Example: 2021-05-06 (no seconds!)
CURRENT_DATE_FORMATTED_TRUNCATED=$( echo ${CURRENT_DATE_FORMATTED} | tr -d "-" ) # Example: 20210506 (removed dashes)
function VERIFY_EXPIRED_CERTIFICATES() {
# Look through the entire System Keychain for any computer certificates that match the AD Name of the computer.
# Parse the entire certificate and SHA Hash for ALL matches into an array, which is important if you are trying to locate duplicate certificates.
# The IFS below "internal field separator" is perform a find-and-replace for separating
# the search results with a , (1 comma) for each cert found that matched criteria.
# The sed find and replace operation after "IFS" adds a , ( 1 comma) to the security tool's
# find to sort and display any potential duplicates (if there are any).
# Word-splitting in zsh is totally different than bash and other shells! IFS may not work as expected.
#IFS=","
local IFS=","
CERT_LIST=$( security find-certificate -a -c "${COMPUTER_CERTIFICATE_NAME}" -p -Z "${SYSTEM_KEYCHAIN}" | sed s/"-----END CERTIFICATE-----"/"-----END CERTIFICATE-----,"/g)
if [[ -n "${CERT_LIST}" ]]; then
CERT_ARRAY=(${CERT_LIST})
for CERT_ELEMENT in "${CERT_ARRAY[@]}"; do
# Pass each matching certificate to openSSL, and pull expiration details from each matched certificate.
# Each instance of the cert name will have a (number) appended.
CERT_ARRAY_INDEX_NUMBER=$((${CERT_ARRAY_INDEX_NUMBER}+1))
CERT_EXPIRATION_DATE_FULL=$( openssl x509 -noout -enddate <<< "${CERT_ELEMENT}" | cut -f2 -d= )
CERT_EXPIRATION_DATE_FORMATTED=$( date -j -f "%b %d %T %Y %Z" "${CERT_EXPIRATION_DATE_FULL}" "+%Y-%m-%d" )
CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED=$( echo ${CERT_EXPIRATION_DATE_FORMATTED} | tr -d "-" )
#CERT_SERIAL=$( openssl x509 -noout -serial <<< $CERT_ELEMENT )
CERT_SHA1_HASH=$( grep -e 'SHA-1 hash:' <<< "${CERT_ELEMENT}" | cut -d" " -f3- )
# More QA output for testing.
#echo "SHA-1 hash: ${CERT_SHA1_HASH}"
#echo "Certificate expiration date (full): ${CERT_EXPIRATION_DATE_FULL}"
#echo "Certificate expiration date (formatted): ${CERT_EXPIRATION_DATE_FORMATTED}"
#echo "Certificate expiration date (formatted and truncated): ${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}"
#echo
#echo "Array elements: \\n"
#printf '%s\\n' "${CERT_ARRAY[@]}"
if [[ "${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}" -eq "${CURRENT_DATE_FORMATTED_TRUNCATED}" ]]; then
echo
echo "Certificate containing string '${COMPUTER_CERTIFICATE_NAME}'(${CERT_ARRAY_INDEX_NUMBER}) (${CERT_SHA1_HASH}) EXPIRES TODAY (${CURRENT_DATE_FORMATTED}). No action is required."
# Do not delete certificate.
elif [[ "${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}" -gt "${CURRENT_DATE_FORMATTED_TRUNCATED}" ]]; then
echo
echo "Certificate containing string '${COMPUTER_CERTIFICATE_NAME}' (${CERT_ARRAY_INDEX_NUMBER}) (${CERT_SHA1_HASH}) is VALID (Expiration: ${CERT_EXPIRATION_DATE_FORMATTED}). No action is required."
# Do not delete certificate.
elif [[ "${CERT_EXPIRATION_DATE_FORMATTED_TRUNCATED}" -lt "${CURRENT_DATE_FORMATTED_TRUNCATED}" ]]; then
echo
echo "Certificate containing string '${COMPUTER_CERTIFICATE_NAME}'(${CERT_ARRAY_INDEX_NUMBER}) (${CERT_SHA1_HASH}) has EXPIRED (${CERT_EXPIRATION_DATE_FORMATTED}). It will be deleted."
# Delete certificate.
DELETE_EXPIRED_CERIFICATES
else
echo
echo "Alert: An error has occured. Certificate containing string '${COMPUTER_CERTIFICATE_NAME}' expiration is UNKNOWN."
# Do not delete certificate.
SCRIPT_POST_FLIGHT_1
fi
done
else
echo
echo "Alert: Certificate containing string '${COMPUTER_CERTIFICATE_NAME}' could not be found in ${SYSTEM_KEYCHAIN}."
SCRIPT_POST_FLIGHT_1
fi
}
function DELETE_EXPIRED_CERIFICATES() {
# Delete only the expired certificates
echo "Deleting expired certificate containing string '${COMPUTER_CERTIFICATE_NAME}' (${CERT_SHA1_HASH}) from ${SYSTEM_KEYCHAIN}..."
# The next line is dangerous!
sudo security delete-certificate -Z ${CERT_SHA1_HASH} ${SYSTEM_KEYCHAIN}
# Verify deletion status
if [[ $? == 0 ]]; then
echo
echo "The expired certificate containing string ${COMPUTER_CERTIFICATE_NAME} was successfully removed from ${SYSTEM_KEYCHAIN}."
else
echo
echo "Error: Failed to remove expired certificate containing string ${COMPUTER_CERTIFICATE_NAME}."
exit 1
fi
}
VERIFY_EXPIRED_CERTIFICATES
Thank you very much @dstranathan.
I ran the script on one of our test devices (just had to redefine the variables at the beginning) and it worked immediately.
On the test device with 2 valid certificates, the script told me at the end that no further steps were needed after the two valid certificates were displayed.
How would I have to extend the script, if I always want to delete the oldest certificate, even if it is still valid? Because that happens sometimes with us as well.
And if also still valid, but no longer necessary certificates are deleted, then everything still remains clear.
Many greetings
Claudius
Enter your E-mail address. We'll send you an e-mail with instructions to reset your password.