Following script worked like charm for me!
Thank you @AdamMartin
#!/bin/bash
# This script will remove all instances of a system keychain cert where:
# 1) The certificate subject matches the cert subject below.
# 2) It does not have the latest expiration date.
certSubject="Common-text-here i.e domain name"
certList=$( security find-certificate -c "${certSubject}" -p -a )
# echo "$certList"
# exit
# Get each cert into an array element
# Remove spaces
certList=$( echo "$certList" | sed 's/ //g' )
# Put a space after the end of each cert
certList=$( echo "$certList" | sed 's/-----ENDCERTIFICATE-----/-----ENDCERTIFICATE----- /g' )
# echo "$certList"
OIFS="$IFS"
IFS=' '
# read -a certArray <<< "${certList}"
declare -a certArray=($certList)
IFS="$OIFS"
i=-1
dateHashList=''
# Print what we got...
for cert in "${certArray[@]}"; do
let "i++"
echo '---------'
# echo "$cert"
# echo '--'
# Fix the begin/end certificate
cert=$( echo "$cert" | sed 's/-----BEGINCERTIFICATE-----/-----BEGIN CERTIFICATE-----/g' )
cert=$( echo "$cert" | sed 's/-----ENDCERTIFICATE-----/-----END CERTIFICATE-----/g' )
# echo "$cert"
# echo "$cert" | openssl x509 -text
certMD5=$( echo "$cert" | openssl x509 -noout -fingerprint -sha1 -inform pem | cut -d "=" -f 2 | sed 's/://g' )
certDate=$( echo "$cert" | openssl x509 -text | grep 'Not After' | sed -E 's|.*Not After : ||' )
certDateFormatted=`date -jf "%b %d %T %Y %Z" "${certDate}" +%Y%m%d%H%M%S`
echo "Cert ${i} : ${certDate} => $certDateFormatted"
echo "Cert ${i} : ${certMD5}"
NL=$'
'
dateHashList="${dateHashList}${NL}${certDateFormatted} ${certMD5}"
done
echo
dateHashList=$( echo "$dateHashList" | sort | uniq )
lines=$( echo "$dateHashList" | wc -l | tr -d ' ' )
let "lines--"
echo "[info] There are $lines lines in the certificate date-hash list."
echo
i=0
OIFS="$IFS"
IFS=$'
' # make newlines the only separator
for dateHash in $dateHashList; do
let "i++"
dateNum="${dateHash%% *}"
hash="${dateHash##* }"
echo "${i}| Hash : "$hash" | dateNum : "$dateNum""
if [[ i -ne $lines ]]; then
echo "=> This cert will be removed"
sudo security delete-certificate -Z $hash /Library/Keychains/System.keychain
echo
else
echo "=> This cert will not be touched because it has the latest expiration date."
fi
done
IFS="$OIFS"
exit 0
Thanks for this script! We issue certs to the serial number of the device. Is there a way to put in a variable for the certSubject? I would like to make that the Serial Number of the device. Thank you again! This script is awesome! @Gonzalo @AdamMartin
This script is awesome. How do you scope this policy? Is there a way a build a Smart Group with clients having duplicated certificates? Actually I found only a way to make a smart group with clients that have installed a common certificate but can not limit this to duplicates.
Thank you for sharing the script @Gonzalo .
It works like a charm.
@colorenz shared a updated version of this script, that works better if your certificate has the same name as the computer.
ComputerName=$(/usr/sbin/scutil --get ComputerName)
#LogFile
LogFile="/var/log/adcs-certificates.log"
#Log Function
logger() {
/bin/echo $(date "+%Y-%m-%d %H:%M:%S ") $1 >>"${LogFile}"
/bin/echo $(date "+%Y-%m-%d %H:%M:%S ") $1
}
logger "-------------------------------------"
logger "Start: Check for mutilpe Certificates"
certList=$(security find-certificate -c $ComputerName -p -a)
# Get each cert into an array element
# Remove spaces
certList=$(echo "$certList" | sed 's/ //g')
# Put a space after the end of each cert
certList=$(echo "$certList" | sed 's/-----ENDCERTIFICATE-----/-----ENDCERTIFICATE----- /g')
# echo "$certList"
OIFS="$IFS"
IFS=' '
# read -a certArray <<< "${certList}"
declare -a certArray=($certList)
IFS="$OIFS"
i=-1
dateHashList=''
# Print what we got...
for cert in "${certArray[@]}"; do
let "i++"
# Fix the begin/end certificate
cert=$(echo "$cert" | sed 's/-----BEGINCERTIFICATE-----/-----BEGIN CERTIFICATE-----/g')
cert=$(echo "$cert" | sed 's/-----ENDCERTIFICATE-----/-----END CERTIFICATE-----/g')
# echo "$cert"
# echo "$cert" | openssl x509 -text
certMD5=$(echo "$cert" | openssl x509 -noout -fingerprint -sha1 -inform pem | cut -d "=" -f 2 | sed 's/://g')
certDate=$(echo "$cert" | openssl x509 -text | grep 'Not After' | sed -E 's|.*Not After : ||')
certDateFormatted=$(date -jf "%b %d %T %Y %Z" "${certDate}" +%Y%m%d%H%M%S)
logger "Cert ${i} : ${certDate} => $certDateFormatted"
logger "Cert ${i} : ${certMD5}"
NL=$'
'
dateHashList="${dateHashList}${NL}${certDateFormatted} ${certMD5}"
done
dateHashList=$(echo "$dateHashList" | sort | uniq)
lines=$(echo "$dateHashList" | wc -l | tr -d ' ')
let "lines--"
logger "Info There are $lines lines in the certificate date-hash list."
i=0
OIFS="$IFS"
IFS=$'
' # make newlines the only separator
for dateHash in $dateHashList; do
let "i++"
dateNum="${dateHash%% *}"
hash="${dateHash##* }"
logger "${i}| Hash : "$hash" | dateNum : "$dateNum""
if [[ i -ne $lines ]]; then
logger "=> This cert will be removed"
sudo security delete-certificate -Z $hash /Library/Keychains/System.keychain
logger "=> Cert was $hash removed"
else
logger "=> This cert will not be touched because it has the latest expiration date."
fi
done
IFS="$OIFS"
logger "-------------------------------------"
logger "End: Check for mutilpe Certificates"
Use it together with this EA and create a smart group.

#!/bin/sh
#Get current number of Computer Certificate
#Current ComputerName
ComputerName=$(/usr/sbin/scutil --get ComputerName)
ComputerCertificateCount=$(/usr/bin/security find-certificate -a -c $ComputerName -p -Z "/Library/Keychains/System.keychain" | grep SHA-1 | wc | awk '{print $1}')
echo "ComputerCertificateCount"
echo "<result>$ComputerCertificateCount</result>"
Once again, thanks @colorenz
This updated script resolved a lot of headache in our environment @Gonzalo @colorenz, kudos! We've also seen instances where the duplicate certs match their username - any suggestions for how to phrase the first line so that this script searches for username?
Use with caution. The script looks a lot like one provided to me by Jamf support. I've recently discovered that it does review all certs based on computer name and keeps the one with the longest expire date. Unfortunately, a few Macs have had certs that have the name of the computer, but are self-assigned or issued by things other than our ADCS services. One I stumbled on was a cert that was self-assigned by CUPS. The result is that every ADCS PKI cert was removed but the Self-Assigned remained in place because it had the longest time to expire.