Skip to main content
Jamf Nation, hosted by Jamf, is a knowledgeable community of Apple-focused admins and Jamf users. Join us in person at the ninth annual Jamf Nation User Conference (JNUC) this November for three days of learning, laughter and IT love.

Getting Expiry date of Certificate

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?

Like Comment
Order by:
SOLVED Posted: by Chris

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>"
Like
SOLVED Posted: by cindySingh

damn you're awesome !!! It took me long but couldnt reach here.

thanks, so much :)

Like
SOLVED Posted: by laeeqhumam

This was cool, no doubt this JAMF community is best. Saves so much of time for lazy people like myself <*.->

Like
SOLVED Posted: by Chris

No problem.
You might run into this issue
later on, so feel free to vote it up ;)

Like
SOLVED Posted: by May

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

Like
SOLVED Posted: by mm2270

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.

Like
SOLVED Posted: by May

@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
Like
SOLVED Posted: by CAJensen01

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?

Like
SOLVED Posted: by bentoms

@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

Like
SOLVED Posted: by CAJensen01

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.

Like
SOLVED Posted: by alexjdale

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.

Like
SOLVED Posted: by roiegat

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?

Like
SOLVED Posted: by Key1

@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
Like
SOLVED Posted: by roiegat

@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.

Like
SOLVED Posted: by Key1

@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)
Like
SOLVED Posted: by roiegat

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. ```

Like
SOLVED Posted: by Key1

@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)
Like
SOLVED Posted: by mm2270

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.

Like
SOLVED Posted: by roiegat

@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.

Like
SOLVED Posted: by normanchan

@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!

Like
SOLVED Posted: by millersys_seth

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
Like
SOLVED Posted: by Xopher

@ 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
Like