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?
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>"
damn you're awesome !!! It took me long but couldnt reach here.
thanks, so much :)
This was cool, no doubt this JAMF community is best. Saves so much of time for lazy people like myself <*.->
No problem.
You might run into this issue
later on, so feel free to vote it up ;)
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
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.
@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
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?
@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
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.
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.
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?
@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
@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.
@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)
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.
```
@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)
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.
@Key1 Fixing that I still get:
bash-3.2$ sudo ./cert.sh
./cert.sh: line 9: -----BEGIN: command not found
When running the script.
@mm2270 You method worked well...but once I have all the names, when I run an expiration date check, it seems to only get the first one.
So back to more testing.
@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!
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
@ 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
@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)?
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
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
Reply
Enter your E-mail address. We'll send you an e-mail with instructions to reset your password.