Posted on 06-13-2015 04:58 PM
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?
Solved! Go to Solution.
Posted on 06-14-2015 10:02 AM
You can use the security command to find the cert in the keychain and pipe the output into openssl.
Are you looking for a user- or system-cert?
For a cert in the System keychain you can do something like this in an extension attribute:
#!/bin/sh
certexpdate=$(/usr/bin/security find-certificate -a -c "name_of_your_cert" -p -Z "/Library/Keychains/System.keychain" | /usr/bin/openssl x509 -noout -enddate| cut -f2 -d=)
dateformat=$(/bin/date -j -f "%b %d %T %Y %Z" "$certexpdate" "+%Y-%m-%d %H:%M:%S")
echo "<result>$dateformat</result>"
Posted on 06-14-2015 10:02 AM
You can use the security command to find the cert in the keychain and pipe the output into openssl.
Are you looking for a user- or system-cert?
For a cert in the System keychain you can do something like this in an extension attribute:
#!/bin/sh
certexpdate=$(/usr/bin/security find-certificate -a -c "name_of_your_cert" -p -Z "/Library/Keychains/System.keychain" | /usr/bin/openssl x509 -noout -enddate| cut -f2 -d=)
dateformat=$(/bin/date -j -f "%b %d %T %Y %Z" "$certexpdate" "+%Y-%m-%d %H:%M:%S")
echo "<result>$dateformat</result>"
Posted on 06-14-2015 04:41 PM
damn you're awesome !!! It took me long but couldnt reach here.
thanks, so much :)
Posted on 06-14-2015 04:46 PM
This was cool, no doubt this JAMF community is best. Saves so much of time for lazy people like myself <*.->
Posted on 06-14-2015 11:38 PM
No problem.
You might run into this issue
later on, so feel free to vote it up ;)
Posted on 01-26-2016 08:36 AM
Hi @Chris
This is really useful, thanks!
Do you know how i could change it to retreive the expiry date of a certificate within the users keychain ?
i've tried as below but get an error, i've had a dig on the openssl options but can't figure the correct way.
#!/bin/sh
username=$( stat -f%Su /dev/console )
echo "$username"
if [ $username = "root" ]; then
#echo "Non AD user logged in- $username - stopping script"
exit
else
cert_name="OURCERT.ourcompany.com"
desired_keychain="/Users/$username/Library/Keychains/login.keychain"
certexpdate=$(/usr/bin/security find-certificate -a -c "$cert_name" -p -Z "desired_keychain" | /usr/bin/openssl x509 -noout -enddate| cut -f2 -d=)
dateformat=$(/bin/date -j -f "%b %d %T %Y %Z" "$certexpdate" "+%Y-%m-%d %H:%M:%S")
echo "<result>$dateformat</result>"
fi
90767:error:0906D06C:PEM routines:PEM_read_bio:no start line:/SourceCache/OpenSSL098/OpenSSL098-52.8.3/src/crypto/pem/pem_lib.c:648:Expecting: TRUSTED CERTIFICATE
Failed conversion of '' using format
%b %d %T %Y %Z''
date: illegal time format
usage: date [-jnu] [-d dst] [-r seconds] [-t west] [-v[+|-]val[ymwdHMS]] ...
[-f fmt date | [[[mm]dd]HH]MM[[cc]yy][.ss]] [+format]
<result></result>
Thank you
Posted on 01-26-2016 09:00 AM
Hey @May You might want to read through this thread.
https://jamfnation.jamfsoftware.com/discussion.html?id=9656
I believe you're running into the issue outlined in the later parts of the thread, having to do with the difference in the timezone. Since the cert timestamp gets recorded in GMT, regardless of where you're located, date
has an issue with converting time from the recorded format to another one because your Mac isn't located in the same GMT time zone. Is it stupid? Yep, it is, but that's apparently how it works, though I consider it a flaw if not an actual bug.
On the thread, @erikberglund offers a fix for this issue.
Posted on 01-26-2016 12:12 PM
@mm2270
I tried with the format that @erikberglund suggests but i couldn't get it to work,
though it did help me notice the missing $ before desired_keychain, Thank you!
so this works for pulling the date expiry date from a users certificate
#!/bin/sh
username=$( stat -f%Su /dev/console )
if [ $username = "root" ]; then
#echo "Non AD user logged in- $username - stopping script"
exit
else
cert_name="OURCERT.ourcompany.com"
desired_keychain="/Users/$username/Library/Keychains/login.keychain"
certexpdate=$(/usr/bin/security find-certificate -a -c "$cert_name" -p -Z "$desired_keychain" | /usr/bin/openssl x509 -noout -enddate | cut -f2 -d=)
dateformat=$(/bin/date -j -f "%b %d %T %Y %Z" "$certexpdate" "+%Y-%m-%d %H:%M:%S")
echo "<result>$dateformat</result>"
fi
and i've got this one together to see if it expires within an amount of days (please excuse my scripting..:)
#!/bin/sh
username=$( stat -f%Su /dev/console )
if [ $username = "root" ]; then
#echo "Non AD user logged in- $username - stopping script"
exit
else
cert_name="OURCERT.ourcompany.com"
desired_keychain="/Users/$username/Library/Keychains/login.keychain"
expires="2419200"
#1 days = 86400
#7 days = 604800
#14 days = 1209600
#21 days = 1814400
#28 days = 2419200
certexpdate=$(/usr/bin/security find-certificate -a -c "$cert_name" -p -Z "$desired_keychain" | /usr/bin/openssl x509 -checkend "$expires")
echo "$certexpdate in under 28 days - $cert_name"
fi
Posted on 12-08-2016 08:22 AM
This works fine if there's only a single cert matching that name. If there are multiples, it will only pull the date from the first.
Anyone have any ideas how you could get the expiry date for multiple certs by the same name, and then determine which is the newest?
Posted on 12-10-2016 01:46 AM
@CAJensen01 can you expand upon why you're looking at this?
Typically multiple copies of the same cert are not a problem, if an expired cert is in the keychain then it shouldn't be used. Instead the non-expired one's should be by the OS
Posted on 12-11-2016 01:34 PM
What I'm looking for @bentoms, is if I have multiple certs with the same name and different expiry dates, the logic to locate and select the one with the expiry date the furthest in the future.
When associating that cert with an application (creating an identity preference in the keychain), it is ideal to select the one that is furthest out into the future, so that if (for example) the default one selected expires in only 3 months, the script doesn't create the identity preference based off of that certificate, but rather, the one that will impact the users the furthest into the future. I haven't quite found the logic to do this, but haven't spent a ton of time trying, either.
Posted on 12-12-2016 04:05 PM
Being able to select a single specific cert if you have multiples of the same name is something I'd like to see. I can delete a certificate by providing the hash, why can't I view one the same way?
I'd really like to be able to clean up old certs as well as certs issued by our older SHA1 infrastructure without trying to parse a giant dump of cert data.
Posted on 01-10-2017 05:45 AM
Quick question....so we have certs that have the same name "name.company.com" so I have a script that looks for that, but so far it only finds the first one. We have issues where one of them is expired, so we need to renew it.
Also, in conditions that there a multiple certs with the same name, is there a way to delete all but one of them via script?
Posted on 01-10-2017 06:17 AM
@CAJensen01 I used the cert beginning text to count the matches
#!/bin/sh
Certs = $( security find-certificates -a -c <NAME_OF_CERT> -p <KEYCHAIN PATH> | grep -c -e '-----BEGIN CERTIFICATE-----' )
if [[ $Certs -gt 1 ]]; then
echo "More than one cert found!"
fi
@roiegat you can parse the Certs into an array using the same tags on the cert
#!/bin/sh
IFS=","
CertList= $( security find-certificates -a -c <NAME_OF_CERT> -p <KEYCHAIN PATH> | sed s/"-----END CERTIFICATE-----"/"-----END CERTIFICATE-----,"/g)
Array=($CertList)
for ACert in "${Array[@]}"
do
#Do date check on ACert variable and remove if expired
#Recount certs etc etc
done
Posted on 01-10-2017 06:52 AM
@Key1 Great info!
Got one more trick question, is there way to script the capability to have the cert be allowed to use by an application? So currently in Casper we set it up so all applications can use it, but ideally we'd like just our VPN app to access it. So always looking for a scripting method to limit it. But priority is to remove duplicates first...so this is a great start.
Posted on 01-10-2017 07:23 AM
@roiegat To do it manually you select a cert that has a private key, double click the key and add the app to the access control list.
Not something I've done by script before but looking at the man page for security: [https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/security.1.html](link URL)
it looks like you can import an item and set its application access control list at that time with -T
import inputfile [-k keychain] [-t type] [-f format] [-w] [-P passphrase] [options...] Import one or more items from inputfile into a keychain. If keychain isn't provided, items will be imported into the user's default keychain. Options: -k keychain Specify keychain into which item(s) will be imported. -t type Specify the type of items to import. Possible types are cert, pub, priv, ses-sion, session, sion, cert, and agg. Pub, priv, and session refer to keys; agg is one of the aggregate types (pkcs12 and PEM sequence). The command can often figure out what item_type an item contains based in the filename and/or item_format. -f format Specify the format of the exported data. Possible formats are openssl, bsafe, raw, pkcs7, pkcs8, pkcs12, x509, openssh1, openssh2, and pemseq. The command can often figure out what format an item is in based in the filename and/or item_type. -w Specify that private keys are wrapped and must be unwrapped on import. -x Specify that private keys are non-extractable after being imported. -P passphrase Specify the unwrapping passphrase immediately. The default is to obtain a secure passphrase via GUI. -a attrName attrValue Specify optional extended attribute name and value. Can be used multiple times. This is only valid when importing keys. -A Allow any application to access the imported key without warning (insecure, not recommended!) -T appPath Specify an application which may access the imported key (multiple -T options are allowed)
Posted on 01-10-2017 07:48 AM
So having issue with the script above....when I run it with my settings I get:
./cert.sh: line 17: -----END CERTIFICATE-----: command not found
Here's line 17:
CertList= $(/usr/bin/security find-certificate -a -c "$cert_name" -p "$desired_keychain" | sed s/"-----BEGIN CERTIFICATE-----"/"-----END CERTIFICATE-----"/g)```
It seems like it doesn't like the comma at the end of END CERTIFICATE line, removing it spits out the certs and then gets the following error:
......
nuiA/9pZRktznpvy3dOrLKFJfsqoCu2E5MUxUll79DUF6rN/UIF4dDXUDYB3T5Ac
qLNuIS5yCvHquzBrkBF0XjqX1olQN0X5Whox8UgA3e0pKTO9htER9Q==
-----END CERTIFICATE-----: File name too long
So not sure what I'm doing wrong here. The first script works in determining there is more then one. Just trying to break them up now and checking the dates. ```
Posted on 01-10-2017 07:53 AM
@roiegat yup sorry my mistake shouldn't be BEGIN CERTIFICATE, i updated my sed command above, we are adding in a , after every END CERTIFICATE so we can use it for a delimiter for the array.
#!/bin/sh
CertList= $( security find-certificates -a -c <NAME_OF_CERT> -p <KEYCHAIN PATH> | sed s/"-----END CERTIFICATE-----"/"-----END CERTIFICATE-----,"/g)
Posted on 01-10-2017 08:11 AM
You can list all installed certificates for a given keychain (or the System keychain if run as root and no keychain is specified) with the following command
/usr/bin/security find-certificate -a | awk -F'"' '/labl/{print $4}'
From there, you can use grep, awk or some other regex matching tool to grab only the certificates you care about. For example, using the example of name.company.com
you posted above, it might be something like
/usr/bin/security find-certificate -a | awk -F'"' '/labl/{print $4}' | grep "^name.company.com$"
Using the ^
and $
start and end line indicators will ensure its only getting the certs with the exact name you specify and not any others that use the same string within their names.
As mentioned above, you could drop those into an array if you needed to do something to them later. Or just feed that output back to the script to loop over them.
Posted on 01-10-2017 10:47 AM
Posted on 02-02-2017 08:14 AM
@mm2270 thank you for your snippet. would you know by any chance how I would grep a certificate name that's randomly generated by the JSS? For example -- after enrollment, a cert with common name "D50FDCBA-C7F1-4DB2-A9CA-46AB3A3A164B" gets generated along with a private key into keychain.
I have a couple of machines that have issues enrolling -- jamf.log states issue with device signature. I was able to replicate this in a virtual machine only after deleting the JSS certs, and also removing framework, I was then able to run a quickadd package for enrollment.
Any help would be greatly appreciated!
Posted on 08-30-2017 03:17 PM
Thanks to everyone who took the answer to this question as far as you did, it was definitely helpful - we hope this will be helpful to others as well.
We needed to enumerate only the AD issued certs in the System keychain as part of an 802.1x troubleshooting exercise. The script below, which will work standalone or via ARD, uses dsconfigad to get the bound computer's name, then returns ALL the certs that match the AD name in the System Keychain. It then passes those certs into a proper array; the array's values are then passed to openssl in a way that it will accept (probably the trickiest bit). We wanted the output to return the cert's subject, issuer, expiration date, and serial number, but you can season to taste with anything you like that openssl will give you.
Enjoy!
#!/bin/sh
# get the AD name and domain, make the FQDN of the machine to match the cert we're looking for
AD_NAME=$(dsconfigad -show | grep "Computer Account" | awk '{print $4}' | rev | cut -c 2- | rev)
# 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 keychain for any Computer certificates that match the AD Name of the computer.
#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=","
CertList=$(/usr/bin/security find-certificate -a -c $AD_NAME -p -Z "/Library/Keychains/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)
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")
echo "$certsubject,$certissuer,$certexpdateformatted,$certserial"
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
echo "No Matching Certs Found"
fi
Posted on 11-17-2017 06:48 PM
@ millersys_seth
Thank you very much for posting. We were needing this very thing and I wanted to post version in case anyone found it helpful. Just added a bit to compare expiration date so we can count at least one machine CA entry valid- as we often see expired entries. Also edited out seconds in time; didn't think they were needed for our purpose. Seems to work but I haven't tested in Jamf Pro yet.
#!/bin/sh
# Get the AD name, domain and Date/Time, make the FQDN of the machine to match the cert we're looking for
AD_NAME=$(dsconfigad -show | grep "Computer Account" | awk '{print $4}' | rev | cut -c 2- | rev)
curr_Date=$(date "+%Y-%m-%d %H:%M")
# 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"
#Look through the entire system keychain for any Computer certificates that match the AD Name of the computer.
#Then 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=","
CertList=$(/usr/bin/security find-certificate -a -c $AD_NAME -p -Z "/Library/Keychains/System.keychain" | sed s/"-----END CERTIFICATE-----"/"-----END CERTIFICATE-----,"/g)
if [ -n "$CertList" ]; then
Array=($CertList)
for ACert in "${Array[@]}"
do
#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)
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")
# Next 3 not used, but may come in handy if we need to match on a different AD value later.
#certissuer=$(/usr/bin/openssl x509 -noout -issuer <<< $ACert)
#certserial=$(/usr/bin/openssl x509 -noout -serial <<< $ACert)
#echo "$certsubject,$certissuer,$certexpdateformatted,$certserial"
#Can comment out the next echo but good for testing
echo "$certsubject,$certexpdateformatted"
done
fi
if [ "$certexpdateformatted" > "$curr_Date" ]; then
echo "Valid CA Found"
#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
echo "No Valid CA Found"
fi
Posted on 05-07-2021 03:55 PM
@CAJensen01 @roiegat @millersys_seth @Key1
Just curious, What's the purpose of performing the sed regex find/replace operation on the target certificates like the snippet below:
| sed s/"-----END CERTIFICATE-----"/"-----END CERTIFICATE-----,"/g
Isnt...
CertList=$(/usr/bin/security find-certificate -a -c $AD_NAME -p -Z "/Library/Keychains/System.keychain")
...enough to get the certificate(s) you are looking for?
Or is adding the , (1 comma) in the sed find and replace operation just a way to display duplicates better (with IFS set to a , (1 comma)?
Posted on 05-07-2021 07:09 PM
I'm not going to post all the code right now, but if someone is interested I can put something together. Since we're coming at this from an 802.1x angle, I've found that reading /Library/Preferences/SystemConfiguration/com.apple.network.eapolclient.configuration.plist gives you a lot of information about your configured 802.1x networks. Combine this with reading the identities on the keychain, and you can get a full picture of which certs are in use, when they're going to expire, etc. and use that information to take action (like deleting certs that are not in use, instead of going by expiration dates).
I have a script that cycles through our networks and evaluates them, outputting info like this for each network:
WiFiSSID Network Configuration: Cert SHA-1: C7205B5F322B3572B9BE1C54AD16C0A25C87C8C3 Cert Common name: COMPUTERNAME.sample.com Common name matches this hostname Cert expires on Apr 29 19:54:53 2022 GMT TLS trust properly set for *.sample.com PASSED - 0 problem(s) found for WiFiSSID
Posted on 05-25-2021 10:04 PM
Hi!
The information from this thread has helped me list out all the certs on a mac with its expiry information however it's very flakey when setting it up as a Jamf extension attribute.
Using the code below as an EA, i was able to get the cert info on some hosts but on others it was not able to and reporting "<result>No Matching Certs Found</result>" instead.
The odd thing is I'm able to get the cert information when running this script locally on the affected mac, Jamf just doesn't like it as an EA.
Any ideas?
#!/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 )
cert_name="name of cert"
desired_keychain="/Users/$username/Library/Keychains/login.keychain"
# 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 keychain for any Computer certificates that match the AD Name of the computer.
#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=","
CertList=$(/usr/bin/security find-certificate -a -c $cert_name -p | 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)
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")
listResults+="Serial: $certserial | Expiry Date: $certexpdateformatted
"
done
echo "<result>$listResults</result>"
#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
echo "<result>No Matching Certs Found</result>"
fi
02-17-2023 06:05 AM - edited 02-17-2023 06:06 AM
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>"
Posted on 02-17-2023 06:27 AM
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>"
Posted on 02-19-2023 11:00 PM
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.
02-19-2023 11:21 PM - edited 02-19-2023 11:28 PM
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>"
Posted on 02-20-2023 02:44 AM
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>"
02-21-2023 12:52 PM - edited 02-21-2023 12:59 PM
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
Posted on 02-24-2023 12:21 AM
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
Posted on 07-13-2023 06:02 AM
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